@@ -42,8 +42,12 @@ pub(crate) type MaybeBoxFuture<'a, T> = BoxFuture<'a, T>;
4242#[ cfg( feature = "local" ) ]
4343pub ( crate ) type MaybeBoxFuture < ' a , T > = LocalBoxFuture < ' a , T > ;
4444
45+ #[ cfg( feature = "server" ) ]
46+ use crate :: model:: ClientNotification ;
4547#[ cfg( feature = "server" ) ]
4648use crate :: model:: ServerJsonRpcMessage ;
49+ #[ cfg( feature = "client" ) ]
50+ use crate :: model:: ServerNotification ;
4751use crate :: {
4852 error:: ErrorData as McpError ,
4953 model:: {
@@ -299,7 +303,37 @@ impl ProgressTokenProvider for AtomicU32Provider {
299303 }
300304}
301305
306+ #[ doc( hidden) ]
307+ pub trait ProgressNotificationToken {
308+ fn progress_token ( & self ) -> Option < & ProgressToken > ;
309+ }
310+
311+ #[ cfg( feature = "server" ) ]
312+ impl ProgressNotificationToken for ClientNotification {
313+ fn progress_token ( & self ) -> Option < & ProgressToken > {
314+ match self {
315+ ClientNotification :: ProgressNotification ( notification) => {
316+ Some ( & notification. params . progress_token )
317+ }
318+ _ => None ,
319+ }
320+ }
321+ }
322+
323+ #[ cfg( feature = "client" ) ]
324+ impl ProgressNotificationToken for ServerNotification {
325+ fn progress_token ( & self ) -> Option < & ProgressToken > {
326+ match self {
327+ ServerNotification :: ProgressNotification ( notification) => {
328+ Some ( & notification. params . progress_token )
329+ }
330+ _ => None ,
331+ }
332+ }
333+ }
334+
302335type Responder < T > = tokio:: sync:: oneshot:: Sender < T > ;
336+ type ProgressTimeoutWatchers = Arc < tokio:: sync:: RwLock < HashMap < ProgressToken , mpsc:: Sender < ( ) > > > > ;
303337
304338/// A handle to a remote request
305339///
@@ -314,40 +348,126 @@ pub struct RequestHandle<R: ServiceRole> {
314348 pub peer : Peer < R > ,
315349 pub id : RequestId ,
316350 pub progress_token : ProgressToken ,
351+ progress_timeout_watchers : ProgressTimeoutWatchers ,
352+ progress_reset_rx : Option < mpsc:: Receiver < ( ) > > ,
317353}
318354
319355impl < R : ServiceRole > RequestHandle < R > {
320356 pub const REQUEST_TIMEOUT_REASON : & str = "request timeout" ;
321- pub async fn await_response ( self ) -> Result < R :: PeerResp , ServiceError > {
322- if let Some ( timeout) = self . options . timeout {
323- let timeout_result = tokio:: time:: timeout ( timeout, async move {
324- self . rx . await . map_err ( |_e| ServiceError :: TransportClosed ) ?
325- } )
326- . await ;
327- match timeout_result {
328- Ok ( response) => response,
357+ pub const REQUEST_MAX_TOTAL_TIMEOUT_REASON : & str = "maximum total timeout exceeded" ;
358+
359+ pub async fn await_response ( mut self ) -> Result < R :: PeerResp , ServiceError > {
360+ let timeout = self . options . timeout ;
361+ let max_total_timeout = self . options . max_total_timeout ;
362+ let reset_timeout_on_progress = self . options . reset_timeout_on_progress ;
363+
364+ let has_progress_reset_rx = self . progress_reset_rx . is_some ( ) ;
365+ let progress_token = self . progress_token . clone ( ) ;
366+
367+ let result = match ( timeout, max_total_timeout, reset_timeout_on_progress) {
368+ ( Some ( timeout) , None , false ) => match tokio:: time:: timeout ( timeout, & mut self . rx ) . await
369+ {
370+ Ok ( response) => response. map_err ( |_e| ServiceError :: TransportClosed ) ?,
329371 Err ( _) => {
330372 let error = Err ( ServiceError :: Timeout { timeout } ) ;
331373 // cancel this request
332- let notification = CancelledNotification {
333- params : CancelledNotificationParam {
334- request_id : self . id ,
335- reason : Some ( Self :: REQUEST_TIMEOUT_REASON . to_owned ( ) ) ,
336- } ,
337- method : crate :: model:: CancelledNotificationMethod ,
338- extensions : Default :: default ( ) ,
339- } ;
340- let _ = self . peer . send_notification ( notification. into ( ) ) . await ;
374+ self . send_timeout_cancel_notification ( Self :: REQUEST_TIMEOUT_REASON )
375+ . await ;
341376 error
342377 }
378+ } ,
379+ ( None , None , _) => ( & mut self . rx )
380+ . await
381+ . map_err ( |_e| ServiceError :: TransportClosed ) ?,
382+ _ => {
383+ self . await_response_with_progress_timeout (
384+ timeout,
385+ max_total_timeout,
386+ reset_timeout_on_progress,
387+ )
388+ . await
389+ }
390+ } ;
391+
392+ Self :: cleanup_progress_timeout_watcher (
393+ & self . peer . progress_timeout_watchers ,
394+ & progress_token,
395+ has_progress_reset_rx,
396+ )
397+ . await ;
398+ result
399+ }
400+
401+ async fn send_timeout_cancel_notification ( & self , reason : & str ) {
402+ let notification = CancelledNotification {
403+ params : CancelledNotificationParam {
404+ request_id : self . id . clone ( ) ,
405+ reason : Some ( reason. to_owned ( ) ) ,
406+ } ,
407+ method : crate :: model:: CancelledNotificationMethod ,
408+ extensions : Default :: default ( ) ,
409+ } ;
410+ let _ = self . peer . send_notification ( notification. into ( ) ) . await ;
411+ }
412+
413+ async fn await_response_with_progress_timeout (
414+ & mut self ,
415+ timeout : Option < Duration > ,
416+ max_total_timeout : Option < Duration > ,
417+ reset_timeout_on_progress : bool ,
418+ ) -> Result < R :: PeerResp , ServiceError > {
419+ let mut idle_sleep = timeout. map ( tokio:: time:: sleep) . map ( Box :: pin) ;
420+ let mut max_total_sleep = max_total_timeout. map ( tokio:: time:: sleep) . map ( Box :: pin) ;
421+
422+ loop {
423+ tokio:: select! {
424+ biased;
425+
426+ response = & mut self . rx => {
427+ return response. map_err( |_e| ServiceError :: TransportClosed ) ?;
428+ }
429+ _ = async {
430+ if let Some ( sleep) = idle_sleep. as_mut( ) {
431+ sleep. as_mut( ) . await ;
432+ }
433+ } , if idle_sleep. is_some( ) => {
434+ let timeout = timeout. expect( "idle timeout exists when idle sleep exists" ) ;
435+ self . send_timeout_cancel_notification( Self :: REQUEST_TIMEOUT_REASON ) . await ;
436+ return Err ( ServiceError :: Timeout { timeout } ) ;
437+ }
438+ _ = async {
439+ if let Some ( sleep) = max_total_sleep. as_mut( ) {
440+ sleep. as_mut( ) . await ;
441+ }
442+ } , if max_total_sleep. is_some( ) => {
443+ let timeout = max_total_timeout. expect( "max total timeout exists when max total sleep exists" ) ;
444+ self . send_timeout_cancel_notification( Self :: REQUEST_MAX_TOTAL_TIMEOUT_REASON ) . await ;
445+ return Err ( ServiceError :: Timeout { timeout } ) ;
446+ }
447+ progress = async {
448+ match self . progress_reset_rx. as_mut( ) {
449+ Some ( rx) => rx. recv( ) . await ,
450+ None => None ,
451+ }
452+ } , if reset_timeout_on_progress && timeout. is_some( ) && self . progress_reset_rx. is_some( ) => {
453+ if progress. is_some( ) {
454+ if let ( Some ( timeout) , Some ( sleep) ) = ( timeout, idle_sleep. as_mut( ) ) {
455+ sleep. as_mut( ) . reset( tokio:: time:: Instant :: now( ) + timeout) ;
456+ }
457+ }
458+ }
343459 }
344- } else {
345- self . rx . await . map_err ( |_e| ServiceError :: TransportClosed ) ?
346460 }
347461 }
348462
349463 /// Cancel this request
350464 pub async fn cancel ( self , reason : Option < String > ) -> Result < ( ) , ServiceError > {
465+ Self :: cleanup_progress_timeout_watcher (
466+ & self . progress_timeout_watchers ,
467+ & self . progress_token ,
468+ self . progress_reset_rx . is_some ( ) ,
469+ )
470+ . await ;
351471 let notification = CancelledNotification {
352472 params : CancelledNotificationParam {
353473 request_id : self . id ,
@@ -359,6 +479,19 @@ impl<R: ServiceRole> RequestHandle<R> {
359479 self . peer . send_notification ( notification. into ( ) ) . await ?;
360480 Ok ( ( ) )
361481 }
482+
483+ async fn cleanup_progress_timeout_watcher (
484+ progress_timeout_watchers : & ProgressTimeoutWatchers ,
485+ progress_token : & ProgressToken ,
486+ has_progress_reset_rx : bool ,
487+ ) {
488+ if has_progress_reset_rx {
489+ progress_timeout_watchers
490+ . write ( )
491+ . await
492+ . remove ( progress_token) ;
493+ }
494+ }
362495}
363496
364497#[ derive( Debug ) ]
@@ -384,6 +517,7 @@ pub struct Peer<R: ServiceRole> {
384517 tx : mpsc:: Sender < PeerSinkMessage < R > > ,
385518 request_id_provider : Arc < dyn RequestIdProvider > ,
386519 progress_token_provider : Arc < dyn ProgressTokenProvider > ,
520+ progress_timeout_watchers : ProgressTimeoutWatchers ,
387521 info : Arc < std:: sync:: RwLock < Option < Arc < R :: PeerInfo > > > > ,
388522}
389523
@@ -403,12 +537,33 @@ type ProxyOutbound<R> = mpsc::Receiver<PeerSinkMessage<R>>;
403537pub struct PeerRequestOptions {
404538 pub timeout : Option < Duration > ,
405539 pub meta : Option < Meta > ,
540+ /// Reset the request timeout when a matching progress notification is received.
541+ pub reset_timeout_on_progress : bool ,
542+ /// Maximum total time to wait for the request, regardless of progress notifications.
543+ pub max_total_timeout : Option < Duration > ,
406544}
407545
408546impl PeerRequestOptions {
409547 pub fn no_options ( ) -> Self {
410548 Self :: default ( )
411549 }
550+
551+ pub fn with_timeout ( timeout : Duration ) -> Self {
552+ Self {
553+ timeout : Some ( timeout) ,
554+ ..Self :: default ( )
555+ }
556+ }
557+
558+ pub fn reset_timeout_on_progress ( mut self ) -> Self {
559+ self . reset_timeout_on_progress = true ;
560+ self
561+ }
562+
563+ pub fn with_max_total_timeout ( mut self , timeout : Duration ) -> Self {
564+ self . max_total_timeout = Some ( timeout) ;
565+ self
566+ }
412567}
413568
414569impl < R : ServiceRole > Peer < R > {
@@ -423,6 +578,7 @@ impl<R: ServiceRole> Peer<R> {
423578 tx,
424579 request_id_provider,
425580 progress_token_provider : Arc :: new ( AtomicU32ProgressTokenProvider :: default ( ) ) ,
581+ progress_timeout_watchers : Default :: default ( ) ,
426582 info : Arc :: new ( std:: sync:: RwLock :: new ( peer_info. map ( Arc :: new) ) ) ,
427583 } ,
428584 rx,
@@ -468,22 +624,68 @@ impl<R: ServiceRole> Peer<R> {
468624 request. get_meta_mut ( ) . extend ( meta) ;
469625 }
470626 let ( responder, receiver) = tokio:: sync:: oneshot:: channel ( ) ;
471- self . tx
627+ let progress_reset_rx = if options. reset_timeout_on_progress && options. timeout . is_some ( ) {
628+ let ( sender, receiver) = mpsc:: channel ( 1 ) ;
629+ self . progress_timeout_watchers
630+ . write ( )
631+ . await
632+ . insert ( progress_token. clone ( ) , sender) ;
633+ Some ( receiver)
634+ } else {
635+ None
636+ } ;
637+ if self
638+ . tx
472639 . send ( PeerSinkMessage :: Request {
473640 request,
474641 id : id. clone ( ) ,
475642 responder,
476643 } )
477644 . await
478- . map_err ( |_m| ServiceError :: TransportClosed ) ?;
645+ . is_err ( )
646+ {
647+ if progress_reset_rx. is_some ( ) {
648+ self . progress_timeout_watchers
649+ . write ( )
650+ . await
651+ . remove ( & progress_token) ;
652+ }
653+ return Err ( ServiceError :: TransportClosed ) ;
654+ }
479655 Ok ( RequestHandle {
480656 id,
481657 rx : receiver,
482658 progress_token,
483659 options,
484660 peer : self . clone ( ) ,
661+ progress_timeout_watchers : self . progress_timeout_watchers . clone ( ) ,
662+ progress_reset_rx,
485663 } )
486664 }
665+
666+ async fn notify_progress_timeout_watcher ( & self , progress_token : & ProgressToken ) {
667+ let sender = self
668+ . progress_timeout_watchers
669+ . read ( )
670+ . await
671+ . get ( progress_token)
672+ . cloned ( ) ;
673+ if let Some ( sender) = sender {
674+ match sender. try_send ( ( ) ) {
675+ Ok ( ( ) ) => { }
676+ Err ( mpsc:: error:: TrySendError :: Full ( _) ) => {
677+ tracing:: trace!( ?progress_token, "progress timeout watcher channel is full" ) ;
678+ }
679+ Err ( mpsc:: error:: TrySendError :: Closed ( _) ) => {
680+ self . progress_timeout_watchers
681+ . write ( )
682+ . await
683+ . remove ( progress_token) ;
684+ }
685+ }
686+ }
687+ }
688+
487689 /// Snapshot of the peer's handshake info.
488690 pub fn peer_info ( & self ) -> Option < Arc < R :: PeerInfo > > {
489691 self . info . read ( ) . expect ( "peer info lock poisoned" ) . clone ( )
@@ -700,6 +902,7 @@ pub fn serve_directly<R, S, T, E, A>(
700902) -> RunningService < R , S >
701903where
702904 R : ServiceRole ,
905+ R :: PeerNot : ProgressNotificationToken ,
703906 S : Service < R > ,
704907 T : IntoTransport < R , E , A > ,
705908 E : std:: error:: Error + Send + Sync + ' static ,
@@ -716,6 +919,7 @@ pub fn serve_directly_with_ct<R, S, T, E, A>(
716919) -> RunningService < R , S >
717920where
718921 R : ServiceRole ,
922+ R :: PeerNot : ProgressNotificationToken ,
719923 S : Service < R > ,
720924 T : IntoTransport < R , E , A > ,
721925 E : std:: error:: Error + Send + Sync + ' static ,
@@ -756,6 +960,7 @@ fn serve_inner<R, S, T>(
756960) -> RunningService < R , S >
757961where
758962 R : ServiceRole ,
963+ R :: PeerNot : ProgressNotificationToken ,
759964 S : Service < R > ,
760965 T : Transport < R > + ' static ,
761966{
@@ -1002,6 +1207,9 @@ where
10021207 }
10031208 Err ( notification) => notification,
10041209 } ;
1210+ if let Some ( progress_token) = notification. progress_token ( ) {
1211+ peer. notify_progress_timeout_watcher ( progress_token) . await ;
1212+ }
10051213 {
10061214 let service = shared_service. clone ( ) ;
10071215 let mut extensions = Extensions :: new ( ) ;
0 commit comments