1use std::sync::Arc;
12
13use serde::{Deserialize, Serialize};
14
15use crate::fs::VirtualFileSystem;
16
17#[derive(Default)]
22pub struct AgentOsConfig {
23 pub software: Vec<SoftwareInput>,
25 pub packages: Vec<PackageRef>,
29 pub packages_mount_at: Option<String>,
32 pub loopback_exempt_ports: Vec<u16>,
34 pub allowed_node_builtins: Option<Vec<String>>,
36 pub module_access_cwd: Option<String>,
38 pub root_filesystem: RootFilesystemConfig,
40 pub mounts: Vec<MountConfig>,
42 pub additional_instructions: Option<String>,
44 pub schedule_driver: Option<Arc<dyn ScheduleDriver>>,
46 pub tool_kits: Vec<ToolKit>,
48 pub sidecar_js_bridge_callback: Option<SidecarJsBridgeCallback>,
50 pub permissions: Option<Permissions>,
52 pub limits: Option<AgentOsLimits>,
54 pub sidecar: Option<AgentOsSidecarConfig>,
56 pub sidecar_binary_path: Option<String>,
61}
62
63#[derive(Default)]
65pub struct AgentOsConfigBuilder {
66 config: AgentOsConfig,
67}
68
69impl AgentOsConfigBuilder {
70 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn packages(mut self, packages: Vec<PackageRef>) -> Self {
75 self.config.packages = packages;
76 self
77 }
78
79 pub fn packages_mount_at(mut self, mount_at: impl Into<String>) -> Self {
80 self.config.packages_mount_at = Some(mount_at.into());
81 self
82 }
83
84 pub fn loopback_exempt_ports(mut self, ports: Vec<u16>) -> Self {
85 self.config.loopback_exempt_ports = ports;
86 self
87 }
88
89 pub fn allowed_node_builtins(mut self, builtins: Vec<String>) -> Self {
90 self.config.allowed_node_builtins = Some(builtins);
91 self
92 }
93
94 pub fn module_access_cwd(mut self, cwd: impl Into<String>) -> Self {
95 self.config.module_access_cwd = Some(cwd.into());
96 self
97 }
98
99 pub fn root_filesystem(mut self, root: RootFilesystemConfig) -> Self {
100 self.config.root_filesystem = root;
101 self
102 }
103
104 pub fn mounts(mut self, mounts: Vec<MountConfig>) -> Self {
105 self.config.mounts = mounts;
106 self
107 }
108
109 pub fn additional_instructions(mut self, instructions: impl Into<String>) -> Self {
110 self.config.additional_instructions = Some(instructions.into());
111 self
112 }
113
114 pub fn schedule_driver(mut self, driver: Arc<dyn ScheduleDriver>) -> Self {
115 self.config.schedule_driver = Some(driver);
116 self
117 }
118
119 pub fn tool_kits(mut self, tool_kits: Vec<ToolKit>) -> Self {
120 self.config.tool_kits = tool_kits;
121 self
122 }
123
124 pub fn sidecar_js_bridge_callback(mut self, callback: SidecarJsBridgeCallback) -> Self {
125 self.config.sidecar_js_bridge_callback = Some(callback);
126 self
127 }
128
129 pub fn permissions(mut self, permissions: Permissions) -> Self {
130 self.config.permissions = Some(permissions);
131 self
132 }
133
134 pub fn limits(mut self, limits: AgentOsLimits) -> Self {
135 self.config.limits = Some(limits);
136 self
137 }
138
139 pub fn sidecar(mut self, sidecar: AgentOsSidecarConfig) -> Self {
140 self.config.sidecar = Some(sidecar);
141 self
142 }
143
144 pub fn sidecar_binary_path(mut self, path: impl Into<String>) -> Self {
145 self.config.sidecar_binary_path = Some(path.into());
146 self
147 }
148
149 pub fn build(self) -> AgentOsConfig {
150 self.config
151 }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
157#[serde(rename_all = "kebab-case")]
158pub enum SoftwareKind {
159 #[default]
162 WasmCommands,
163 Agent,
165 Tool,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171pub struct SoftwareInput {
172 pub package: String,
173 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub version: Option<String>,
175 #[serde(default)]
177 pub kind: SoftwareKind,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
184pub struct PackageRef {
185 pub dir: String,
186}
187
188pub type ToolCallback = Arc<
192 dyn Fn(
193 serde_json::Value,
194 ) -> futures::future::BoxFuture<'static, Result<serde_json::Value, String>>
195 + Send
196 + Sync,
197>;
198
199#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct SidecarJsBridgeCall {
202 pub call_id: String,
203 pub mount_id: String,
204 pub operation: String,
205 pub args: serde_json::Value,
206}
207
208pub type SidecarJsBridgeCallback = Arc<
214 dyn Fn(
215 SidecarJsBridgeCall,
216 )
217 -> futures::future::BoxFuture<'static, Result<Option<serde_json::Value>, String>>
218 + Send
219 + Sync,
220>;
221
222#[derive(Clone)]
224pub struct HostTool {
225 pub name: String,
226 pub description: String,
227 pub input_schema: serde_json::Value,
229 pub timeout_ms: Option<u64>,
230 pub execute: ToolCallback,
232}
233
234#[derive(Clone)]
238pub struct ToolKit {
239 pub name: String,
240 pub description: String,
241 pub tools: Vec<HostTool>,
242}
243
244#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
251pub struct AgentOsLimits {
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 pub resources: Option<ResourceLimits>,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
255 pub http: Option<HttpLimits>,
256 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub tools: Option<ToolLimits>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 pub plugins: Option<PluginLimits>,
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub acp: Option<AcpLimits>,
262 #[serde(default, rename = "jsRuntime", skip_serializing_if = "Option::is_none")]
263 pub js_runtime: Option<JsRuntimeLimits>,
264 #[serde(default, skip_serializing_if = "Option::is_none")]
265 pub python: Option<PythonLimits>,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
267 pub wasm: Option<WasmLimits>,
268}
269
270#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
271pub struct ResourceLimits {
272 #[serde(default, rename = "cpuCount", skip_serializing_if = "Option::is_none")]
273 pub cpu_count: Option<u64>,
274 #[serde(
275 default,
276 rename = "maxProcesses",
277 skip_serializing_if = "Option::is_none"
278 )]
279 pub max_processes: Option<u64>,
280 #[serde(
281 default,
282 rename = "maxOpenFds",
283 skip_serializing_if = "Option::is_none"
284 )]
285 pub max_open_fds: Option<u64>,
286 #[serde(default, rename = "maxPipes", skip_serializing_if = "Option::is_none")]
287 pub max_pipes: Option<u64>,
288 #[serde(default, rename = "maxPtys", skip_serializing_if = "Option::is_none")]
289 pub max_ptys: Option<u64>,
290 #[serde(
291 default,
292 rename = "maxSockets",
293 skip_serializing_if = "Option::is_none"
294 )]
295 pub max_sockets: Option<u64>,
296 #[serde(
297 default,
298 rename = "maxConnections",
299 skip_serializing_if = "Option::is_none"
300 )]
301 pub max_connections: Option<u64>,
302 #[serde(
303 default,
304 rename = "maxSocketBufferedBytes",
305 skip_serializing_if = "Option::is_none"
306 )]
307 pub max_socket_buffered_bytes: Option<u64>,
308 #[serde(
309 default,
310 rename = "maxSocketDatagramQueueLen",
311 skip_serializing_if = "Option::is_none"
312 )]
313 pub max_socket_datagram_queue_len: Option<u64>,
314 #[serde(
315 default,
316 rename = "maxFilesystemBytes",
317 skip_serializing_if = "Option::is_none"
318 )]
319 pub max_filesystem_bytes: Option<u64>,
320 #[serde(
321 default,
322 rename = "maxInodeCount",
323 skip_serializing_if = "Option::is_none"
324 )]
325 pub max_inode_count: Option<u64>,
326 #[serde(
327 default,
328 rename = "maxBlockingReadMs",
329 skip_serializing_if = "Option::is_none"
330 )]
331 pub max_blocking_read_ms: Option<u64>,
332 #[serde(
333 default,
334 rename = "maxPreadBytes",
335 skip_serializing_if = "Option::is_none"
336 )]
337 pub max_pread_bytes: Option<u64>,
338 #[serde(
339 default,
340 rename = "maxFdWriteBytes",
341 skip_serializing_if = "Option::is_none"
342 )]
343 pub max_fd_write_bytes: Option<u64>,
344 #[serde(
345 default,
346 rename = "maxProcessArgvBytes",
347 skip_serializing_if = "Option::is_none"
348 )]
349 pub max_process_argv_bytes: Option<u64>,
350 #[serde(
351 default,
352 rename = "maxProcessEnvBytes",
353 skip_serializing_if = "Option::is_none"
354 )]
355 pub max_process_env_bytes: Option<u64>,
356 #[serde(
357 default,
358 rename = "maxReaddirEntries",
359 skip_serializing_if = "Option::is_none"
360 )]
361 pub max_readdir_entries: Option<u64>,
362 #[serde(
363 default,
364 rename = "maxWasmFuel",
365 skip_serializing_if = "Option::is_none"
366 )]
367 pub max_wasm_fuel: Option<u64>,
368 #[serde(
369 default,
370 rename = "maxWasmMemoryBytes",
371 skip_serializing_if = "Option::is_none"
372 )]
373 pub max_wasm_memory_bytes: Option<u64>,
374 #[serde(
375 default,
376 rename = "maxWasmStackBytes",
377 skip_serializing_if = "Option::is_none"
378 )]
379 pub max_wasm_stack_bytes: Option<u64>,
380}
381
382#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
383pub struct HttpLimits {
384 #[serde(
385 default,
386 rename = "maxFetchResponseBytes",
387 skip_serializing_if = "Option::is_none"
388 )]
389 pub max_fetch_response_bytes: Option<u64>,
390}
391
392#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
393pub struct ToolLimits {
394 #[serde(
395 default,
396 rename = "defaultToolTimeoutMs",
397 skip_serializing_if = "Option::is_none"
398 )]
399 pub default_tool_timeout_ms: Option<u64>,
400 #[serde(
401 default,
402 rename = "maxToolTimeoutMs",
403 skip_serializing_if = "Option::is_none"
404 )]
405 pub max_tool_timeout_ms: Option<u64>,
406 #[serde(
407 default,
408 rename = "maxRegisteredToolkits",
409 skip_serializing_if = "Option::is_none"
410 )]
411 pub max_registered_toolkits: Option<u64>,
412 #[serde(
413 default,
414 rename = "maxRegisteredToolsPerVm",
415 skip_serializing_if = "Option::is_none"
416 )]
417 pub max_registered_tools_per_vm: Option<u64>,
418 #[serde(
419 default,
420 rename = "maxToolsPerToolkit",
421 skip_serializing_if = "Option::is_none"
422 )]
423 pub max_tools_per_toolkit: Option<u64>,
424 #[serde(
425 default,
426 rename = "maxToolSchemaBytes",
427 skip_serializing_if = "Option::is_none"
428 )]
429 pub max_tool_schema_bytes: Option<u64>,
430 #[serde(
431 default,
432 rename = "maxToolExamplesPerTool",
433 skip_serializing_if = "Option::is_none"
434 )]
435 pub max_tool_examples_per_tool: Option<u64>,
436 #[serde(
437 default,
438 rename = "maxToolExampleInputBytes",
439 skip_serializing_if = "Option::is_none"
440 )]
441 pub max_tool_example_input_bytes: Option<u64>,
442}
443
444#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
445pub struct PluginLimits {
446 #[serde(
447 default,
448 rename = "maxPersistedManifestBytes",
449 skip_serializing_if = "Option::is_none"
450 )]
451 pub max_persisted_manifest_bytes: Option<u64>,
452 #[serde(
453 default,
454 rename = "maxPersistedManifestFileBytes",
455 skip_serializing_if = "Option::is_none"
456 )]
457 pub max_persisted_manifest_file_bytes: Option<u64>,
458}
459
460#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
461pub struct AcpLimits {
462 #[serde(
463 default,
464 rename = "maxReadLineBytes",
465 skip_serializing_if = "Option::is_none"
466 )]
467 pub max_read_line_bytes: Option<u64>,
468 #[serde(
469 default,
470 rename = "stdoutBufferByteLimit",
471 skip_serializing_if = "Option::is_none"
472 )]
473 pub stdout_buffer_byte_limit: Option<u64>,
474}
475
476#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
477pub struct JsRuntimeLimits {
478 #[serde(
479 default,
480 rename = "v8HeapLimitMb",
481 skip_serializing_if = "Option::is_none"
482 )]
483 pub v8_heap_limit_mb: Option<u64>,
484 #[serde(
485 default,
486 rename = "capturedOutputLimitBytes",
487 skip_serializing_if = "Option::is_none"
488 )]
489 pub captured_output_limit_bytes: Option<u64>,
490 #[serde(
491 default,
492 rename = "stdinBufferLimitBytes",
493 skip_serializing_if = "Option::is_none"
494 )]
495 pub stdin_buffer_limit_bytes: Option<u64>,
496 #[serde(
497 default,
498 rename = "eventPayloadLimitBytes",
499 skip_serializing_if = "Option::is_none"
500 )]
501 pub event_payload_limit_bytes: Option<u64>,
502 #[serde(
503 default,
504 rename = "v8IpcMaxFrameBytes",
505 skip_serializing_if = "Option::is_none"
506 )]
507 pub v8_ipc_max_frame_bytes: Option<u64>,
508}
509
510#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
511pub struct PythonLimits {
512 #[serde(
513 default,
514 rename = "outputBufferMaxBytes",
515 skip_serializing_if = "Option::is_none"
516 )]
517 pub output_buffer_max_bytes: Option<u64>,
518 #[serde(
519 default,
520 rename = "executionTimeoutMs",
521 skip_serializing_if = "Option::is_none"
522 )]
523 pub execution_timeout_ms: Option<u64>,
524 #[serde(
525 default,
526 rename = "vfsRpcTimeoutMs",
527 skip_serializing_if = "Option::is_none"
528 )]
529 pub vfs_rpc_timeout_ms: Option<u64>,
530}
531
532#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
533pub struct WasmLimits {
534 #[serde(
535 default,
536 rename = "maxModuleFileBytes",
537 skip_serializing_if = "Option::is_none"
538 )]
539 pub max_module_file_bytes: Option<u64>,
540 #[serde(
541 default,
542 rename = "capturedOutputLimitBytes",
543 skip_serializing_if = "Option::is_none"
544 )]
545 pub captured_output_limit_bytes: Option<u64>,
546 #[serde(
547 default,
548 rename = "syncReadLimitBytes",
549 skip_serializing_if = "Option::is_none"
550 )]
551 pub sync_read_limit_bytes: Option<u64>,
552}
553
554#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
560pub struct Permissions {
561 #[serde(default, skip_serializing_if = "Option::is_none")]
562 pub fs: Option<FsPermissions>,
563 #[serde(default, skip_serializing_if = "Option::is_none")]
564 pub network: Option<PatternPermissions>,
565 #[serde(
566 default,
567 rename = "childProcess",
568 skip_serializing_if = "Option::is_none"
569 )]
570 pub child_process: Option<PatternPermissions>,
571 #[serde(default, skip_serializing_if = "Option::is_none")]
572 pub process: Option<PatternPermissions>,
573 #[serde(default, skip_serializing_if = "Option::is_none")]
574 pub env: Option<PatternPermissions>,
575 #[serde(default, skip_serializing_if = "Option::is_none")]
576 pub binding: Option<PatternPermissions>,
577}
578
579#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
581#[serde(rename_all = "lowercase")]
582pub enum PermissionMode {
583 Allow,
584 Deny,
585}
586
587#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
589#[serde(untagged)]
590pub enum FsPermissions {
591 Mode(PermissionMode),
592 Rules(RulePermissions<FsPermissionRule>),
593}
594
595#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
597#[serde(untagged)]
598pub enum PatternPermissions {
599 Mode(PermissionMode),
600 Rules(RulePermissions<PatternPermissionRule>),
601}
602
603#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
605pub struct RulePermissions<T> {
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub default: Option<PermissionMode>,
608 pub rules: Vec<T>,
609}
610
611#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
613pub struct FsPermissionRule {
614 pub mode: PermissionMode,
615 #[serde(default, skip_serializing_if = "Option::is_none")]
616 pub operations: Option<Vec<String>>,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
618 pub paths: Option<Vec<String>>,
619}
620
621#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
623pub struct PatternPermissionRule {
624 pub mode: PermissionMode,
625 #[serde(default, skip_serializing_if = "Option::is_none")]
626 pub operations: Option<Vec<String>>,
627 #[serde(default, skip_serializing_if = "Option::is_none")]
628 pub patterns: Option<Vec<String>>,
629}
630
631#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
637pub struct RootFilesystemConfig {
638 #[serde(default, rename = "type")]
639 pub kind: RootFilesystemKind,
640 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub mode: Option<RootFilesystemMode>,
642 #[serde(
643 default,
644 rename = "nativePlugin",
645 skip_serializing_if = "Option::is_none"
646 )]
647 pub native_plugin: Option<MountPlugin>,
648 #[serde(default, rename = "disableDefaultBaseLayer")]
649 pub disable_default_base_layer: bool,
650 #[serde(default)]
651 pub lowers: Vec<RootLowerInput>,
652}
653
654impl Default for RootFilesystemConfig {
655 fn default() -> Self {
656 Self {
657 kind: RootFilesystemKind::Overlay,
658 mode: None,
659 native_plugin: None,
660 disable_default_base_layer: false,
661 lowers: Vec::new(),
662 }
663 }
664}
665
666#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
668#[serde(rename_all = "lowercase")]
669pub enum RootFilesystemKind {
670 #[default]
671 Overlay,
672 Native,
673}
674
675#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
677#[serde(rename_all = "kebab-case")]
678pub enum RootFilesystemMode {
679 Ephemeral,
680 ReadOnly,
681}
682
683#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
685#[serde(tag = "kind", rename_all = "kebab-case")]
686pub enum RootLowerInput {
687 BundledBaseFilesystem,
689 #[serde(untagged)]
691 SnapshotExport(crate::fs::RootSnapshotExport),
692}
693
694pub enum MountConfig {
700 Plain {
702 path: String,
703 driver: Arc<dyn VirtualFileSystem>,
704 read_only: bool,
705 },
706 Native {
708 path: String,
709 plugin: MountPlugin,
710 read_only: bool,
711 },
712 Overlay {
714 path: String,
715 filesystem: OverlayMountConfig,
716 },
717}
718
719#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
721pub struct MountPlugin {
722 pub id: String,
723 #[serde(default, skip_serializing_if = "Option::is_none")]
724 pub config: Option<serde_json::Value>,
725}
726
727#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
729pub struct OverlayMountConfig {
730 #[serde(rename = "type")]
731 pub kind: String,
732 pub store: serde_json::Value,
733 #[serde(default, skip_serializing_if = "Option::is_none")]
734 pub mode: Option<RootFilesystemMode>,
735 pub lowers: Vec<RootLowerInput>,
736}
737
738pub enum AgentOsSidecarConfig {
744 Shared { pool: Option<String> },
746 Explicit {
748 handle: Arc<crate::sidecar::AgentOsSidecar>,
749 },
750}
751
752pub type ScheduleCallback = Arc<dyn Fn() -> futures::future::BoxFuture<'static, ()> + Send + Sync>;
762
763#[derive(Clone)]
766pub struct ScheduleEntry {
767 pub id: String,
769 pub schedule: String,
771 pub callback: ScheduleCallback,
773}
774
775pub trait ScheduleDriver: Send + Sync {
781 fn schedule(&self, entry: ScheduleEntry) -> ScheduleHandle;
784
785 fn cancel(&self, handle: &ScheduleHandle);
787
788 fn dispose(&self);
790}
791
792#[derive(Clone)]
795pub struct ScheduleHandle {
796 pub id: String,
797}
798
799#[derive(Default)]
806pub struct TimerScheduleDriver {
807 timers: Arc<scc::HashMap<String, tokio_util::sync::CancellationToken>>,
808}
809
810impl TimerScheduleDriver {
811 pub fn new() -> Self {
812 Self {
813 timers: Arc::new(scc::HashMap::new()),
814 }
815 }
816
817 fn schedule_next(
821 timers: Arc<scc::HashMap<String, tokio_util::sync::CancellationToken>>,
822 entry: ScheduleEntry,
823 cancel: tokio_util::sync::CancellationToken,
824 ) {
825 let now = chrono::Utc::now();
826 let parsed = match crate::cron::parse_schedule(&entry.schedule) {
827 Ok(parsed) => parsed,
828 Err(_) => {
829 let _ = timers.remove(&entry.id);
830 return;
831 }
832 };
833 let is_cron = parsed.is_cron();
834 let next = match crate::cron::resolve_next_run(&parsed, now) {
835 Some(next) => next,
836 None => {
837 let _ = timers.remove(&entry.id);
839 return;
840 }
841 };
842
843 let delay = (next - now).to_std().unwrap_or(std::time::Duration::ZERO);
844
845 tokio::spawn(async move {
846 tokio::select! {
847 _ = cancel.cancelled() => {
848 return;
849 }
850 _ = tokio::time::sleep(delay) => {}
851 }
852 if cancel.is_cancelled() {
853 return;
854 }
855 (entry.callback)().await;
857
858 if is_cron && timers.contains(&entry.id) {
859 Self::schedule_next(Arc::clone(&timers), entry, cancel);
860 } else {
861 let _ = timers.remove(&entry.id);
862 }
863 });
864 }
865}
866
867impl ScheduleDriver for TimerScheduleDriver {
868 fn schedule(&self, entry: ScheduleEntry) -> ScheduleHandle {
869 let id = entry.id.clone();
870 let cancel = tokio_util::sync::CancellationToken::new();
871 if let Some((_, old)) = self.timers.remove(&id) {
873 old.cancel();
874 }
875 let _ = self.timers.insert(id.clone(), cancel.clone());
876
877 Self::schedule_next(Arc::clone(&self.timers), entry, cancel);
878
879 ScheduleHandle { id }
880 }
881
882 fn cancel(&self, handle: &ScheduleHandle) {
883 if let Some((_, cancel)) = self.timers.remove(&handle.id) {
884 cancel.cancel();
885 }
886 }
887
888 fn dispose(&self) {
889 self.timers.scan(|_, cancel| cancel.cancel());
890 self.timers.clear();
891 }
892}