fix: premature socket teardown on CLOSE + duplicate CLOSE echo
build / build (push) Has been cancelled

Two bugs when upstream closes:

1. Data loss: gatuna received CLOSE from gatunad and immediately
   RST'd the socket to curl while the drain pump still had queued
   payloads in _deliverChannel. Fix: OnRemoteClose() completes the
   channel writer and cancels the read pump, but lets the drain pump
   finish naturally. Drain pump no longer takes _cts.Token — it stops
   on channel completion and closes the socket gracefully (FIN, OS
   flushes in background).

2. Duplicate CLOSE: gatuna echoed CLOSE back to gatunad (server
   already knows — it initiated). SendClose() now guarded by
   _closeSent flag. OnRemoteClose() never sends CLOSE at all.

Server-side: remove session from store before sending CLOSE so the
retransmit timer stops before CLOSE is queued. Only log CLOSE if
the session actually existed (dedup).
This commit is contained in:
2026-08-17 07:27:50 +00:00
parent b4222df349
commit fcd172f341
3 changed files with 30 additions and 9 deletions
+3 -2
View File
@@ -224,8 +224,9 @@ async fn handle_frame(
}
}
Frame::Close { session_id, reason: _ } => {
info!("CLOSE session {session_id} from {src}");
store.lock().expect("store poisoned").remove(&session_id);
if store.lock().expect("store poisoned").remove(&session_id).is_some() {
info!("CLOSE session {session_id} from {src}");
}
}
Frame::Ping { nonce } => {
let pong = Frame::Pong { nonce };
+2 -2
View File
@@ -275,12 +275,12 @@ pub fn spawn_pump(
}
if should_close {
store.lock().expect("store poisoned").remove(&session_id);
let close = Frame::Close {
session_id,
reason: Some(REASON_MAX_RETRIES),
};
let _ = tx.try_send((peer_mac, close.encode()));
store.lock().expect("store poisoned").remove(&session_id);
break;
}
}
@@ -330,8 +330,8 @@ pub fn spawn_pump(
break;
}
}
store.lock().expect("store poisoned").remove(&session_id);
let close = Frame::Close { session_id, reason: None };
let _ = tx.send((peer_mac, close.encode())).await;
store.lock().expect("store poisoned").remove(&session_id);
});
}