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: a late subscriber (monitor spawned after session setup, or pump EOF before the receiver is taken) still observes EOF (WS-02). - from_wss drop monitor: on EOF (or session close) fail all pendings retryable, then keep sweeping the pending map every 1 s — calls registered after the initial fail_all (the forgotten-session import path) resolve instead of hanging (CON-02). - Tests: drop-during-registration race variants (forget + held session) and a post-EOF registration resolved via the sweep; the existing no-hang test stays green. cargo test (219), cargo test --features wss (231, 3x for flake check), cargo clippy --all-targets -- -D warnings, cargo fmt --check
This commit is contained in:
@@ -95,33 +95,13 @@ impl WsPumps {
|
||||
/// EOF signaled at any point — including before the receiver is
|
||||
/// taken or the observer starts awaiting — is still observed, and
|
||||
/// may be observed repeatedly. Used by `from_wss`'s
|
||||
/// connection-drop monitor (ADR-070); await it with
|
||||
/// [`wait_for_eof`].
|
||||
/// connection-drop monitor (ADR-070).
|
||||
#[cfg(feature = "wss")]
|
||||
pub(crate) fn read_eof(&self) -> tokio::sync::watch::Receiver<bool> {
|
||||
self.read_eof.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves once the given WS read-EOF watch receiver observes `true`.
|
||||
///
|
||||
/// Lossless by construction: a `watch::Receiver` retains the latest
|
||||
/// value, so an EOF signaled before this call (or before the receiver
|
||||
/// existed) is still observed — the property a one-shot `Notify` lacked
|
||||
/// (WS-02). Returns early (treated as EOF) if the sender half is
|
||||
/// dropped, e.g. the read pump was aborted.
|
||||
#[cfg(feature = "wss")]
|
||||
pub(crate) async fn wait_for_eof(rx: &mut tokio::sync::watch::Receiver<bool>) {
|
||||
loop {
|
||||
if *rx.borrow_and_update() {
|
||||
return;
|
||||
}
|
||||
if rx.changed().await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Split a `WebSocket` into the byte stream + the pump tasks. The
|
||||
/// adapter is the single seam between axum's WS and alkcall's
|
||||
/// byte-oriented channels machinery; shared with `from_wss`.
|
||||
|
||||
@@ -18,8 +18,9 @@ pub use byte_adapter::{
|
||||
|
||||
#[cfg(any(test, feature = "wss"))]
|
||||
pub use byte_adapter::split_tungstenite_to_bytes;
|
||||
#[cfg(feature = "wss")]
|
||||
pub(crate) use byte_adapter::wait_for_eof;
|
||||
#[cfg(any(test, feature = "wss"))]
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use upgrade::adapter_install_channel_zero;
|
||||
pub use upgrade::{run_channels_session, ws_bearer_auth, ws_upgrade_handler};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -97,6 +97,13 @@ fn install_channel_zero(
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn adapter_install_channel_zero(
|
||||
registry: Arc<OperationRegistry>,
|
||||
) -> alkcall::channels::adapter::InstallChannelZero {
|
||||
install_channel_zero(registry)
|
||||
}
|
||||
|
||||
struct NoopProvider;
|
||||
|
||||
impl alkcall::core::auth::IdentityProvider for NoopProvider {
|
||||
|
||||
Reference in New Issue
Block a user