fix(websocket): configurable WS session cap (WS-09)

This commit is contained in:
2026-08-29 13:37:08 +00:00
parent 4747a12c02
commit fa73684ebe
6 changed files with 166 additions and 10 deletions
+11 -2
View File
@@ -26,8 +26,9 @@ pub enum DecoyConfig {
/// State embedded in the axum `Router`: the registry and identity
/// provider every request handler reaches through the router state, plus
/// the decoy config for the fallback, the pre-serialized
/// `/openapi.json` projection cache (SRV-09), and the shared WS session
/// registry the WS upgrade retains pump handles in (WS-08).
/// `/openapi.json` projection cache (SRV-09), and the WS session
/// lifecycle config the upgrade handler enforces: the shared pump-handle
/// registry (WS-08) and the concurrent-session cap (WS-09).
#[derive(Clone)]
pub(crate) struct RouterState {
pub(crate) registry: Arc<OperationRegistry>,
@@ -35,6 +36,10 @@ pub(crate) struct RouterState {
pub(crate) decoy: DecoyConfig,
pub(crate) openapi_doc: crate::server::adapter::CachedOpenAPIDoc,
pub(crate) ws_sessions: Arc<crate::websocket::WsSessions>,
/// Session cap (WS-09): one `HttpAdapter`-wide semaphore, built
/// once at construction — the upgrade handler acquires one permit
/// per upgrade and holds it for the session's lifetime.
pub(crate) ws_session_slots: Arc<tokio::sync::Semaphore>,
}
impl axum::extract::FromRef<RouterState> for crate::websocket::SessionState {
@@ -42,6 +47,7 @@ impl axum::extract::FromRef<RouterState> for crate::websocket::SessionState {
crate::websocket::SessionState::new(
Arc::clone(&state.registry),
Arc::clone(&state.ws_sessions),
Arc::clone(&state.ws_session_slots),
)
}
}
@@ -89,6 +95,9 @@ mod tests {
},
openapi_doc: crate::server::adapter::CachedOpenAPIDoc::new(&OperationRegistry::new()),
ws_sessions: Arc::new(crate::websocket::WsSessions::new()),
ws_session_slots: Arc::new(tokio::sync::Semaphore::new(
crate::websocket::DEFAULT_WS_MAX_SESSIONS,
)),
};
let extracted: DecoyConfig = axum::extract::FromRef::from_ref(&state);
assert!(matches!(extracted, DecoyConfig::Redirect { .. }));