Release handlers on close to break session reference cycles

Handlers commonly capture a clone of their own Session, and the Session
owns its handlers, so a closed session was never freed. On shutdown,
take the on_close handler instead of cloning it and clear the request
and notification handlers after it runs.

Bump to 0.2.1.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-25 05:45:07 +02:00
co-authored by claude
parent c9df3b5717
commit 05d5e53788
4 changed files with 48 additions and 3 deletions
+40
View File
@@ -450,3 +450,43 @@ async fn axum_adapter_serves_sessions_next_to_http_routes() {
tcp.read_to_string(&mut body).await.unwrap();
assert!(body.starts_with("HTTP/1.1 200") && body.ends_with("ok"), "{body}");
}
#[tokio::test]
async fn closing_releases_handlers_that_capture_the_session() {
let client = connect(start_server().await).await;
let captured = Arc::new(());
// Handlers typically hold a clone of their own session; that must not keep
// the session (and everything the handlers capture) alive after close.
client
.on_request::<Silent, _>({
let session = client.clone();
let captured = captured.clone();
move |_, ()| {
let _ = (&session, &captured);
async { Ok(()) }
}
})
.await;
client
.on_close({
let session = client.clone();
let captured = captured.clone();
move || {
let _ = (&session, &captured);
async { Ok(()) }
}
})
.await;
assert_eq!(Arc::strong_count(&captured), 3);
client.close().await.unwrap();
tokio::time::timeout(WAIT, async {
while Arc::strong_count(&captured) > 1 {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
.expect("handlers were not released after close");
}