1use crate::error::{JsonRpcError, McpError};
11use crate::types::task::{
12 CancelTaskResult, GetTaskResult, ListTasksResult, Task, TaskId, TaskStatus,
13};
14use event_listener::Event;
15use serde_json::Value;
16use std::collections::HashMap;
17use std::future::Future;
18use std::pin::Pin;
19use std::sync::atomic::{AtomicBool, Ordering};
20use std::sync::{Arc, RwLock};
21use std::task::{Context as TaskContext, Poll};
22use std::time::Instant;
23
24pub const RELATED_TASK_META_KEY: &str = "io.modelcontextprotocol/related-task";
27
28#[derive(Clone)]
37pub struct CancellationToken {
38 cancelled: Arc<AtomicBool>,
39 event: Arc<Event>,
40}
41
42impl CancellationToken {
43 #[must_use]
45 pub fn new() -> Self {
46 Self {
47 cancelled: Arc::new(AtomicBool::new(false)),
48 event: Arc::new(Event::new()),
49 }
50 }
51
52 #[must_use]
54 pub fn is_cancelled(&self) -> bool {
55 self.cancelled.load(Ordering::SeqCst)
56 }
57
58 pub fn cancel(&self) {
60 self.cancelled.store(true, Ordering::SeqCst);
61 self.event.notify(usize::MAX);
63 }
64
65 #[must_use]
71 pub fn cancelled(&self) -> CancelledFuture {
72 CancelledFuture::new(self.cancelled.clone(), self.event.clone())
73 }
74}
75
76impl std::fmt::Debug for CancellationToken {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.debug_struct("CancellationToken")
79 .field("cancelled", &self.is_cancelled())
80 .finish()
81 }
82}
83
84impl Default for CancellationToken {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90pub struct CancelledFuture {
95 inner: Pin<Box<dyn Future<Output = ()> + Send>>,
96}
97
98impl CancelledFuture {
99 fn new(cancelled: Arc<AtomicBool>, event: Arc<Event>) -> Self {
100 Self {
101 inner: Box::pin(async move {
102 loop {
103 if cancelled.load(Ordering::SeqCst) {
104 return;
105 }
106 let listener = event.listen();
111 if cancelled.load(Ordering::SeqCst) {
112 return;
113 }
114 listener.await;
115 }
116 }),
117 }
118 }
119}
120
121impl Future for CancelledFuture {
122 type Output = ();
123
124 fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
125 self.inner.as_mut().poll(cx)
126 }
127}
128
129#[derive(Debug, Clone)]
138pub enum TaskPayload {
139 Success(Value),
144 Error(JsonRpcError),
146}
147
148#[derive(Debug, Clone)]
150pub struct TaskState {
151 pub task: Task,
153 pub payload: Option<TaskPayload>,
156 pub cancel_token: CancellationToken,
158 pub last_access: Instant,
160 pub created: Instant,
163 terminal: Arc<Event>,
166}
167
168impl TaskState {
169 fn new(task: Task) -> Self {
170 let now = Instant::now();
171 Self {
172 task,
173 payload: None,
174 cancel_token: CancellationToken::new(),
175 last_access: now,
176 created: now,
177 terminal: Arc::new(Event::new()),
178 }
179 }
180
181 #[must_use]
183 pub fn is_cancelled(&self) -> bool {
184 self.cancel_token.is_cancelled()
185 }
186}
187
188pub struct TaskHandle {
190 task_id: TaskId,
191 manager: Arc<TaskManager>,
192}
193
194impl TaskHandle {
195 #[must_use]
197 pub const fn id(&self) -> &TaskId {
198 &self.task_id
199 }
200
201 #[must_use]
203 pub fn task(&self) -> Option<Task> {
204 self.manager.get(&self.task_id).map(|s| s.task)
205 }
206
207 #[must_use]
210 pub fn cancel_token(&self) -> Option<CancellationToken> {
211 self.manager.get(&self.task_id).map(|s| s.cancel_token)
212 }
213
214 pub fn mark_input_required(&self) -> Result<(), McpError> {
216 self.manager
217 .set_status(&self.task_id, TaskStatus::InputRequired, None)
218 }
219
220 pub fn complete(&self, payload: Value) -> Result<(), McpError> {
222 self.manager.finish(
223 &self.task_id,
224 TaskStatus::Completed,
225 Some(TaskPayload::Success(payload)),
226 None,
227 )
228 }
229
230 pub fn fail(&self, message: impl Into<String>) -> Result<(), McpError> {
237 let message = message.into();
238 self.manager.finish(
239 &self.task_id,
240 TaskStatus::Failed,
241 Some(TaskPayload::Error(JsonRpcError::internal_error(
242 message.clone(),
243 ))),
244 Some(message),
245 )
246 }
247
248 pub fn fail_with_error(&self, error: JsonRpcError) -> Result<(), McpError> {
251 let message = error.message.clone();
252 self.manager.finish(
253 &self.task_id,
254 TaskStatus::Failed,
255 Some(TaskPayload::Error(error)),
256 Some(message),
257 )
258 }
259
260 pub fn fail_with_result(
265 &self,
266 payload: Value,
267 message: Option<String>,
268 ) -> Result<(), McpError> {
269 self.manager.finish(
270 &self.task_id,
271 TaskStatus::Failed,
272 Some(TaskPayload::Success(payload)),
273 message,
274 )
275 }
276
277 #[must_use]
279 pub fn is_cancelled(&self) -> bool {
280 self.manager
281 .get(&self.task_id)
282 .is_none_or(|s| s.is_cancelled())
283 }
284
285 pub async fn cancelled(&self) {
287 if let Some(state) = self.manager.get(&self.task_id) {
288 state.cancel_token.cancelled().await;
289 }
290 }
291}
292
293pub const DEFAULT_TASK_TTL_MS: u64 = 60 * 60 * 1000;
296
297#[derive(Debug, Clone)]
308pub struct TaskEvent {
309 pub task: Task,
311 pub previous_status: TaskStatus,
313}
314
315pub trait TaskObserver: Send + Sync + std::fmt::Debug {
330 fn on_task_event(&self, event: &TaskEvent);
332}
333
334#[derive(Debug)]
336pub struct TaskManager {
337 tasks: RwLock<HashMap<TaskId, TaskState>>,
338 default_ttl_ms: Option<u64>,
341 observer: std::sync::OnceLock<Arc<dyn TaskObserver>>,
343 default_poll_interval_ms: Option<u64>,
347}
348
349impl Default for TaskManager {
350 fn default() -> Self {
351 Self::new()
352 }
353}
354
355impl TaskManager {
356 #[must_use]
359 pub fn new() -> Self {
360 Self::with_default_ttl(Some(DEFAULT_TASK_TTL_MS))
361 }
362
363 #[must_use]
367 pub fn with_default_ttl(default_ttl_ms: Option<u64>) -> Self {
368 Self {
369 tasks: RwLock::new(HashMap::new()),
370 default_ttl_ms,
371 observer: std::sync::OnceLock::new(),
372 default_poll_interval_ms: None,
373 }
374 }
375
376 #[must_use]
384 pub const fn with_poll_interval(mut self, poll_interval_ms: Option<u64>) -> Self {
385 self.default_poll_interval_ms = poll_interval_ms;
386 self
387 }
388
389 pub fn set_observer(&self, observer: Arc<dyn TaskObserver>) -> Result<(), McpError> {
397 self.observer
398 .set(observer)
399 .map_err(|_| McpError::internal("task observer already registered"))
400 }
401
402 fn emit(&self, event: &TaskEvent) {
406 if let Some(observer) = self.observer.get() {
407 observer.on_task_event(event);
408 }
409 }
410
411 pub fn create(self: &Arc<Self>, ttl: Option<u64>) -> TaskHandle {
416 self.cleanup_expired();
417
418 let mut task = Task::create();
419 task.ttl = ttl.or(self.default_ttl_ms);
420 task.poll_interval = self.default_poll_interval_ms;
421 let task_id = task.task_id.clone();
422
423 if let Ok(mut tasks) = self.tasks.write() {
424 tasks.insert(task_id.clone(), TaskState::new(task));
425 }
426
427 TaskHandle {
428 task_id,
429 manager: Arc::clone(self),
430 }
431 }
432
433 #[must_use]
435 pub fn get(&self, id: &TaskId) -> Option<TaskState> {
436 self.tasks.read().ok()?.get(id).cloned()
437 }
438
439 #[must_use]
441 pub fn list(&self) -> Vec<Task> {
442 self.tasks
443 .read()
444 .map(|tasks| tasks.values().map(|s| s.task.clone()).collect())
445 .unwrap_or_default()
446 }
447
448 #[must_use]
450 pub fn payload(&self, id: &TaskId) -> Option<TaskPayload> {
451 self.tasks.read().ok()?.get(id)?.payload.clone()
452 }
453
454 pub async fn wait_terminal(&self, id: &TaskId) -> Option<TaskState> {
461 loop {
462 let listener = {
463 let tasks = self.tasks.read().ok()?;
464 let state = tasks.get(id)?;
465 if state.task.status.is_terminal() {
466 return Some(state.clone());
467 }
468 state.terminal.listen()
472 };
473 listener.await;
474 }
475 }
476
477 pub fn cancel(&self, id: &TaskId) -> Result<(), McpError> {
482 let event = {
485 let mut tasks = self
486 .tasks
487 .write()
488 .map_err(|_| McpError::internal("Failed to acquire task lock"))?;
489
490 if let Some(state) = tasks.get_mut(id) {
491 if state.task.status.is_terminal() {
492 return Err(McpError::invalid_params(
493 "tasks/cancel",
494 format!(
495 "Cannot cancel task: already in terminal status '{}'",
496 state.task.status
497 ),
498 ));
499 }
500 let previous_status = state.task.status;
501 state.cancel_token.cancel();
502 state.task.set_status(TaskStatus::Cancelled);
503 state.last_access = Instant::now();
504 state.terminal.notify(usize::MAX);
505 TaskEvent {
506 task: state.task.clone(),
507 previous_status,
508 }
509 } else {
510 return Err(McpError::invalid_params(
511 "tasks/cancel",
512 format!("Unknown task: {}", id.as_str()),
513 ));
514 }
515 };
516
517 self.emit(&event);
518 Ok(())
519 }
520
521 fn set_status(
523 &self,
524 id: &TaskId,
525 status: TaskStatus,
526 message: Option<String>,
527 ) -> Result<(), McpError> {
528 let event = {
529 let mut tasks = self
530 .tasks
531 .write()
532 .map_err(|_| McpError::internal("Failed to acquire task lock"))?;
533
534 if let Some(state) = tasks.get_mut(id) {
535 if state.task.status.is_terminal() {
538 return Err(McpError::invalid_params(
539 "tasks/get",
540 format!(
541 "task {} is already terminal ('{}')",
542 id.as_str(),
543 state.task.status
544 ),
545 ));
546 }
547 let previous_status = state.task.status;
548 state.task.set_status(status);
549 if message.is_some() {
550 state.task.status_message = message;
551 }
552 state.last_access = Instant::now();
553 if status.is_terminal() {
554 state.terminal.notify(usize::MAX);
555 }
556 TaskEvent {
557 task: state.task.clone(),
558 previous_status,
559 }
560 } else {
561 return Err(McpError::invalid_params(
562 "tasks/get",
563 format!("Unknown task: {}", id.as_str()),
564 ));
565 }
566 };
567
568 self.emit(&event);
569 Ok(())
570 }
571
572 fn finish(
574 &self,
575 id: &TaskId,
576 status: TaskStatus,
577 payload: Option<TaskPayload>,
578 message: Option<String>,
579 ) -> Result<(), McpError> {
580 let event = {
581 let mut tasks = self
582 .tasks
583 .write()
584 .map_err(|_| McpError::internal("Failed to acquire task lock"))?;
585
586 if let Some(state) = tasks.get_mut(id) {
587 if state.task.status.is_terminal() {
591 return Err(McpError::invalid_params(
592 "tasks/result",
593 format!(
594 "task {} is already terminal ('{}')",
595 id.as_str(),
596 state.task.status
597 ),
598 ));
599 }
600 let previous_status = state.task.status;
601 state.task.set_status(status);
602 if message.is_some() {
603 state.task.status_message = message;
604 }
605 state.payload = payload;
606 state.last_access = Instant::now();
607 state.terminal.notify(usize::MAX);
608 TaskEvent {
609 task: state.task.clone(),
610 previous_status,
611 }
612 } else {
613 return Err(McpError::invalid_params(
614 "tasks/result",
615 format!("Unknown task: {}", id.as_str()),
616 ));
617 }
618 };
619
620 self.emit(&event);
621 Ok(())
622 }
623
624 pub fn cleanup(&self, max_age: std::time::Duration) {
626 if let Ok(mut tasks) = self.tasks.write() {
627 tasks.retain(|_, state| {
628 let is_terminal = state.task.status.is_terminal();
629 !is_terminal || state.last_access.elapsed() < max_age
630 });
631 }
632 }
633
634 pub fn cleanup_expired(&self) {
639 if let Ok(mut tasks) = self.tasks.write() {
640 tasks.retain(|_, state| {
641 if !state.task.status.is_terminal() {
642 return true;
643 }
644 match state.task.ttl {
645 Some(ttl_ms) => {
646 state.created.elapsed() < std::time::Duration::from_millis(ttl_ms)
647 }
648 None => true,
649 }
650 });
651 }
652 }
653}
654
655#[must_use]
673pub fn inject_related_task(mut payload: Value, id: &TaskId) -> Value {
674 if let Value::Object(map) = &mut payload {
675 if let Value::Object(meta) = map
676 .entry("_meta")
677 .or_insert_with(|| Value::Object(serde_json::Map::new()))
678 {
679 meta.insert(
680 RELATED_TASK_META_KEY.to_string(),
681 serde_json::json!({ "taskId": id.as_str() }),
682 );
683 }
684 }
685 payload
686}
687
688pub async fn route_task_store(
704 store: &TaskManager,
705 method: &str,
706 params: Option<&Value>,
707) -> TaskRoute {
708 TaskRoute::from_parts(
709 route_task_store_inner(store, method, params).await,
710 method,
711 params,
712 )
713}
714
715#[derive(Debug)]
732pub enum TaskRoute {
733 NotTaskMethod,
735 UnownedTask {
737 method: String,
739 task_id: String,
741 },
742 Handled(Result<Value, McpError>),
744}
745
746impl TaskRoute {
747 fn from_parts(
748 inner: Option<Result<Value, McpError>>,
749 method: &str,
750 params: Option<&Value>,
751 ) -> Self {
752 match inner {
753 Some(result) => Self::Handled(result),
754 None if is_task_method(method) => Self::UnownedTask {
755 method: method.to_string(),
756 task_id: params
757 .and_then(|p| p.get("taskId"))
758 .and_then(|v| v.as_str())
759 .unwrap_or("<missing>")
760 .to_string(),
761 },
762 None => Self::NotTaskMethod,
763 }
764 }
765
766 #[must_use]
772 pub fn or_unknown_task(self) -> Option<Result<Value, McpError>> {
773 match self {
774 Self::NotTaskMethod => None,
775 Self::UnownedTask { method, task_id } => Some(Err(McpError::invalid_params(
776 method,
777 format!("Unknown task: {task_id}"),
778 ))),
779 Self::Handled(result) => Some(result),
780 }
781 }
782}
783
784fn is_task_method(method: &str) -> bool {
788 matches!(method, "tasks/get" | "tasks/result" | "tasks/cancel")
789}
790
791async fn route_task_store_inner(
792 store: &TaskManager,
793 method: &str,
794 params: Option<&Value>,
795) -> Option<Result<Value, McpError>> {
796 store.cleanup_expired();
799 let task_id = || {
800 params
801 .and_then(|p| p.get("taskId"))
802 .and_then(|v| v.as_str())
803 .map(TaskId::new)
804 };
805 match method {
806 "tasks/list" => {
807 let result = ListTasksResult::from(store.list());
810 Some(Ok(serde_json::to_value(result).unwrap_or_default()))
811 }
812 "tasks/get" => {
813 let Some(id) = task_id() else {
814 return Some(Err(McpError::invalid_params("tasks/get", "missing taskId")));
815 };
816 store.get(&id).map(|s| {
817 let result = GetTaskResult::from(s.task);
818 Ok(serde_json::to_value(result).unwrap_or_default())
819 })
820 }
821 "tasks/result" => {
822 let Some(id) = task_id() else {
823 return Some(Err(McpError::invalid_params(
824 "tasks/result",
825 "missing taskId",
826 )));
827 };
828 store.get(&id)?;
830 let Some(state) = store.wait_terminal(&id).await else {
832 return Some(Err(McpError::invalid_params(
834 "tasks/result",
835 format!("Task has expired: {}", id.as_str()),
836 )));
837 };
838 match state.payload {
839 Some(TaskPayload::Success(payload)) => Some(Ok(inject_related_task(payload, &id))),
840 Some(TaskPayload::Error(error)) => Some(Err(McpError::JsonRpc(error))),
841 None => Some(Err(McpError::invalid_params(
844 "tasks/result",
845 format!(
846 "task {} ended {} with no result",
847 id.as_str(),
848 state.task.status
849 ),
850 ))),
851 }
852 }
853 "tasks/cancel" => {
854 let Some(id) = task_id() else {
855 return Some(Err(McpError::invalid_params(
856 "tasks/cancel",
857 "missing taskId",
858 )));
859 };
860 if store.get(&id).is_some() {
861 if let Err(e) = store.cancel(&id) {
863 return Some(Err(e));
864 }
865 Some(Ok(store
866 .get(&id)
867 .map(|s| {
868 let result = CancelTaskResult::from(s.task);
869 serde_json::to_value(result).unwrap_or_default()
870 })
871 .unwrap_or_default()))
872 } else {
873 None
874 }
875 }
876 _ => None,
877 }
878}
879
880#[cfg(test)]
881mod tests {
882
883 #[test]
888 fn poll_interval_is_absent_by_default_and_set_when_configured() {
889 let plain = Arc::new(TaskManager::new());
890 let a = plain.create(None);
891 assert_eq!(a.task().expect("task").poll_interval, None);
892
893 let suggesting = Arc::new(TaskManager::new().with_poll_interval(Some(250)));
894 let b = suggesting.create(None);
895 let task = b.task().expect("task");
896 assert_eq!(task.poll_interval, Some(250));
897
898 let wire = serde_json::to_value(&task).expect("serialize");
900 assert_eq!(wire["pollInterval"], 250);
901 }
902
903 use super::*;
904
905 #[derive(Debug, Default)]
907 struct Collector {
908 events: std::sync::Mutex<Vec<TaskEvent>>,
909 }
910
911 impl TaskObserver for Collector {
912 fn on_task_event(&self, event: &TaskEvent) {
913 if let Ok(mut events) = self.events.lock() {
914 events.push(event.clone());
915 }
916 }
917 }
918
919 impl Collector {
920 fn transitions(&self) -> Vec<(TaskStatus, TaskStatus)> {
921 self.events
922 .lock()
923 .map(|e| {
924 e.iter()
925 .map(|e| (e.previous_status, e.task.status))
926 .collect()
927 })
928 .unwrap_or_default()
929 }
930 }
931
932 #[test]
933 fn test_observer_sees_every_transition() -> Result<(), Box<dyn std::error::Error>> {
934 let manager = Arc::new(TaskManager::new());
935 let collector = Arc::new(Collector::default());
936 manager.set_observer(collector.clone())?;
937
938 let a = manager.create(None);
940 a.mark_input_required()?;
941 a.complete(serde_json::json!({"ok": true}))?;
943 let b = manager.create(None);
945 manager.cancel(b.id())?;
946
947 assert_eq!(
948 collector.transitions(),
949 vec![
950 (TaskStatus::Working, TaskStatus::InputRequired),
951 (TaskStatus::InputRequired, TaskStatus::Completed),
952 (TaskStatus::Working, TaskStatus::Cancelled),
953 ]
954 );
955 Ok(())
956 }
957
958 #[test]
959 fn test_observer_may_reenter_the_manager() -> Result<(), Box<dyn std::error::Error>> {
960 #[derive(Debug)]
963 struct Reentrant(std::sync::Weak<TaskManager>);
964
965 impl TaskObserver for Reentrant {
966 fn on_task_event(&self, event: &TaskEvent) {
967 if let Some(manager) = self.0.upgrade() {
968 assert!(manager.get(&event.task.task_id).is_some());
969 let _ = manager.list();
970 }
971 }
972 }
973
974 let manager = Arc::new(TaskManager::new());
975 manager.set_observer(Arc::new(Reentrant(Arc::downgrade(&manager))))?;
976
977 let handle = manager.create(None);
978 handle.complete(serde_json::json!({}))?;
979 assert_eq!(
980 manager.get(handle.id()).ok_or("not found")?.task.status,
981 TaskStatus::Completed
982 );
983 Ok(())
984 }
985
986 #[test]
987 fn test_observer_registers_at_most_once() {
988 let manager = Arc::new(TaskManager::new());
989 assert!(manager.set_observer(Arc::new(Collector::default())).is_ok());
990 assert!(
991 manager
992 .set_observer(Arc::new(Collector::default()))
993 .is_err()
994 );
995 }
996
997 #[test]
998 fn test_task_manager_create_and_list() {
999 let manager = Arc::new(TaskManager::new());
1000
1001 let handle = manager.create(None);
1002 assert!(!handle.is_cancelled());
1003
1004 let tasks = manager.list();
1005 assert_eq!(tasks.len(), 1);
1006 assert_eq!(tasks[0].status, TaskStatus::Working);
1007 }
1008
1009 #[test]
1010 fn test_task_complete_stores_payload() -> Result<(), Box<dyn std::error::Error>> {
1011 let manager = Arc::new(TaskManager::new());
1012 let handle = manager.create(None);
1013 let task_id = handle.id().clone();
1014
1015 handle.complete(serde_json::json!({"result": "ok"}))?;
1016
1017 let state = manager.get(&task_id).ok_or("Task not found")?;
1018 assert_eq!(state.task.status, TaskStatus::Completed);
1019 match manager.payload(&task_id) {
1020 Some(TaskPayload::Success(v)) => {
1021 assert_eq!(v, serde_json::json!({"result": "ok"}));
1022 }
1023 other => panic!("expected success payload, got {other:?}"),
1024 }
1025 Ok(())
1026 }
1027
1028 #[test]
1029 fn test_task_input_required_and_fail() -> Result<(), Box<dyn std::error::Error>> {
1030 let manager = Arc::new(TaskManager::new());
1031 let handle = manager.create(None);
1032 let task_id = handle.id().clone();
1033
1034 handle.mark_input_required()?;
1035 assert_eq!(
1036 manager.get(&task_id).ok_or("not found")?.task.status,
1037 TaskStatus::InputRequired
1038 );
1039
1040 handle.fail("boom")?;
1041 let state = manager.get(&task_id).ok_or("not found")?;
1042 assert_eq!(state.task.status, TaskStatus::Failed);
1043 assert_eq!(state.task.status_message.as_deref(), Some("boom"));
1044 Ok(())
1045 }
1046
1047 #[test]
1048 fn test_task_cancellation() -> Result<(), Box<dyn std::error::Error>> {
1049 let manager = Arc::new(TaskManager::new());
1050 let handle = manager.create(None);
1051 let task_id = handle.id().clone();
1052
1053 assert!(!handle.is_cancelled());
1054 manager.cancel(&task_id)?;
1055 assert!(handle.is_cancelled());
1056 assert_eq!(
1057 manager.get(&task_id).ok_or("not found")?.task.status,
1058 TaskStatus::Cancelled
1059 );
1060 Ok(())
1061 }
1062
1063 #[test]
1066 fn omitted_ttl_is_materialized_to_default() {
1067 let manager = Arc::new(TaskManager::with_default_ttl(Some(5000)));
1068 assert_eq!(manager.create(None).task().unwrap().ttl, Some(5000));
1070 assert_eq!(manager.create(Some(1234)).task().unwrap().ttl, Some(1234));
1072 }
1073
1074 #[test]
1075 fn cleanup_expired_evicts_old_terminal_task() {
1076 let manager = Arc::new(TaskManager::with_default_ttl(Some(1)));
1077 let handle = manager.create(None); let id = handle.id().clone();
1079 handle.complete(serde_json::json!({})).unwrap();
1080 std::thread::sleep(std::time::Duration::from_millis(20));
1081 manager.cleanup_expired();
1082 assert!(
1083 manager.get(&id).is_none(),
1084 "expired terminal task not evicted"
1085 );
1086 }
1087
1088 #[test]
1089 fn cleanup_expired_keeps_fresh_terminal_task() {
1090 let manager = Arc::new(TaskManager::new());
1091 let handle = manager.create(Some(60_000));
1092 let id = handle.id().clone();
1093 handle.complete(serde_json::json!({})).unwrap();
1094 manager.cleanup_expired();
1095 assert!(
1096 manager.get(&id).is_some(),
1097 "fresh terminal task wrongly evicted"
1098 );
1099 }
1100
1101 #[test]
1102 fn cleanup_expired_keeps_non_terminal_task() {
1103 let manager = Arc::new(TaskManager::with_default_ttl(Some(1)));
1104 let handle = manager.create(None); let id = handle.id().clone();
1106 std::thread::sleep(std::time::Duration::from_millis(20));
1107 manager.cleanup_expired();
1108 assert!(
1109 manager.get(&id).is_some(),
1110 "non-terminal task must never be evicted"
1111 );
1112 }
1113
1114 #[test]
1115 fn unlimited_ttl_is_never_evicted() {
1116 let manager = Arc::new(TaskManager::with_default_ttl(None));
1117 let handle = manager.create(None); let id = handle.id().clone();
1119 assert_eq!(handle.task().unwrap().ttl, None);
1120 handle.complete(serde_json::json!({})).unwrap();
1121 std::thread::sleep(std::time::Duration::from_millis(20));
1122 manager.cleanup_expired();
1123 assert!(
1124 manager.get(&id).is_some(),
1125 "unlimited-ttl task wrongly evicted"
1126 );
1127 }
1128
1129 #[tokio::test]
1130 async fn route_task_store_access_triggers_cleanup() {
1131 let manager = Arc::new(TaskManager::with_default_ttl(Some(1)));
1132 let handle = manager.create(None);
1133 let id = handle.id().clone();
1134 handle.complete(serde_json::json!({})).unwrap();
1135 std::thread::sleep(std::time::Duration::from_millis(20));
1136 let _ = route_task_store(&manager, "tasks/list", None).await;
1138 assert!(manager.get(&id).is_none(), "access did not trigger cleanup");
1139 }
1140
1141 fn result_params(id: &TaskId) -> Value {
1144 serde_json::json!({ "taskId": id.as_str() })
1145 }
1146
1147 #[tokio::test]
1148 async fn tasks_result_returns_immediately_for_terminal_task() {
1149 let manager = Arc::new(TaskManager::new());
1150 let handle = manager.create(None);
1151 let id = handle.id().clone();
1152 handle
1153 .complete(serde_json::json!({ "answer": 42 }))
1154 .unwrap();
1155
1156 let params = result_params(&id);
1157 let result = route_task_store(&manager, "tasks/result", Some(¶ms))
1158 .await
1159 .or_unknown_task()
1160 .expect("owned task")
1161 .expect("success");
1162 assert_eq!(result["answer"], 42);
1163 }
1164
1165 #[tokio::test]
1166 async fn tasks_result_blocks_until_terminal() {
1167 let manager = Arc::new(TaskManager::new());
1168 let handle = manager.create(None);
1169 let id = handle.id().clone();
1170
1171 let completer = {
1172 let manager = Arc::clone(&manager);
1173 tokio::spawn(async move {
1174 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1175 let handle = TaskHandle {
1176 task_id: id,
1177 manager,
1178 };
1179 handle
1180 .complete(serde_json::json!({ "late": true }))
1181 .unwrap();
1182 })
1183 };
1184
1185 let params = result_params(handle.id());
1186 let started = std::time::Instant::now();
1187 let result = tokio::time::timeout(std::time::Duration::from_secs(5), async {
1188 route_task_store(&manager, "tasks/result", Some(¶ms))
1189 .await
1190 .or_unknown_task()
1191 })
1192 .await
1193 .expect("must not hang")
1194 .expect("owned task")
1195 .expect("success");
1196 assert_eq!(result["late"], true);
1197 assert!(
1198 started.elapsed() >= std::time::Duration::from_millis(40),
1199 "must have blocked until completion"
1200 );
1201 completer.await.unwrap();
1202 }
1203
1204 #[tokio::test]
1205 async fn tasks_result_reproduces_stored_jsonrpc_error() {
1206 let manager = Arc::new(TaskManager::new());
1207 let handle = manager.create(None);
1208 let id = handle.id().clone();
1209 let stored = JsonRpcError {
1210 code: -32001,
1211 message: "downstream exploded".to_string(),
1212 data: Some(serde_json::json!({ "detail": "xyz" })),
1213 };
1214 handle.fail_with_error(stored.clone()).unwrap();
1215
1216 let params = result_params(&id);
1217 let err = route_task_store(&manager, "tasks/result", Some(¶ms))
1218 .await
1219 .or_unknown_task()
1220 .expect("owned task")
1221 .expect_err("stored error");
1222 let wire: JsonRpcError = (&err).into();
1223 assert_eq!(wire.code, stored.code);
1224 assert_eq!(wire.message, stored.message);
1225 assert_eq!(wire.data, stored.data);
1226 let state = manager.get(&id).unwrap();
1228 assert_eq!(state.task.status, TaskStatus::Failed);
1229 assert_eq!(
1230 state.task.status_message.as_deref(),
1231 Some("downstream exploded")
1232 );
1233 }
1234
1235 #[tokio::test]
1236 async fn tasks_result_success_carries_related_task_meta() {
1237 let manager = Arc::new(TaskManager::new());
1238 let handle = manager.create(None);
1239 let id = handle.id().clone();
1240 handle
1242 .complete(serde_json::json!({ "ok": true, "_meta": { "keep": 1 } }))
1243 .unwrap();
1244
1245 let params = result_params(&id);
1246 let result = route_task_store(&manager, "tasks/result", Some(¶ms))
1247 .await
1248 .or_unknown_task()
1249 .expect("owned task")
1250 .expect("success");
1251 assert_eq!(result["_meta"]["keep"], 1);
1252 assert_eq!(
1253 result["_meta"][RELATED_TASK_META_KEY]["taskId"],
1254 id.as_str()
1255 );
1256 }
1257
1258 #[tokio::test]
1259 async fn failed_task_with_success_payload_returns_it() {
1260 let manager = Arc::new(TaskManager::new());
1263 let handle = manager.create(None);
1264 let id = handle.id().clone();
1265 handle
1266 .fail_with_result(
1267 serde_json::json!({ "isError": true, "content": [] }),
1268 Some("tool reported an error".to_string()),
1269 )
1270 .unwrap();
1271
1272 assert_eq!(manager.get(&id).unwrap().task.status, TaskStatus::Failed);
1273 let params = result_params(&id);
1274 let result = route_task_store(&manager, "tasks/result", Some(¶ms))
1275 .await
1276 .or_unknown_task()
1277 .expect("owned task")
1278 .expect("isError result is still a successful JSON-RPC response");
1279 assert_eq!(result["isError"], true);
1280 assert_eq!(
1281 result["_meta"][RELATED_TASK_META_KEY]["taskId"],
1282 id.as_str()
1283 );
1284 }
1285
1286 #[tokio::test]
1287 async fn tasks_result_for_cancelled_task_is_an_error() {
1288 let manager = Arc::new(TaskManager::new());
1289 let handle = manager.create(None);
1290 let id = handle.id().clone();
1291 manager.cancel(&id).unwrap();
1292
1293 let params = result_params(&id);
1294 let err = route_task_store(&manager, "tasks/result", Some(¶ms))
1295 .await
1296 .or_unknown_task()
1297 .expect("owned task")
1298 .expect_err("cancelled task has no result");
1299 assert_eq!(err.code(), -32602);
1300 }
1301
1302 #[tokio::test]
1303 async fn cancel_unblocks_tasks_result_waiter() {
1304 let manager = Arc::new(TaskManager::new());
1305 let handle = manager.create(None);
1306 let id = handle.id().clone();
1307
1308 let canceller = {
1309 let manager = Arc::clone(&manager);
1310 let id = id.clone();
1311 tokio::spawn(async move {
1312 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1313 manager.cancel(&id).unwrap();
1314 })
1315 };
1316
1317 let params = result_params(&id);
1318 let outcome = tokio::time::timeout(std::time::Duration::from_secs(5), async {
1319 route_task_store(&manager, "tasks/result", Some(¶ms))
1320 .await
1321 .or_unknown_task()
1322 })
1323 .await
1324 .expect("cancel must unblock the waiter")
1325 .expect("owned task");
1326 assert!(outcome.is_err(), "cancelled task has no result");
1327 canceller.await.unwrap();
1328 }
1329
1330 #[test]
1333 fn cancelled_task_stays_cancelled_when_execution_completes() {
1334 let manager = Arc::new(TaskManager::new());
1335 let handle = manager.create(None);
1336 let id = handle.id().clone();
1337 manager.cancel(&id).unwrap();
1338
1339 assert!(handle.complete(serde_json::json!({ "late": 1 })).is_err());
1341 assert!(handle.fail("late failure").is_err());
1342
1343 let state = manager.get(&id).unwrap();
1344 assert_eq!(state.task.status, TaskStatus::Cancelled);
1345 assert!(state.payload.is_none(), "late outcome must be discarded");
1346 }
1347
1348 #[tokio::test]
1349 async fn cancel_on_terminal_task_is_invalid_params() {
1350 let manager = Arc::new(TaskManager::new());
1351 let handle = manager.create(None);
1352 let id = handle.id().clone();
1353 handle.complete(serde_json::json!({})).unwrap();
1354
1355 let err = manager.cancel(&id).expect_err("terminal cancel rejected");
1357 assert_eq!(err.code(), -32602);
1358
1359 let params = result_params(&id);
1361 let err = route_task_store(&manager, "tasks/cancel", Some(¶ms))
1362 .await
1363 .or_unknown_task()
1364 .expect("owned task")
1365 .expect_err("terminal cancel rejected");
1366 assert_eq!(err.code(), -32602);
1367 }
1368
1369 #[test]
1372 fn test_cancellation_token() {
1373 let token = CancellationToken::new();
1374 assert!(!token.is_cancelled());
1375 token.cancel();
1376 assert!(token.is_cancelled());
1377 }
1378
1379 #[test]
1384 fn cancelled_future_parks_and_wakes_on_cancel() {
1385 use std::sync::atomic::AtomicUsize;
1386 use std::task::{Wake, Waker};
1387
1388 struct CountingWaker(AtomicUsize);
1389 impl Wake for CountingWaker {
1390 fn wake(self: Arc<Self>) {
1391 self.0.fetch_add(1, Ordering::SeqCst);
1392 }
1393 fn wake_by_ref(self: &Arc<Self>) {
1394 self.0.fetch_add(1, Ordering::SeqCst);
1395 }
1396 }
1397
1398 let counter = Arc::new(CountingWaker(AtomicUsize::new(0)));
1399 let waker = Waker::from(counter.clone());
1400 let mut cx = TaskContext::from_waker(&waker);
1401
1402 let token = CancellationToken::new();
1403 let mut fut = Box::pin(token.cancelled());
1404
1405 assert_eq!(fut.as_mut().poll(&mut cx), Poll::Pending);
1408 assert_eq!(
1409 counter.0.load(Ordering::SeqCst),
1410 0,
1411 "cancelled future must park, not busy-spin (no self-wake)"
1412 );
1413
1414 token.cancel();
1416 assert!(
1417 counter.0.load(Ordering::SeqCst) >= 1,
1418 "cancel() must wake the parked waiter"
1419 );
1420 assert_eq!(fut.as_mut().poll(&mut cx), Poll::Ready(()));
1421 }
1422
1423 #[test]
1426 fn cancelled_future_ready_when_already_cancelled() {
1427 let waker = std::task::Waker::noop();
1428 let mut cx = TaskContext::from_waker(waker);
1429
1430 let token = CancellationToken::new();
1431 token.cancel();
1432 let mut fut = Box::pin(token.cancelled());
1433 assert_eq!(fut.as_mut().poll(&mut cx), Poll::Ready(()));
1434 }
1435}