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