11use std:: {
22 collections:: { HashMap , HashSet , VecDeque } ,
33 num:: ParseIntError ,
4+ pin:: Pin ,
45 sync:: Arc ,
6+ task:: { Context , Poll } ,
57 time:: { Duration , Instant } ,
68} ;
79
810use futures:: { Stream , StreamExt } ;
11+ use pin_project_lite:: pin_project;
912use thiserror:: Error ;
1013use tokio:: sync:: {
1114 mpsc:: { Receiver , Sender } ,
@@ -17,9 +20,10 @@ use tracing::instrument;
1720use crate :: {
1821 RoleServer ,
1922 model:: {
20- CancelledNotificationParam , ClientJsonRpcMessage , ClientNotification , ClientRequest ,
21- JsonRpcNotification , JsonRpcRequest , Notification , ProgressNotificationParam ,
22- ProgressToken , RequestId , ServerJsonRpcMessage , ServerNotification ,
23+ CancelledNotification , CancelledNotificationParam , ClientJsonRpcMessage ,
24+ ClientNotification , ClientRequest , JsonRpcNotification , JsonRpcRequest , Notification ,
25+ ProgressNotificationParam , ProgressToken , RequestId , ServerJsonRpcMessage ,
26+ ServerNotification ,
2327 } ,
2428 transport:: {
2529 WorkerTransport ,
@@ -109,7 +113,11 @@ impl SessionManager for LocalSessionManager {
109113 let receiver = handle. establish_request_wise_channel ( ) . await ?;
110114 let http_request_id = receiver. http_request_id ;
111115 handle. push_message ( message, http_request_id) . await ?;
112- Ok ( ReceiverStream :: new ( receiver. inner ) )
116+ Ok ( RequestWiseResponseStream :: new (
117+ ReceiverStream :: new ( receiver. inner ) ,
118+ handle. clone ( ) ,
119+ http_request_id,
120+ ) )
113121 }
114122
115123 async fn create_standalone_stream (
@@ -426,6 +434,53 @@ pub struct StreamableHttpMessageReceiver {
426434 pub inner : Receiver < ServerSseMessage > ,
427435}
428436
437+ pin_project ! {
438+ /// Cancels the in-flight session request when the request-wise response
439+ /// stream is dropped before it reaches natural completion.
440+ struct RequestWiseResponseStream {
441+ #[ pin]
442+ inner: ReceiverStream <ServerSseMessage >,
443+ handle: LocalSessionHandle ,
444+ http_request_id: Option <HttpRequestId >,
445+ }
446+
447+ impl PinnedDrop for RequestWiseResponseStream {
448+ fn drop( this: Pin <& mut Self >) {
449+ let this = this. project( ) ;
450+ if let Some ( id) = this. http_request_id. take( ) {
451+ this. handle. cancel_request_wise_channel_on_disconnect( id) ;
452+ }
453+ }
454+ }
455+ }
456+
457+ impl RequestWiseResponseStream {
458+ fn new (
459+ inner : ReceiverStream < ServerSseMessage > ,
460+ handle : LocalSessionHandle ,
461+ http_request_id : Option < HttpRequestId > ,
462+ ) -> Self {
463+ Self {
464+ inner,
465+ handle,
466+ http_request_id,
467+ }
468+ }
469+ }
470+
471+ impl Stream for RequestWiseResponseStream {
472+ type Item = ServerSseMessage ;
473+
474+ fn poll_next ( self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Option < Self :: Item > > {
475+ let this = self . project ( ) ;
476+ let polled = this. inner . poll_next ( cx) ;
477+ if let Poll :: Ready ( None ) = & polled {
478+ * this. http_request_id = None ;
479+ }
480+ polled
481+ }
482+ }
483+
429484impl LocalSessionWorker {
430485 fn unregister_resource ( & mut self , resource : & ResourceKey ) {
431486 let Some ( http_request_id) = self . resource_router . remove ( resource) else {
@@ -486,6 +541,22 @@ impl LocalSessionWorker {
486541 self . unregister_resource ( & resource) ;
487542 }
488543 }
544+ fn remove_request_wise_channel ( & mut self , id : HttpRequestId ) -> Vec < RequestId > {
545+ let Some ( channel) = self . tx_router . remove ( & id) else {
546+ return Vec :: new ( ) ;
547+ } ;
548+ channel
549+ . resources
550+ . into_iter ( )
551+ . filter_map ( |resource| {
552+ self . resource_router . remove ( & resource) ;
553+ match resource {
554+ ResourceKey :: McpRequestId ( request_id) => Some ( request_id) ,
555+ ResourceKey :: ProgressToken ( _) => None ,
556+ }
557+ } )
558+ . collect ( )
559+ }
489560 fn evict_expired_channels ( & mut self ) {
490561 let ttl = self . session_config . completed_cache_ttl ;
491562 self . tx_router
@@ -813,6 +884,9 @@ pub enum SessionEvent {
813884 id : HttpRequestId ,
814885 responder : oneshot:: Sender < Result < ( ) , SessionError > > ,
815886 } ,
887+ CancelRequestWiseChannel {
888+ id : HttpRequestId ,
889+ } ,
816890 Resume {
817891 last_event_id : EventId ,
818892 responder : oneshot:: Sender < Result < StreamableHttpMessageReceiver , SessionError > > ,
@@ -914,6 +988,19 @@ impl LocalSessionHandle {
914988 . map_err ( |_| SessionError :: SessionServiceTerminated ) ?
915989 }
916990
991+ fn cancel_request_wise_channel_on_disconnect ( & self , request_id : HttpRequestId ) {
992+ let event = SessionEvent :: CancelRequestWiseChannel { id : request_id } ;
993+ match self . event_tx . try_send ( event) {
994+ Ok ( ( ) ) | Err ( tokio:: sync:: mpsc:: error:: TrySendError :: Closed ( _) ) => { }
995+ Err ( tokio:: sync:: mpsc:: error:: TrySendError :: Full ( event) ) => {
996+ let event_tx = self . event_tx . clone ( ) ;
997+ tokio:: spawn ( async move {
998+ let _ = event_tx. send ( event) . await ;
999+ } ) ;
1000+ }
1001+ }
1002+ }
1003+
9171004 /// Establish a common channel for general purpose messages.
9181005 pub async fn establish_common_channel (
9191006 & self ,
@@ -1186,9 +1273,24 @@ impl Worker for LocalSessionWorker {
11861273 id,
11871274 responder,
11881275 } ) => {
1189- let _handle_result = self . tx_router . remove ( & id) ;
1276+ self . remove_request_wise_channel ( id) ;
11901277 let _ = responder. send ( Ok ( ( ) ) ) ;
11911278 }
1279+ InnerEvent :: FromHttpService ( SessionEvent :: CancelRequestWiseChannel { id } ) => {
1280+ let request_ids = self . remove_request_wise_channel ( id) ;
1281+ for request_id in request_ids {
1282+ context
1283+ . send_to_handler ( ClientJsonRpcMessage :: notification (
1284+ ClientNotification :: CancelledNotification (
1285+ CancelledNotification :: new ( CancelledNotificationParam :: new (
1286+ Some ( request_id) ,
1287+ Some ( "client disconnected" . to_owned ( ) ) ,
1288+ ) ) ,
1289+ ) ,
1290+ ) )
1291+ . await ?;
1292+ }
1293+ }
11921294 InnerEvent :: FromHttpService ( SessionEvent :: Resume {
11931295 last_event_id,
11941296 responder,
0 commit comments