fix(websocket): configurable WS read idle timeout (WS-01)
This commit is contained in:
@@ -25,7 +25,7 @@ use axum::response::{IntoResponse, Response};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use super::byte_adapter::{
|
||||
split_ws_to_bytes, WsPumps, INBOUND_WS_FRAME_CAP, INBOUND_WS_MESSAGE_CAP,
|
||||
split_ws_to_bytes_idle, WsPumps, INBOUND_WS_FRAME_CAP, INBOUND_WS_MESSAGE_CAP,
|
||||
};
|
||||
|
||||
/// Registry of live WS session pump handles (WS-08). The upgrade
|
||||
@@ -66,6 +66,8 @@ pub struct SessionState {
|
||||
/// Session cap (WS-09): acquired post-auth, pre-upgrade; a caller
|
||||
/// over the cap is rejected with 503.
|
||||
session_slots: Arc<tokio::sync::Semaphore>,
|
||||
/// Idle-read timeout (WS-01): `None` disables the knob.
|
||||
idle_timeout: Option<std::time::Duration>,
|
||||
}
|
||||
|
||||
impl SessionState {
|
||||
@@ -78,6 +80,7 @@ impl SessionState {
|
||||
registry: Arc::clone(registry),
|
||||
sessions: Arc::new(WsSessions::new()),
|
||||
session_slots: Arc::new(tokio::sync::Semaphore::new(DEFAULT_WS_MAX_SESSIONS)),
|
||||
idle_timeout: Some(crate::websocket::DEFAULT_WS_IDLE_TIMEOUT),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +92,13 @@ impl SessionState {
|
||||
registry: Arc<OperationRegistry>,
|
||||
sessions: Arc<WsSessions>,
|
||||
session_slots: Arc<tokio::sync::Semaphore>,
|
||||
idle_timeout: Option<std::time::Duration>,
|
||||
) -> Self {
|
||||
Self {
|
||||
registry,
|
||||
sessions,
|
||||
session_slots,
|
||||
idle_timeout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +109,10 @@ impl SessionState {
|
||||
pub(crate) fn sessions(&self) -> &Arc<WsSessions> {
|
||||
&self.sessions
|
||||
}
|
||||
|
||||
pub(crate) fn idle_timeout(&self) -> Option<std::time::Duration> {
|
||||
self.idle_timeout
|
||||
}
|
||||
}
|
||||
|
||||
impl axum::extract::FromRef<SessionState> for Arc<OperationRegistry> {
|
||||
@@ -157,15 +166,18 @@ impl WsSessions {
|
||||
/// (identity attached) → `ChannelsAdapter::handle`. `policy` gates
|
||||
/// data-channel opens (ADR-041). When `sessions` is `Some`, the
|
||||
/// session's pump handle is registered for the session's lifetime —
|
||||
/// the WS-08 eviction lever ([`WsSessions::abort`]).
|
||||
/// the WS-08 eviction lever ([`WsSessions::abort`]). `idle_timeout`
|
||||
/// bounds the read stall (WS-01): `None` disables the knob, `Some(d)`
|
||||
/// closes the read with 1001 after `d` without an inbound WS message.
|
||||
pub async fn run_channels_session(
|
||||
socket: axum::extract::ws::WebSocket,
|
||||
registry: Arc<OperationRegistry>,
|
||||
identity: Identity,
|
||||
policy: Arc<dyn ChannelLifecyclePolicy>,
|
||||
sessions: Option<WsSessions>,
|
||||
idle_timeout: Option<std::time::Duration>,
|
||||
) {
|
||||
let (byte_stream, pumps) = split_ws_to_bytes(socket);
|
||||
let (byte_stream, pumps) = split_ws_to_bytes_idle(socket, idle_timeout);
|
||||
let pumps = Arc::new(pumps);
|
||||
|
||||
let _guard = sessions.map(|sessions| {
|
||||
@@ -316,12 +328,13 @@ pub async fn ws_upgrade_handler(
|
||||
.map(|axum::Extension(p)| p.0)
|
||||
.unwrap_or_else(|| Arc::new(NoCap));
|
||||
let registry = Arc::clone(state.registry());
|
||||
let idle_timeout = state.idle_timeout();
|
||||
ws_upgrade
|
||||
.max_frame_size(INBOUND_WS_FRAME_CAP)
|
||||
.max_message_size(INBOUND_WS_MESSAGE_CAP)
|
||||
.on_upgrade(move |socket| async move {
|
||||
let _permit = permit;
|
||||
run_channels_session(socket, registry, identity, policy, sessions).await
|
||||
run_channels_session(socket, registry, identity, policy, sessions, idle_timeout).await
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user