feat(websocket): WsTimeouts request extension for WS pump knobs (WS-17)
- WsTimeouts { idle, write } request extension mirrors ChannelsPolicy:
a deployment layers it on a WS route (bare-registry routes included)
to set the pump knobs per route
- precedence: extension present replaces the router state entirely;
absent falls back to SessionState (adapter-configured idle) and the
crate default write window — a Default impl never clobbers the
adapter-configured idle knob
- upgrade.rs module + handler docs now state the real defaults for
bare-registry routes (60 s idle + 60 s write, 64-session semaphore,
handler-private WsSessions) and the extension surface
- split_ws_to_bytes_idle_with_write exposes the WS-18 write window to
run_channels_session; acceptance test drives a bare-registry route
with a 150 ms extension idle window (1001 eviction observed)
Verification: scripts/verify.sh OK (343 passed), test-support suite
ok, clippy -D warnings clean, fmt clean
This commit is contained in:
@@ -918,3 +918,54 @@ async fn idle_progress_knob_none_disables_eviction_over_axum_upgrade() {
|
||||
);
|
||||
ws.close().await;
|
||||
}
|
||||
|
||||
/// WS-17 acceptance: a bare-registry upgrade route takes the pump
|
||||
/// knobs per request via the `WsTimeouts` extension (mirroring
|
||||
/// `ChannelsPolicy`) — a client that completes no chunk is evicted
|
||||
/// with 1001 inside the extension's short idle window, not the 60 s
|
||||
/// default.
|
||||
#[tokio::test]
|
||||
async fn ws_timeouts_extension_sets_the_idle_window_on_a_bare_registry_route() {
|
||||
let registry = std::sync::Arc::new(OperationRegistry::new());
|
||||
struct StaticTok;
|
||||
impl IdentityProvider for StaticTok {
|
||||
fn resolve_from_fingerprint(&self, _: &str) -> Option<Identity> {
|
||||
None
|
||||
}
|
||||
fn resolve_from_token(&self, token: &alkcall::core::auth::AuthToken) -> Option<Identity> {
|
||||
let s = String::from_utf8_lossy(&token.raw).to_string();
|
||||
(s == "tok-1").then(|| identity("alice", &[]))
|
||||
}
|
||||
}
|
||||
let app = axum::Router::new()
|
||||
.route(
|
||||
"/alk/channels",
|
||||
axum::routing::get(alkhttp::websocket::ws_upgrade_handler),
|
||||
)
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
std::sync::Arc::new(StaticTok) as std::sync::Arc<dyn IdentityProvider>,
|
||||
alkhttp::websocket::ws_bearer_auth,
|
||||
))
|
||||
.layer(axum::Extension(alkhttp::websocket::WsTimeouts {
|
||||
idle: Some(std::time::Duration::from_millis(150)),
|
||||
write: None,
|
||||
}))
|
||||
.with_state(registry);
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = format!("ws://{}", listener.local_addr().unwrap());
|
||||
tokio::spawn(async move {
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
});
|
||||
|
||||
let mut ws = WsClient::connect_authorized(&format!("{addr}/alk/channels"), "tok-1")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let close = ws.next_close(std::time::Duration::from_secs(5)).await;
|
||||
assert_eq!(
|
||||
close,
|
||||
Some(Some(alkhttp::websocket::WS_GOING_AWAY)),
|
||||
"evicted with 1001 inside the extension's idle window"
|
||||
);
|
||||
ws.close().await;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user