Skip to content

Commit 443677c

Browse files
authored
fix: align progress timeout token (#909)
1 parent 4fd4986 commit 443677c

2 files changed

Lines changed: 42 additions & 30 deletions

File tree

crates/rmcp/src/service.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,6 @@ pub struct RequestHandle<R: ServiceRole> {
348348
pub peer: Peer<R>,
349349
pub id: RequestId,
350350
pub progress_token: ProgressToken,
351-
progress_timeout_watchers: ProgressTimeoutWatchers,
352351
progress_reset_rx: Option<mpsc::Receiver<()>>,
353352
}
354353

@@ -416,8 +415,10 @@ impl<R: ServiceRole> RequestHandle<R> {
416415
max_total_timeout: Option<Duration>,
417416
reset_timeout_on_progress: bool,
418417
) -> 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);
418+
let mut idle_sleep =
419+
timeout.map(|timeout| (timeout, Box::pin(tokio::time::sleep(timeout))));
420+
let mut max_total_sleep =
421+
max_total_timeout.map(|timeout| (timeout, Box::pin(tokio::time::sleep(timeout))));
421422

422423
loop {
423424
tokio::select! {
@@ -427,32 +428,34 @@ impl<R: ServiceRole> RequestHandle<R> {
427428
return response.map_err(|_e| ServiceError::TransportClosed)?;
428429
}
429430
_ = async {
430-
if let Some(sleep) = idle_sleep.as_mut() {
431+
if let Some((_, sleep)) = idle_sleep.as_mut() {
431432
sleep.as_mut().await;
432433
}
433434
}, 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 });
435+
if let Some((timeout, _)) = idle_sleep.as_ref() {
436+
self.send_timeout_cancel_notification(Self::REQUEST_TIMEOUT_REASON).await;
437+
return Err(ServiceError::Timeout { timeout: *timeout });
438+
}
437439
}
438440
_ = async {
439-
if let Some(sleep) = max_total_sleep.as_mut() {
441+
if let Some((_, sleep)) = max_total_sleep.as_mut() {
440442
sleep.as_mut().await;
441443
}
442444
}, 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 });
445+
if let Some((timeout, _)) = max_total_sleep.as_ref() {
446+
self.send_timeout_cancel_notification(Self::REQUEST_MAX_TOTAL_TIMEOUT_REASON).await;
447+
return Err(ServiceError::Timeout { timeout: *timeout });
448+
}
446449
}
447450
progress = async {
448451
match self.progress_reset_rx.as_mut() {
449452
Some(rx) => rx.recv().await,
450453
None => None,
451454
}
452-
}, if reset_timeout_on_progress && timeout.is_some() && self.progress_reset_rx.is_some() => {
455+
}, if reset_timeout_on_progress && idle_sleep.is_some() && self.progress_reset_rx.is_some() => {
453456
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);
457+
if let Some((timeout, sleep)) = idle_sleep.as_mut() {
458+
sleep.as_mut().reset(tokio::time::Instant::now() + *timeout);
456459
}
457460
}
458461
}
@@ -463,7 +466,7 @@ impl<R: ServiceRole> RequestHandle<R> {
463466
/// Cancel this request
464467
pub async fn cancel(self, reason: Option<String>) -> Result<(), ServiceError> {
465468
Self::cleanup_progress_timeout_watcher(
466-
&self.progress_timeout_watchers,
469+
&self.peer.progress_timeout_watchers,
467470
&self.progress_token,
468471
self.progress_reset_rx.is_some(),
469472
)
@@ -617,12 +620,12 @@ impl<R: ServiceRole> Peer<R> {
617620
) -> Result<RequestHandle<R>, ServiceError> {
618621
let id = self.request_id_provider.next_request_id();
619622
let progress_token = self.progress_token_provider.next_progress_token();
620-
request
621-
.get_meta_mut()
622-
.set_progress_token(progress_token.clone());
623623
if let Some(meta) = options.meta.clone() {
624624
request.get_meta_mut().extend(meta);
625625
}
626+
request
627+
.get_meta_mut()
628+
.set_progress_token(progress_token.clone());
626629
let (responder, receiver) = tokio::sync::oneshot::channel();
627630
let progress_reset_rx = if options.reset_timeout_on_progress && options.timeout.is_some() {
628631
let (sender, receiver) = mpsc::channel(1);
@@ -658,7 +661,6 @@ impl<R: ServiceRole> Peer<R> {
658661
progress_token,
659662
options,
660663
peer: self.clone(),
661-
progress_timeout_watchers: self.progress_timeout_watchers.clone(),
662664
progress_reset_rx,
663665
})
664666
}

crates/rmcp/tests/test_request_timeout_progress.rs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ use std::{
1010

1111
use rmcp::{
1212
ClientHandler, Peer, RoleServer, ServiceError, ServiceExt,
13-
model::{CallToolRequestParams, ClientRequest, Meta, ProgressNotificationParam, Request},
13+
model::{
14+
CallToolRequestParams, ClientRequest, Meta, NumberOrString, ProgressNotificationParam,
15+
ProgressToken, Request,
16+
},
1417
service::PeerRequestOptions,
1518
tool, tool_router,
1619
};
@@ -32,12 +35,6 @@ impl ClientHandler for ProgressCountingClient {
3235

3336
struct ProgressTimeoutServer;
3437

35-
impl ProgressTimeoutServer {
36-
fn new() -> Self {
37-
Self
38-
}
39-
}
40-
4138
#[tool_router(server_handler)]
4239
impl ProgressTimeoutServer {
4340
#[tool]
@@ -83,9 +80,7 @@ impl ProgressTimeoutServer {
8380
tokio::time::sleep(Duration::from_millis(50)).await;
8481
let _ = client
8582
.notify_progress(ProgressNotificationParam {
86-
progress_token: rmcp::model::ProgressToken(
87-
rmcp::model::NumberOrString::Number(999_999),
88-
),
83+
progress_token: ProgressToken(NumberOrString::Number(999_999)),
8984
progress: step as f64,
9085
total: Some(4.0),
9186
message: Some("unrelated".into()),
@@ -99,7 +94,7 @@ impl ProgressTimeoutServer {
9994

10095
async fn start_pair()
10196
-> anyhow::Result<rmcp::service::RunningService<rmcp::RoleClient, ProgressCountingClient>> {
102-
let server = ProgressTimeoutServer::new();
97+
let server = ProgressTimeoutServer;
10398
let client = ProgressCountingClient::default();
10499
let (transport_server, transport_client) = tokio::io::duplex(4096);
105100

@@ -172,6 +167,21 @@ async fn matching_progress_resets_timeout_when_enabled() -> anyhow::Result<()> {
172167
Ok(())
173168
}
174169

170+
#[tokio::test]
171+
async fn generated_progress_token_overrides_option_meta_token() -> anyhow::Result<()> {
172+
let client = start_pair().await?;
173+
let mut options =
174+
PeerRequestOptions::with_timeout(Duration::from_millis(75)).reset_timeout_on_progress();
175+
options.meta = Some(Meta::with_progress_token(ProgressToken(
176+
NumberOrString::Number(999_999),
177+
)));
178+
179+
let result = call_tool_with_options(&client, "delayed_with_progress", options).await;
180+
181+
assert!(result.is_ok());
182+
Ok(())
183+
}
184+
175185
#[tokio::test]
176186
async fn max_total_timeout_wins_over_progress_reset() -> anyhow::Result<()> {
177187
let client = start_pair().await?;

0 commit comments

Comments
 (0)