@@ -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,142 @@ 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,
329- Err ( _) => {
330- let error = Err ( ServiceError :: Timeout { timeout } ) ;
331- // 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 ;
341- error
357+ pub const REQUEST_MAX_TOTAL_TIMEOUT_REASON : & str = "maximum total timeout exceeded" ;
358+
359+ async fn send_timeout_cancel_notification ( & self , reason : & str ) {
360+ let notification = CancelledNotification {
361+ params : CancelledNotificationParam {
362+ request_id : self . id . clone ( ) ,
363+ reason : Some ( reason. to_owned ( ) ) ,
364+ } ,
365+ method : crate :: model:: CancelledNotificationMethod ,
366+ extensions : Default :: default ( ) ,
367+ } ;
368+ let _ = self . peer . send_notification ( notification. into ( ) ) . await ;
369+ }
370+
371+ async fn cleanup_progress_timeout_watcher (
372+ progress_timeout_watchers : & ProgressTimeoutWatchers ,
373+ progress_token : & ProgressToken ,
374+ has_progress_reset_rx : bool ,
375+ ) {
376+ if has_progress_reset_rx {
377+ progress_timeout_watchers
378+ . write ( )
379+ . await
380+ . remove ( progress_token) ;
381+ }
382+ }
383+
384+ pub async fn await_response ( mut self ) -> Result < R :: PeerResp , ServiceError > {
385+ let timeout = self . options . timeout ;
386+ let max_total_timeout = self . options . max_total_timeout ;
387+ let reset_timeout_on_progress = self . options . reset_timeout_on_progress ;
388+
389+ let has_progress_reset_rx = self . progress_reset_rx . is_some ( ) ;
390+ let progress_timeout_watchers = self . progress_timeout_watchers . clone ( ) ;
391+ let progress_token = self . progress_token . clone ( ) ;
392+
393+ let result =
394+ if timeout. is_some ( ) && !reset_timeout_on_progress && max_total_timeout. is_none ( ) {
395+ let timeout = timeout. expect ( "timeout is checked above" ) ;
396+ let timeout_result = tokio:: time:: timeout ( timeout, & mut self . rx ) . await ;
397+ match timeout_result {
398+ Ok ( response) => response. map_err ( |_e| ServiceError :: TransportClosed ) ?,
399+ Err ( _) => {
400+ let error = Err ( ServiceError :: Timeout { timeout } ) ;
401+ // cancel this request
402+ self . send_timeout_cancel_notification ( Self :: REQUEST_TIMEOUT_REASON )
403+ . await ;
404+ error
405+ }
406+ }
407+ } else if timeout. is_none ( ) && max_total_timeout. is_none ( ) {
408+ ( & mut self . rx )
409+ . await
410+ . map_err ( |_e| ServiceError :: TransportClosed ) ?
411+ } else {
412+ self . await_response_with_progress_timeout (
413+ timeout,
414+ max_total_timeout,
415+ reset_timeout_on_progress,
416+ )
417+ . await
418+ } ;
419+
420+ Self :: cleanup_progress_timeout_watcher (
421+ & progress_timeout_watchers,
422+ & progress_token,
423+ has_progress_reset_rx,
424+ )
425+ . await ;
426+ result
427+ }
428+
429+ async fn await_response_with_progress_timeout (
430+ & mut self ,
431+ timeout : Option < Duration > ,
432+ max_total_timeout : Option < Duration > ,
433+ reset_timeout_on_progress : bool ,
434+ ) -> Result < R :: PeerResp , ServiceError > {
435+ let mut idle_sleep = timeout. map ( tokio:: time:: sleep) . map ( Box :: pin) ;
436+ let mut max_total_sleep = max_total_timeout. map ( tokio:: time:: sleep) . map ( Box :: pin) ;
437+
438+ loop {
439+ tokio:: select! {
440+ biased;
441+
442+ response = & mut self . rx => {
443+ return response. map_err( |_e| ServiceError :: TransportClosed ) ?;
444+ }
445+ _ = async {
446+ if let Some ( sleep) = idle_sleep. as_mut( ) {
447+ sleep. as_mut( ) . await ;
448+ }
449+ } , if idle_sleep. is_some( ) => {
450+ let timeout = timeout. expect( "idle timeout exists when idle sleep exists" ) ;
451+ self . send_timeout_cancel_notification( Self :: REQUEST_TIMEOUT_REASON ) . await ;
452+ return Err ( ServiceError :: Timeout { timeout } ) ;
453+ }
454+ _ = async {
455+ if let Some ( sleep) = max_total_sleep. as_mut( ) {
456+ sleep. as_mut( ) . await ;
457+ }
458+ } , if max_total_sleep. is_some( ) => {
459+ let timeout = max_total_timeout. expect( "max total timeout exists when max total sleep exists" ) ;
460+ self . send_timeout_cancel_notification( Self :: REQUEST_MAX_TOTAL_TIMEOUT_REASON ) . await ;
461+ return Err ( ServiceError :: Timeout { timeout } ) ;
462+ }
463+ progress = async {
464+ match self . progress_reset_rx. as_mut( ) {
465+ Some ( rx) => rx. recv( ) . await ,
466+ None => None ,
467+ }
468+ } , if reset_timeout_on_progress && timeout. is_some( ) && self . progress_reset_rx. is_some( ) => {
469+ if progress. is_some( ) {
470+ if let ( Some ( timeout) , Some ( sleep) ) = ( timeout, idle_sleep. as_mut( ) ) {
471+ sleep. as_mut( ) . reset( tokio:: time:: Instant :: now( ) + timeout) ;
472+ }
473+ }
342474 }
343475 }
344- } else {
345- self . rx . await . map_err ( |_e| ServiceError :: TransportClosed ) ?
346476 }
347477 }
348478
349479 /// Cancel this request
350480 pub async fn cancel ( self , reason : Option < String > ) -> Result < ( ) , ServiceError > {
481+ Self :: cleanup_progress_timeout_watcher (
482+ & self . progress_timeout_watchers ,
483+ & self . progress_token ,
484+ self . progress_reset_rx . is_some ( ) ,
485+ )
486+ . await ;
351487 let notification = CancelledNotification {
352488 params : CancelledNotificationParam {
353489 request_id : self . id ,
@@ -384,6 +520,7 @@ pub struct Peer<R: ServiceRole> {
384520 tx : mpsc:: Sender < PeerSinkMessage < R > > ,
385521 request_id_provider : Arc < dyn RequestIdProvider > ,
386522 progress_token_provider : Arc < dyn ProgressTokenProvider > ,
523+ progress_timeout_watchers : ProgressTimeoutWatchers ,
387524 info : Arc < tokio:: sync:: OnceCell < R :: PeerInfo > > ,
388525}
389526
@@ -403,12 +540,33 @@ type ProxyOutbound<R> = mpsc::Receiver<PeerSinkMessage<R>>;
403540pub struct PeerRequestOptions {
404541 pub timeout : Option < Duration > ,
405542 pub meta : Option < Meta > ,
543+ /// Reset the request timeout when a matching progress notification is received.
544+ pub reset_timeout_on_progress : bool ,
545+ /// Maximum total time to wait for the request, regardless of progress notifications.
546+ pub max_total_timeout : Option < Duration > ,
406547}
407548
408549impl PeerRequestOptions {
409550 pub fn no_options ( ) -> Self {
410551 Self :: default ( )
411552 }
553+
554+ pub fn with_timeout ( timeout : Duration ) -> Self {
555+ Self {
556+ timeout : Some ( timeout) ,
557+ ..Self :: default ( )
558+ }
559+ }
560+
561+ pub fn reset_timeout_on_progress ( mut self ) -> Self {
562+ self . reset_timeout_on_progress = true ;
563+ self
564+ }
565+
566+ pub fn with_max_total_timeout ( mut self , timeout : Duration ) -> Self {
567+ self . max_total_timeout = Some ( timeout) ;
568+ self
569+ }
412570}
413571
414572impl < R : ServiceRole > Peer < R > {
@@ -423,6 +581,7 @@ impl<R: ServiceRole> Peer<R> {
423581 tx,
424582 request_id_provider,
425583 progress_token_provider : Arc :: new ( AtomicU32ProgressTokenProvider :: default ( ) ) ,
584+ progress_timeout_watchers : Default :: default ( ) ,
426585 info : Arc :: new ( tokio:: sync:: OnceCell :: new_with ( peer_info) ) ,
427586 } ,
428587 rx,
@@ -468,6 +627,16 @@ impl<R: ServiceRole> Peer<R> {
468627 request. get_meta_mut ( ) . extend ( meta) ;
469628 }
470629 let ( responder, receiver) = tokio:: sync:: oneshot:: channel ( ) ;
630+ let progress_reset_rx = if options. reset_timeout_on_progress && options. timeout . is_some ( ) {
631+ let ( sender, receiver) = mpsc:: channel ( 1 ) ;
632+ self . progress_timeout_watchers
633+ . write ( )
634+ . await
635+ . insert ( progress_token. clone ( ) , sender) ;
636+ Some ( receiver)
637+ } else {
638+ None
639+ } ;
471640 self . tx
472641 . send ( PeerSinkMessage :: Request {
473642 request,
@@ -482,8 +651,33 @@ impl<R: ServiceRole> Peer<R> {
482651 progress_token,
483652 options,
484653 peer : self . clone ( ) ,
654+ progress_timeout_watchers : self . progress_timeout_watchers . clone ( ) ,
655+ progress_reset_rx,
485656 } )
486657 }
658+
659+ async fn notify_progress_timeout_watcher ( & self , progress_token : & ProgressToken ) {
660+ let sender = self
661+ . progress_timeout_watchers
662+ . read ( )
663+ . await
664+ . get ( progress_token)
665+ . cloned ( ) ;
666+ if let Some ( sender) = sender {
667+ match sender. try_send ( ( ) ) {
668+ Ok ( ( ) ) => { }
669+ Err ( mpsc:: error:: TrySendError :: Full ( _) ) => {
670+ tracing:: trace!( ?progress_token, "progress timeout watcher channel is full" ) ;
671+ }
672+ Err ( mpsc:: error:: TrySendError :: Closed ( _) ) => {
673+ self . progress_timeout_watchers
674+ . write ( )
675+ . await
676+ . remove ( progress_token) ;
677+ }
678+ }
679+ }
680+ }
487681 pub fn peer_info ( & self ) -> Option < & R :: PeerInfo > {
488682 self . info . get ( )
489683 }
@@ -692,6 +886,7 @@ pub fn serve_directly<R, S, T, E, A>(
692886) -> RunningService < R , S >
693887where
694888 R : ServiceRole ,
889+ R :: PeerNot : ProgressNotificationToken ,
695890 S : Service < R > ,
696891 T : IntoTransport < R , E , A > ,
697892 E : std:: error:: Error + Send + Sync + ' static ,
@@ -708,6 +903,7 @@ pub fn serve_directly_with_ct<R, S, T, E, A>(
708903) -> RunningService < R , S >
709904where
710905 R : ServiceRole ,
906+ R :: PeerNot : ProgressNotificationToken ,
711907 S : Service < R > ,
712908 T : IntoTransport < R , E , A > ,
713909 E : std:: error:: Error + Send + Sync + ' static ,
@@ -748,6 +944,7 @@ fn serve_inner<R, S, T>(
748944) -> RunningService < R , S >
749945where
750946 R : ServiceRole ,
947+ R :: PeerNot : ProgressNotificationToken ,
751948 S : Service < R > ,
752949 T : Transport < R > + ' static ,
753950{
@@ -994,6 +1191,9 @@ where
9941191 }
9951192 Err ( notification) => notification,
9961193 } ;
1194+ if let Some ( progress_token) = notification. progress_token ( ) {
1195+ peer. notify_progress_timeout_watcher ( progress_token) . await ;
1196+ }
9971197 {
9981198 let service = shared_service. clone ( ) ;
9991199 let mut extensions = Extensions :: new ( ) ;
0 commit comments