Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ vllm-proto = { version = "=0.1.0" }
kube = { version = "2.0.1", default-features = false, features = ["client", "rustls-tls", "aws-lc-rs", "runtime"] }
k8s-openapi = { version = "0.26.0", features = ["v1_32"] }
rcgen = { version = "0.13" }
quinn = { version = "0.11.9", default-features = false, features = ["runtime-tokio", "rustls-ring"] }
quinn = { version = "0.11.12", default-features = false, features = ["runtime-tokio", "rustls-ring"] }
Comment thread
jthomson04 marked this conversation as resolved.
Resolved
sha2 = { version = "0.10" }
tokio-rustls = { version = "0.26" }
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
Expand Down
8 changes: 4 additions & 4 deletions lib/bindings/kvbm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions lib/bindings/python/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions lib/runtime/examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 47 additions & 16 deletions lib/runtime/src/pipeline/network/quic_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ const FRAME_HEADER_LEN: usize = 1 + 16 + 4;
// at the receiver so parsing those frames does not poll Quinn once per header
// and payload and exhaust Tokio's cooperative task budget.
const RECEIVE_BUFFER_CAPACITY: usize = 256 * 1024;
const READ_BUDGET_FRAMES: usize = 16;
const READ_BUDGET_BYTES: usize = 4 * 1024;
// A single frontend UDP socket is limited by the host receive-buffer ceiling.
// Reuse-port endpoints preserve one advertised address while spreading QUIC
// connections and receive queues across several sockets.
#[cfg(target_os = "linux")]
const SERVER_ENDPOINTS: usize = 8;
const SERVER_ENDPOINTS: usize = 32;
#[cfg(not(target_os = "linux"))]
const SERVER_ENDPOINTS: usize = 1;
const MAX_FRAME_PAYLOAD: usize = 32 * 1024 * 1024;
Expand Down Expand Up @@ -854,7 +856,7 @@ fn bind_server_udp(address: SocketAddr, join_reuseport: bool) -> std::io::Result
socket.bind(&address.into())?;
// Bind the first endpoint exclusively so an ephemeral port cannot join an
// unrelated server's reuse-port group. Linux permits enabling reuse-port
// after that first bind, and the remaining seven endpoints can then join.
// after that first bind, and the remaining endpoints can then join.
#[cfg(target_os = "linux")]
if !join_reuseport {
socket.set_reuse_address(true)?;
Expand Down Expand Up @@ -932,8 +934,9 @@ async fn run_server_connection(
)
.await;
if let Err(error) = result {
tracing::warn!(connection_id, %error, "QUIC response lane failed; closing connection");
tracing::warn!(connection_id, error = format!("{error:#}"), close_reason = ?lane_connection.close_reason(), "QUIC response lane failed; closing connection");
fail_server_connection(&lane_state, connection_id);
// Close explicitly if the lane failed before bundle registration.
lane_connection
.close(CLOSE_CODE_INVARIANT, b"response lane invariant failure");
}
Expand Down Expand Up @@ -1041,17 +1044,30 @@ async fn run_server_lane(
register_server_connection_bundle(&state, connection, bundle_id)?;

let (control_tx, mut control_rx) = mpsc::channel::<Frame>(RESPONSE_BUFFER_CAPACITY);
let mut writer = tokio::spawn(async move {
let writer = async move {
while let Some(frame) = control_rx.recv().await {
let mut chunks = [frame.header(), frame.payload];
send.write_all_chunks(&mut chunks).await?;
}
Ok::<(), quinn::WriteError>(())
});
};

let reader = async {
let mut frames = READ_BUDGET_FRAMES;
let mut bytes = READ_BUDGET_BYTES;
loop {
// Charge a bounded batch of small frames to the task budget.
// Charging every token frame can leave already-received responses
// queued behind repeated scheduler waits. Keep both bounds so
// large frames still yield and the reverse-control writer runs.
if frames >= READ_BUDGET_FRAMES || bytes >= READ_BUDGET_BYTES {
tokio::task::consume_budget().await;
frames = 0;
bytes = 0;
}
let frame = read_frame(&mut recv).await?;
frames += 1;
bytes += FRAME_HEADER_LEN + frame.payload.len();
process_server_frame(
frame,
bundle_id,
Expand All @@ -1064,16 +1080,13 @@ async fn run_server_lane(
#[allow(unreachable_code)]
Ok::<(), anyhow::Error>(())
};
tokio::pin!(reader);
// Keep both futures pinned across polls. This avoids a separate task wake
// for registration acknowledgements without cancelling partial reads.
tokio::pin!(reader, writer);
tokio::select! {
result = &mut reader => {
writer.abort();
let _ = writer.await;
result
}
result = &mut reader => result,
result = &mut writer => match result {
Ok(Ok(())) => bail!("QUIC reverse-control writer exited unexpectedly"),
Ok(Err(error)) => Err(error.into()),
Ok(()) => bail!("QUIC reverse-control writer exited unexpectedly"),
Err(error) => Err(error.into()),
},
}
Expand Down Expand Up @@ -1848,14 +1861,23 @@ fn spawn_client_lane(
&writer_connections,
&writer_contexts,
&writer_healthy,
&error.to_string(),
bundle_id,
"writer",
&format!("{error:#}"),
);
}
});

tokio::spawn(async move {
if let Err(error) = run_client_control_reader(recv, contexts.clone()).await {
fail_client_connection_bundle(&connections, &contexts, &healthy, &error.to_string());
fail_client_connection_bundle(
&connections,
&contexts,
&healthy,
bundle_id,
"control_reader",
&format!("{error:#}"),
);
}
});
}
Expand Down Expand Up @@ -1946,13 +1968,22 @@ fn fail_client_connection_bundle(
connections: &[quinn::Connection],
contexts: &Mutex<HashMap<Uuid, Arc<ClientResponseContext>>>,
healthy: &AtomicBool,
bundle_id: Uuid,
failure_path: &'static str,
reason: &str,
) {
if !healthy.swap(false, Ordering::AcqRel) {
return;
}
crate::metrics::quic_response::record_bundle_failure("worker");
tracing::warn!(%reason, "QUIC response connection bundle invariant failed");
let remote = connections.first().map(quinn::Connection::remote_address);
tracing::warn!(
%bundle_id,
failure_path,
?remote,
%reason,
Comment thread
jthomson04 marked this conversation as resolved.
Resolved
"QUIC response connection bundle invariant failed"
);
for (_, entry) in contexts.lock().drain() {
entry.fail_registration(reason);
entry.record_cancellation();
Expand Down
Loading