Skip to content

test(uring): pin down what a send staged on the way out is owed - #3153

Merged
kixelated merged 1 commit into
devfrom
claude/github-issue-3141-dd66a6
Aug 30, 2026
Merged

test(uring): pin down what a send staged on the way out is owed#3153
kixelated merged 1 commit into
devfrom
claude/github-issue-3141-dd66a6

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Closes #3141, by disproving it.

Summary

  • uring: a close can be lost when the worker is torn down before its send completes #3141 claims a datagram handed to udp::Socket on the last turn before block_on returns never reaches the kernel, so dropping the worker loses it. Measured on Linux 6.19 (podman, real io_uring): it does reach the kernel. Worker's drop submits whatever SQEs are still staged (pump -> submit) and then waits for their completions before freeing anything the kernel may still read, so the packet goes out. The staging is real; the loss is not.
  • End to end, the one-shot client shape the issue describes works: dial, close(42, "done"), wait for the close to be published, stop the runtime outright. The peer sees App { code: 42, reason: "done" } 26ms after accepting, not the 10s idle timeout. It cannot lose the CONNECTION_CLOSE, because quiche only reports is_closed() a draining period after that packet was sent, so the close is published many turns after the send was submitted.
  • A completion-aware TxBuf::send would therefore buy nothing, and it would put a per-send handle on the flush hot path.
  • There is a real loss, but with a different mechanism, a layer above the socket: a close the worker is never driven past. close only records the code on the connection; the driver task is what frames the CONNECTION_CLOSE and hands it to a TxBuf. Stop the worker on the call that asked for the close and no packet is ever built, so the peer idles out (measured: 10.005s, TimedOut). No socket API change reaches that, since there is nothing staged to complete. The remedy is to drive until the close is published, which moq_net's session machine already does.

So this lands as tests and a written-down contract rather than an API change: the guarantee Worker's drop provides was accidental and undocumented, which is what made the bug look real.

  • tests/teardown.rs pins both halves: a raw datagram staged by the last turn still reaches the wire, and a published close has already left a client that then stops.
  • udp::TxBuf::send, Worker, and quic::Connection now say which half of the window each of them owns.

Public API changes

None. Doc comments and a new integration test only.

Wire behavior changes

None.

Test plan

In the podman container on Linux 6.19 (real io_uring, no skips):

  • cargo test --locked -p moq-uring -j 1 -- 44 tests, all pass, including the two new ones.
  • cargo clippy --locked -p moq-uring --all-targets -j 1 -- -D warnings -- clean.
  • RUSTDOCFLAGS="-D warnings" cargo doc --locked -p moq-uring --no-deps -- clean.
  • cargo fmt -p moq-uring -- --check -- clean.

On the host: just check and just test both pass (263 tests). macOS compiles moq-uring to nothing, so the container runs above are what actually covered this crate.

The two disproof runs, for the record:

staged_send_survives_worker_drop ... ok        (datagram delivered)
awaited close:   server saw application closed: code=42 reason="done" after 25.668524ms
unawaited close: server saw connection timed out after 10.005042693s

Cross-package sync

No row applies: no wire format, no moq-ffi surface, no CLI flag.

(Written by Claude Opus 5)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3720385fd2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-uring/src/udp.rs
Comment on lines +586 to +587
///
/// This only stages an SQE, so the datagram reaches the kernel when the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid promising that send only stages an SQE

When the submission queue lacks enough free slots, particularly for a non-GSO send split into multiple datagrams, Shared::push calls ring.submit() inline to make room. Thus TxBuf::send can enter the ring before returning rather than waiting for the worker's next turn, and the new single-datagram test with the default deep queue never exercises this path. Narrow the documented guarantee to SQEs that remain staged, or cover and guarantee the inline-submission case separately.

AGENTS.md reference: AGENTS.md:L109-L109

Useful? React with 👍 / 👎.

#3141 claims a datagram handed to `udp::Socket` on the last turn before
`block_on` returns is lost when the worker is dropped, because
`TxBuf::send` only stages an SQE and `Shared::push` only enters the ring
when the submission queue fills. It is not: `Worker`'s drop submits
whatever is still staged and then waits for the completions, so the
packet goes out. Measured on Linux 6.19, a client that dials, closes,
waits for the close to be published and then stops the runtime outright
is seen closing by its peer in 26ms, application code and reason intact.

What does get lost is a `close` the worker is never driven past: the
driver task is what turns the recorded code into a CONNECTION_CLOSE
packet, so stopping the worker on the call that asked for the close
means no packet is ever built and the peer idles out 10s later. That is
a layer above the socket and no completion-aware `send` reaches it.

Both guarantees now have a test, and the contract is written down where
each half of it lives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kixelated
kixelated force-pushed the claude/github-issue-3141-dd66a6 branch from 3720385 to 3afabc2 Compare August 30, 2026 01:47
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T01:49:52.193974Z 3afabc2 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kixelated
kixelated merged commit 7494084 into dev Aug 30, 2026
2 checks passed
@kixelated
kixelated deleted the claude/github-issue-3141-dd66a6 branch August 30, 2026 01:55
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.

1 participant