From 4b95a3c99957b6cda3070c1a0011c08cff8d580c Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 16 Mar 2026 10:51:59 +0000 Subject: [PATCH] fix(remoteingress-core): ensure upstream writes cancel promptly and reliably deliver CLOSE_BACK frames --- changelog.md | 6 ++++++ rust/crates/remoteingress-core/src/hub.rs | 24 +++++++++++++++-------- ts/00_commitinfo_data.ts | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index db8f437..ee39666 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2026-03-16 - 4.5.8 - fix(remoteingress-core) +ensure upstream writes cancel promptly and reliably deliver CLOSE_BACK frames + +- listen for stream cancellation while waiting on upstream write timeouts so FRAME_CLOSE does not block for up to 60 seconds +- replace try_send with send().await when emitting CLOSE_BACK frames to avoid silently dropping close notifications when the data channel is full + ## 2026-03-16 - 4.5.7 - fix(remoteingress-core) improve tunnel reconnect and frame write efficiency diff --git a/rust/crates/remoteingress-core/src/hub.rs b/rust/crates/remoteingress-core/src/hub.rs index d2c6e2f..d07d1ae 100644 --- a/rust/crates/remoteingress-core/src/hub.rs +++ b/rust/crates/remoteingress-core/src/hub.rs @@ -541,10 +541,16 @@ async fn handle_edge_connection( match data { Some(data) => { let len = data.len() as u32; - match tokio::time::timeout( - Duration::from_secs(60), - up_write.write_all(&data), - ).await { + // Check cancellation alongside the write so we respond + // promptly to FRAME_CLOSE instead of blocking up to 60s. + let write_result = tokio::select! { + r = tokio::time::timeout( + Duration::from_secs(60), + up_write.write_all(&data), + ) => r, + _ = writer_token.cancelled() => break, + }; + match write_result { Ok(Ok(())) => {} Ok(Err(_)) => break, Err(_) => { @@ -619,10 +625,11 @@ async fn handle_edge_connection( } } - // Send CLOSE_BACK via DATA channel (must arrive AFTER last DATA_BACK) + // Send CLOSE_BACK via DATA channel (must arrive AFTER last DATA_BACK). + // Use send().await to guarantee delivery (try_send silently drops if full). if !stream_token.is_cancelled() { let close_frame = encode_frame(stream_id, FRAME_CLOSE_BACK, &[]); - let _ = data_writer_tx.try_send(close_frame); + let _ = data_writer_tx.send(close_frame).await; } writer_for_edge_data.abort(); @@ -632,10 +639,11 @@ async fn handle_edge_connection( if let Err(e) = result { log::error!("Stream {} error: {}", stream_id, e); - // Send CLOSE_BACK via DATA channel on error (must arrive after any DATA_BACK) + // Send CLOSE_BACK via DATA channel on error (must arrive after any DATA_BACK). + // Use send().await to guarantee delivery. if !stream_token.is_cancelled() { let close_frame = encode_frame(stream_id, FRAME_CLOSE_BACK, &[]); - let _ = data_writer_tx.try_send(close_frame); + let _ = data_writer_tx.send(close_frame).await; } } diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index d54c086..e5bd2ac 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/remoteingress', - version: '4.5.7', + version: '4.5.8', description: 'Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.' }