Repository navigation
Conversation
`Connection::poll` selects the next outbound-ready `SubstreamRequested`
via `iter_mut().next()` and calls `.extract()` on it. `extract()` moves
the data out and replaces the variant with `Done`, then relies on
`extracted_waker.wake()` to schedule the cleanup poll that removes the
entry from `requested_substreams`.
The waker is only populated by the `Future::poll` impl when the entry
first observes a `Poll::Pending`. If the entry is extracted before that
first poll ever happens — which can occur when `FuturesUnordered`'s
ordering puts a freshly-pushed entry at the head of `iter_mut()`, or
when the muxer returns `poll_outbound = Ready` on the same loop
iteration as the request was pushed — the waker is `None`, no wake is
scheduled, and the `Done` entry persists.
The next outbound-ready iteration's `iter_mut().next()` lands on that
stale `Done` entry and `extract()` panics.
The intent at this site has always been "find a request waiting for a
substream"; making that explicit with `iter_mut().find(|r| matches!(r,
SubstreamRequested::Waiting { .. }))` removes the dependence on
`FuturesUnordered`'s scheduling. Eager removal of the `Done` entry is
not possible through `FuturesUnordered`'s public API while iterating
it, so the existing wake-driven cleanup stays in place. The number of
un-cleaned `Done` entries is bounded by outstanding outbound requests
and is drained naturally as later events trigger connection polls.
Adds a unit test reproducing the failure shape directly on a
`FuturesUnordered<SubstreamRequested<()>, DeniedUpgrade>>`.
panic!("cannot extract twice") in Connection::poll
|
Thank you for the PR @caniko. We appreciate all contributions, including ones that were created with the help of LLMs/ AI-tools. That said, the sheer amount of text in this PR (descriptions, code comments) makes it very time-consuming to review a change that appears to be just a one-liner.
|
|
Apologies @elenaf9, I usually let these sit and wait as draft, and improve the PR before asking for review. This PR seems to have skipped this step, anyway, it is in the past. I tried reducing the text, and hope it is good enough for review |
|
It seems this issue might have been automatically generated. To help us address it effectively, please provide additional details. We value the use of LLMs for code generation and welcome your contributions but please ensure your submission is of such quality that a maintainer will spend less time reviewing it than implementing it themselves. Verify the code functions correctly and meets our standards. If your change requires tests, kindly include them and ensure they pass. If no further information is provided, the issue will be automatically closed in 7 days. Thank you for your understanding and for aiding us in maintaining quality contributions! |
|
It seems this issue might have been automatically generated. To help us address it effectively, please provide additional details. We value the use of LLMs for code generation and welcome your contributions but please ensure your submission is of such quality that a maintainer will spend less time reviewing it than implementing it themselves. Verify the code functions correctly and meets our standards. If your change requires tests, kindly include them and ensure they pass. If no further information is provided, the issue will be automatically closed in 7 days. Thank you for your understanding and for aiding us in maintaining quality contributions! |
|
@elenaf9 I think there is something wrong with the bot |
jxs
left a comment
There was a problem hiding this comment.
Hi, and thanks for this! Left some comments
panic!("cannot extract twice") in Connection::poll|
Updated per review: Validation run locally with an ad hoc Nix Rust shell because the downstream project flake is currently blocked by a stale
|
|
This pull request has merge conflicts. Could you please resolve them @caniko? 🙏 |
| match muxing.poll_outbound_unpin(cx)? { | ||
| Poll::Pending => {} | ||
| Poll::Ready(substream) => { | ||
| if let Some((user_data, timeout, upgrade)) = requested_substreams | ||
| .iter_mut() | ||
| .find_map(SubstreamRequested::extract) | ||
| { |
There was a problem hiding this comment.
sorry I just noticed that If the muxer returns Poll::Ready(substream) but all entries are Done (so find_map returns None), the stream is silently dropped
There was a problem hiding this comment.
Good catch, fixed in 09f0491. The continue is now inside the if let Some block so we only loop back when we actually consumed the stream. When no Waiting entry exists, we fall through to the rest of poll logic.
If poll_outbound_unpin returns Ready(substream) but all requested_substreams entries are Done, do not unconditionally continue — the substream had no matching request and the loop would busy-spin. Fall through instead.
|
@jxs Would you prefer restoring the old |
Published through GitHub’s verified commit API during the hosted-only PR maintenance pass.
|
fix(swarm): open outbound streams only for waiting requests in |
|
The outbound-stream ownership repair and regressions are at |
Fix
Select a waiting outbound-substream request before polling the muxer. Completed or absent requests cannot cause an outbound stream to be opened and then discarded; this also avoids extracting a completed request twice.
Regression coverage
Tests cover polling with no waiting request and serving two requested streams while an extra stream is available.
Qualification
Current head:
0c6b55925053f4c5a859448a5828e076f9efabaa. Continuous integration and Interoperability Testing require maintainer approval. The remaining review thread stays open pending swarm qualification. No local tests or evaluations were run during this maintenance pass.