Skip to content

Failed reask source delete needed explicit download-owner detachment

Summary

The failed TCP reask path in CPartFile::Process() could delete a CUpDownClient while the client was still present in download-owner structures. That made later cleanup capable of seeing the same source again and contributed to double-free / heap-corruption risk around client destruction.

The observed morning dump showed heap corruption during CUpDownClient destruction from the CPartFile::Process() failed-reask flow. The minidump did not include pageheap allocation history, so the exact duplicate owner cannot be proven from that dump alone. The owner-detach fix closes the concrete lifetime gap at the failed-reask delete site.

Provenance

This has mixed provenance.

The fragile lifetime contract is inherited from stock/community code:

  • AskForDownload() returned false as a liveness signal.
  • TryToConnect() historically documented that false meant the client was deleted.
  • CPartFile::Process() historically carried the warning that the call may delete the client and then broke out of the source loop.

However, the exact local delete cur_src in CPartFile::Process() was introduced by eMuleBB lifetime-explicit refactoring in commit 5037876 (FIX: make TryToConnect lifetime explicit). That refactor moved deletion responsibility from TryToConnect() into callers. The underlying raw-pointer ownership model is inherited, but eMuleBB owns the exact failed-reask deletion site that was hardened here.

The workspace does not currently contain a 0.73 stock tag or branch. The newest local/upstream community comparison point observed during the trace was 0.72a-community.

eMuleAI Latest Status

Checked against eMuleAI upstream master at 8e34bdec2b7e4fe9e4307df9d80f691804be99ed (eMuleAIv1.4) on 2026-05-24. eMuleAI latest still uses the inherited AskForDownload() liveness contract and TryToConnect() can still delete through SafeDelete(). Its CPartFile::Process() call site still has the historical if (!cur_src->AskForDownload()) / break shape, not eMuleBB's explicit caller-side delete cur_src.

eMuleAI also has broader SafeDelete() / runtime-reference machinery and download-queue removal helpers. Therefore the exact eMuleBB regression shape is not present there, but the underlying lifetime model remains closely related and should not be treated as a clean ownership design.

Conclusion: eMuleAI latest does not carry this exact eMuleBB caller-delete bug, but it still carries a related inherited self-delete / SafeDelete lifetime model.

Resolution

Fixed on eMuleBB main by app commit b65b3c9 (BUG-017 detach failed reask sources before delete). The fix detaches the failed reask source from download ownership before deleting the client at the caller-owned failure site.

Regression coverage was added in test commit 2a95c98 (BUG-017 cover failed reask source ownership).

Validation

  • Native Debug parity: passed, 729 passed / 0 failed / 237 skipped.
  • Native Release parity: passed, 726 passed / 0 failed / 237 skipped.
  • x64 Debug app build: passed.
  • x64 Release app build: passed.