fix(websocket): lossless EOF signal + pending sweep (WS-02, CON-02)
Replace the axum/tungstenite pump paths' Notify-based read-EOF signal with a retained tokio watch channel so a late subscriber observes EOF regardless of when it fired. Extend the from_wss drop monitor to sweep the pending map (1 s interval) once EOF is observed, so calls registered after the initial fail_all also resolve retryable instead of hanging. cargo test; cargo clippy --all-targets -- -D warnings (default + all-features); cargo fmt --check
This commit is contained in:
+46
-13
@@ -23,10 +23,18 @@
|
||||
//! Reconnect policy (OQ-03 disposition): v1 = none. A connection drop
|
||||
//! fails in-flight calls retryable: alkcall's client-side read pump only
|
||||
//! routes envelopes and does not observe EOF, so the adapter owns drop
|
||||
//! semantics — the [`WssSession`] monitor awaits the WS read pump and
|
||||
//! fails all pending calls with retryable `CONNECTION_CLOSED`. Subsequent
|
||||
//! handler calls fail on write; reconnect policy is the assembly layer's
|
||||
//! job.
|
||||
//! semantics — the [`WssSession`] monitor awaits the WS read pump's EOF
|
||||
//! signal and fails all pending calls with retryable `CONNECTION_CLOSED`.
|
||||
//! The EOF signal is lossless (a retained watch value, WS-02): EOF is
|
||||
//! observed even if it fires before the monitor starts, or after the
|
||||
//! session was forgotten (fire-and-forget import). The monitor also
|
||||
//! sweeps the pending map once a second while the session lives: calls
|
||||
//! registered *after* the initial `fail_all` (still possible on the
|
||||
//! forgotten-session path, where imports may race the drop) are failed
|
||||
//! on the next sweep tick once EOF has been observed. No hang: pendings
|
||||
//! resolve retryable regardless of registration-vs-EOF ordering
|
||||
//! (CON-02). Subsequent handler calls fail on write; reconnect policy is
|
||||
//! the assembly layer's job.
|
||||
//!
|
||||
//! [ADR-070]: https://docs.rs/alkhttp (docs/architecture/decisions)
|
||||
|
||||
@@ -43,6 +51,11 @@ use tokio_tungstenite::tungstenite::client::IntoClientRequest;
|
||||
|
||||
use crate::websocket::split_tungstenite_to_bytes;
|
||||
|
||||
/// How often the drop monitor sweeps the pending map for calls
|
||||
/// registered after the last `fail_all` (only effective once EOF has
|
||||
/// been observed — see the module doc).
|
||||
const PENDING_SWEEP_INTERVAL: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
|
||||
pub struct FromWss {
|
||||
endpoint: String,
|
||||
auth_token: Option<String>,
|
||||
@@ -151,18 +164,38 @@ impl WssSession {
|
||||
})?;
|
||||
let call_connection = Arc::new(call_connection);
|
||||
|
||||
let (close_tx, close_rx) = tokio::sync::oneshot::channel();
|
||||
let (close_tx, mut close_rx) = tokio::sync::oneshot::channel();
|
||||
let pending = Arc::clone(call_connection.pending());
|
||||
let mut eof_rx = pumps.read_eof();
|
||||
tokio::spawn(async move {
|
||||
tokio::select! {
|
||||
_ = pumps.read_eof() => {},
|
||||
_ = close_rx => {},
|
||||
let mut sweep = tokio::time::interval(PENDING_SWEEP_INTERVAL);
|
||||
sweep.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = crate::websocket::wait_for_eof(&mut eof_rx) => break,
|
||||
_ = &mut close_rx => break,
|
||||
_ = sweep.tick() => {
|
||||
if *eof_rx.borrow() {
|
||||
pending.lock().fail_all(
|
||||
CallError::new(
|
||||
"CONNECTION_CLOSED",
|
||||
"from_wss connection dropped",
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let failure = CallError::new("CONNECTION_CLOSED", "from_wss connection dropped", true);
|
||||
let mut guard = pending.lock();
|
||||
guard.fail_all(failure.clone());
|
||||
if *eof_rx.borrow() {
|
||||
let swept = guard.fail_all(failure);
|
||||
if !swept.is_empty() {
|
||||
tracing::debug!("from_wss: sweep failed {} late pendings", swept.len());
|
||||
}
|
||||
}
|
||||
pending.lock().fail_all(CallError::new(
|
||||
"CONNECTION_CLOSED",
|
||||
"from_wss connection dropped",
|
||||
true,
|
||||
));
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
|
||||
Reference in New Issue
Block a user