Skip to content

Conversation

@kixelated
Copy link
Collaborator

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Walkthrough

This pull request introduces handshake synchronization in the web-transport-quiche driver. It adds a waker-based mechanism in DriverState (handshake_wakers, poll_handshake(), complete_handshake()), and wires handshake completion to Driver::connected. Incoming::accept() is changed from synchronous to async returning Result<Connection, ConnectionError>, and now awaits TLS handshake completion before yielding a connection, with explicit handling of handshake failures. The server accept loop now awaits incoming.accept() and only enqueues successful connections.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive No description was provided by the author, making it impossible to evaluate relevance to the changeset. Add a pull request description explaining the purpose of making accept async and any related implementation details or motivation.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: making the accept method async, which is evident in the server.rs changes showing the synchronous accept call replaced with awaited accept.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch async-accept

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-transport-quiche/src/ez/server.rs (1)

199-204: ⚠️ Potential issue | 🟡 Minor

Stale doc comment — handshake is no longer complete at this point.

The doc says "with the TLS handshake already complete", but after this PR, the handshake completes inside accept().await. The Incoming is now created before the handshake finishes.

Suggested doc fix
-/// A pre-accepted QUIC connection with the TLS handshake already complete.
-///
-/// The peer address is available before calling [Incoming::accept].
+/// A pending QUIC connection whose TLS handshake may still be in progress.
+///
+/// Call [Incoming::accept] to await handshake completion and obtain the [Connection].
+/// The peer address is available before calling [Incoming::accept].
🤖 Fix all issues with AI agents
In `@web-transport-quiche/src/server.rs`:
- Around line 160-168: The accept loop is blocked by awaiting
incoming.accept().await inside the select arm; instead create a FuturesUnordered
(e.g., tls_handshakes) and, when Some(incoming) = self.inner.accept() fires,
push a boxed/pinned future that performs incoming.accept().await into that
FuturesUnordered rather than awaiting inline; add a new select branch that polls
completed futures from tls_handshakes, and in that branch match the result and
on Ok(conn) push Box::pin(h3::Request::accept(conn)) into self.accept (on Err
log the warning), so TLS handshakes proceed concurrently with accepting new
connections and draining h3 handshakes.
🧹 Nitpick comments (1)
web-transport-quiche/src/ez/driver.rs (1)

85-104: Duplicate waker accumulation in handshake_wakers.

Each re-poll of poll_handshake pushes a new clone of the waker without de-duplicating or replacing the previous entry. Spurious wakes (e.g., from local.poll / remote.poll registrations) will cause the same task's waker to accumulate. This is functionally harmless (extra wakes are idempotent), but slightly wasteful.

A common pattern is to either clear the vec before pushing, or use a single Option<Waker> if only one waiter is expected. Given that multiple tasks could legitimately wait (and the handshake completes once), the Vec is reasonable, but you may want to guard against the same waker being pushed repeatedly from a single task.

Comment on lines 160 to 168
Some(incoming) = self.inner.accept() => {
let conn = incoming.accept();
self.accept.push(Box::pin(h3::Request::accept(conn)));
match incoming.accept().await {
Ok(conn) => {
self.accept.push(Box::pin(h3::Request::accept(conn)));
}
Err(err) => {
tracing::warn!("ignoring failed connection handshake: {}", err);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Head-of-line blocking: incoming.accept().await inside the select! arm stalls the accept loop.

The incoming.accept().await runs inside the body of the select! match arm. While it awaits the TLS handshake for one connection, the entire loop is blocked — no new QUIC connections can be accepted and no completed H3 handshakes can be processed. A slow or malicious client that delays its handshake will stall the server.

Consider spawning the TLS handshake into the existing FuturesUnordered (or a second one) so handshakes proceed concurrently, similar to how H3 handshakes are already handled:

Sketch of concurrent handshake approach
 pub struct Server<M: ez::Metrics = ez::DefaultMetrics> {
     inner: ez::Server<M>,
+    handshake: FuturesUnordered<BoxFuture<'static, Result<ez::Connection, ez::ConnectionError>>>,
     accept: FuturesUnordered<BoxFuture<'static, Result<h3::Request, ServerError>>>,
 }

Then in the accept loop, push the TLS handshake future instead of awaiting it inline, and add a third select! branch to drain completed handshakes into the H3 accept pipeline:

 Some(incoming) = self.inner.accept() => {
-    match incoming.accept().await {
-        Ok(conn) => {
-            self.accept.push(Box::pin(h3::Request::accept(conn)));
-        }
-        Err(err) => {
-            tracing::warn!("ignoring failed connection handshake: {}", err);
-        }
-    }
+    self.handshake.push(Box::pin(incoming.accept()));
+ }
+ Some(res) = self.handshake.next() => {
+    match res {
+        Ok(conn) => {
+            self.accept.push(Box::pin(h3::Request::accept(conn)));
+        }
+        Err(err) => {
+            tracing::warn!("ignoring failed connection handshake: {}", err);
+        }
+    }
  }
🤖 Prompt for AI Agents
In `@web-transport-quiche/src/server.rs` around lines 160 - 168, The accept loop
is blocked by awaiting incoming.accept().await inside the select arm; instead
create a FuturesUnordered (e.g., tls_handshakes) and, when Some(incoming) =
self.inner.accept() fires, push a boxed/pinned future that performs
incoming.accept().await into that FuturesUnordered rather than awaiting inline;
add a new select branch that polls completed futures from tls_handshakes, and in
that branch match the result and on Ok(conn) push
Box::pin(h3::Request::accept(conn)) into self.accept (on Err log the warning),
so TLS handshakes proceed concurrently with accepting new connections and
draining h3 handshakes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-transport-quiche/src/ez/server.rs (1)

201-207: ⚠️ Potential issue | 🟡 Minor

Stale doc comment: handshake is no longer already complete.

Line 201 says "with the TLS handshake already complete," but Incoming is now yielded before the handshake finishes — accept() is what awaits it. Consider updating to something like: "A pre-accepted QUIC connection. Call accept() to await TLS handshake completion."

Proposed fix
-/// A pre-accepted QUIC connection with the TLS handshake already complete.
+/// A pre-accepted QUIC connection.
 ///
-/// The peer address is available before calling [Incoming::accept].
+/// The peer address is available immediately.
+/// Call [Incoming::accept] to await TLS handshake completion.

@kixelated kixelated merged commit ea86bbb into main Feb 10, 2026
1 check passed
@kixelated kixelated deleted the async-accept branch February 10, 2026 20:15
@github-actions github-actions bot mentioned this pull request Feb 10, 2026
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