docs(websocket): detached task lifetime semantics (WS-10)
This commit is contained in:
@@ -9,6 +9,32 @@
|
||||
//! path). The `install_channel_zero` hook constructs channel 0's
|
||||
//! `CallConnection` (the identity rides the channels-layer
|
||||
//! `Connection`) and runs the shared `Dispatcher::run_loop_single_stream`.
|
||||
//!
|
||||
//! ## Detached task lifetime semantics (WS-10)
|
||||
//!
|
||||
//! Two task families spawned here are **detached by design** and
|
||||
//! outlive the session task that spawned them:
|
||||
//!
|
||||
//! - the WS pump tasks (the `WsPumps` pair behind the byte adapter,
|
||||
//! re-exported from `crate::websocket`), and
|
||||
//! - the channel-0 dispatcher task spawned by `install_channel_zero`
|
||||
//! per accepted channels connection.
|
||||
//!
|
||||
//! If the channels session task (`run_channels_session`) dies —
|
||||
//! upgrade-time early return, an adapter fault, or its own task being
|
||||
//! cancelled — these tasks keep running **self-healing**: each ends on
|
||||
//! its own when its stream half closes (peer disconnect, peer close
|
||||
//! frame, read error, or the local teardown arms —
|
||||
//! `WsSessions::abort`, the idle-read timeout, `AsyncWrite::shutdown`).
|
||||
//! They are never leaked unconditionally: the leak window is bounded
|
||||
//! by peer behavior (a peer that holds the socket open keeps the pump
|
||||
//! and dispatcher tasks alive with it) and additionally by the WS-01
|
||||
//! idle-read timeout when configured, and every session's pumps stay
|
||||
//! force-evictable through the WS-08 registry
|
||||
//! ([`WsSessions::abort`]) for the session's whole lifetime. The
|
||||
//! dispatcher task specifically ends when channel 0's `BiStream` read
|
||||
//! side hits EOF (its only exit condition), i.e. when the underlying
|
||||
//! WS connection ends by any of the paths above.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
@@ -35,9 +61,9 @@ use super::byte_adapter::{
|
||||
/// halves close and the channels session unwinds). The handle for a
|
||||
/// session is removed when the session task finishes (self-removing
|
||||
/// guard), so the registry only holds live sessions. Assembly layers
|
||||
/// share one instance via [`crate::server::state::RouterState`] (the
|
||||
/// upgrade handler registers against it) or per-route request
|
||||
/// extensions (an extension clone takes precedence).
|
||||
/// share one instance via the adapter's `RouterState` (the upgrade
|
||||
/// handler registers against it) or per-route request extensions (an
|
||||
/// extension clone takes precedence).
|
||||
///
|
||||
/// Un-registered (default) — the upgrade runs fine and simply keeps no
|
||||
/// eviction lever, matching the pre-WS-08 behavior.
|
||||
@@ -54,11 +80,11 @@ pub const DEFAULT_WS_MAX_SESSIONS: usize = 64;
|
||||
|
||||
/// The upgrade handler's state slice: what it needs beyond the request
|
||||
/// itself. Axum lifts it via `FromRef` from either full router state —
|
||||
/// the adapter's [`crate::server::state::RouterState`] (carrying the
|
||||
/// shared [`WsSessions`] instance and the configured session cap) or a
|
||||
/// bare `Arc<OperationRegistry>` (custom upgrade routes / integration
|
||||
/// tests get a handler-private registry and the default cap; eviction
|
||||
/// still works in-crate, just not shared).
|
||||
/// the adapter's `RouterState` (carrying the shared [`WsSessions`]
|
||||
/// instance and the configured session cap) or a bare
|
||||
/// `Arc<OperationRegistry>` (custom upgrade routes / integration tests
|
||||
/// get a handler-private registry and the default cap; eviction still
|
||||
/// works in-crate, just not shared).
|
||||
#[derive(Clone)]
|
||||
pub struct SessionState {
|
||||
registry: Arc<OperationRegistry>,
|
||||
@@ -122,7 +148,7 @@ impl axum::extract::FromRef<SessionState> for Arc<OperationRegistry> {
|
||||
}
|
||||
|
||||
/// `FromRef` chain: a bare `Arc<OperationRegistry>` router state lifts
|
||||
/// into the handler's [`SessionState`]; a full [`RouterState`] carries
|
||||
/// into the handler's [`SessionState`]; a full `RouterState` carries
|
||||
/// the shared [`WsSessions`] instance and lifts through its own impl.
|
||||
impl axum::extract::FromRef<Arc<OperationRegistry>> for SessionState {
|
||||
fn from_ref(registry: &Arc<OperationRegistry>) -> Self {
|
||||
|
||||
Reference in New Issue
Block a user