Skip to content

Fix unbounded growth of the waiting queue in async RecvFut (#182) - #189

Open
jaideeppyne wants to merge 1 commit into
zesterer:masterfrom
jaideeppyne:fix/async-recv-duplicate-hooks
Open

jaideeppyne wants to merge 1 commit into
zesterer:masterfrom
jaideeppyne:fix/async-recv-duplicate-hooks

Conversation

@jaideeppyne

Copy link
Copy Markdown

Fixes #182.

When an async RecvFut is polled after its hook has been fired but it fails to actually receive a message (e.g. the message was stolen by another receiver on a shared channel), the hook is re-inserted into Chan::waiting. The hook's woken flag was never cleared on re-insertion, so every subsequent poll that fails to receive re-observes woken == true and pushes yet another duplicate clone of the same hook, growing waiting without bound (a memory leak, and it corrupts the waiting-receiver accounting).

Interleaving, with two receivers sharing one channel:

  1. R1 polls recv_async — hook H1 is registered in waiting, woken = false.
  2. A sender pops H1 and calls fire(), setting woken = true; the message goes to the queue.
  3. R2 (or a try_recv) steals that message first.
  4. R1 is polled again: recv_sync fails, so H1 is re-inserted — but woken stays true.
  5. Every later failing poll re-observes woken == true and pushes another copy of H1.

This clears the woken flag while re-arming and re-inserting the hook, under the channel lock. fire also runs under that lock and the hook is not in waiting during this window, so the reset is race-free. It only triggers on the path where recv_sync has already failed (queue empty), so no message is dropped. This is orthogonal to #177's missed-wakeup fix.

Includes a deterministic regression test: before the fix, 10 spurious polls leave 10 duplicate hooks in waiting; after, exactly 1. Full test suite passes across all features; fmt and clippy are clean.

When an async `RecvFut` is polled after its hook has been fired but it
fails to actually receive a message (e.g. the message was stolen by
another receiver on a shared channel), the hook must be re-inserted into
the `waiting` queue. However, the hook's `woken` flag was never cleared
on re-insertion, so every subsequent poll that failed to receive would
observe `woken` still set and push yet another duplicate clone of the
same hook onto `waiting`, growing it without bound and leaking memory.

Clear the `woken` flag while re-arming and re-inserting the hook, under
the channel lock. `fire` also runs under this lock and the hook is not
in `waiting` during this window, so the reset is race-free. Add a
deterministic regression test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RecvFut::poll pushes duplicate hooks into waiting queue when woken flag is not cleared, causing unbounded VecDeque growth

1 participant