From 69281ceed25aa23419e1ae738630bde6615d301b Mon Sep 17 00:00:00 2001 From: otsmr Date: Fri, 11 Sep 2026 23:53:56 +0200 Subject: [PATCH] fix server ack issue --- rust/src/api/runtime/client.rs | 4 ++-- rust/src/api/runtime/request.rs | 38 ++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/rust/src/api/runtime/client.rs b/rust/src/api/runtime/client.rs index 379daa29..6a939cf8 100644 --- a/rust/src/api/runtime/client.rs +++ b/rust/src/api/runtime/client.rs @@ -293,7 +293,7 @@ impl ApiClient { /// A replaced socket can emit its final disconnect/shutdown events after /// the new socket has started. Those events must not overwrite the new /// connection's state or schedule another reconnect. - async fn is_current_connection(&self, connection: &Arc) -> bool { + pub(super) async fn is_current_connection(&self, connection: &Arc) -> bool { self.ws_client .lock() .await @@ -421,7 +421,7 @@ impl ApiClient { } // Tungstenite message if let stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary(bytes) = &*msg { - self_clone.handle_incoming(bytes).await; + self_clone.handle_incoming(&connection, bytes).await; } } Err(_) => break, diff --git a/rust/src/api/runtime/request.rs b/rust/src/api/runtime/request.rs index 31ed93f2..ec1e4b84 100644 --- a/rust/src/api/runtime/request.rs +++ b/rust/src/api/runtime/request.rs @@ -54,7 +54,11 @@ fn is_dead_transport(error: &SendError) -> bool { } impl ApiClient { - pub(crate) async fn handle_incoming(self: &Arc, bytes: &[u8]) { + pub(crate) async fn handle_incoming( + self: &Arc, + connection: &Arc, + bytes: &[u8], + ) { let Ok(message) = server_to_client::ServerToClient::decode(bytes) else { return; }; @@ -68,6 +72,12 @@ impl ApiClient { } } else { let client = self.clone(); + // A response belongs to the WebSocket which delivered the + // request. Sequence IDs are scoped to a server session and + // restart when a replacement connection is authenticated, + // so forwarding this response through `client.send()` + // could satisfy an unrelated request on the new session. + let response_connection = connection.clone(); let Some(ctx) = self.context.upgrade() else { tracing::warn!("context was dropped before handling a server message"); return; @@ -85,10 +95,28 @@ impl ApiClient { }, )), }; - if let Err(error) = - client.send(acknowledgement.encode_to_vec()).await - { - tracing::warn!("failed to acknowledge server message: {error}"); + if !client.is_current_connection(&response_connection).await { + tracing::debug!( + sequence = v0.seq, + "discarding response for a replaced WebSocket session" + ); + return; + } + + let message = stream_tungstenite::tokio_tungstenite::tungstenite::Message::Binary( + acknowledgement.encode_to_vec().into(), + ); + if let Err(error) = response_connection.send_async(message).await { + if is_dead_transport(&error) { + client.schedule_reconnect( + &response_connection, + "server acknowledgement send failed", + ); + } + tracing::warn!( + sequence = v0.seq, + "failed to acknowledge server message: {error}" + ); } } Err(error) => {