Skip to main content

ares_server/
supervisor.rs

1//! Daemon-side half of the supervised-worker protocol.
2//!
3//! A server started under [`supervise`] does its real work in a child copy
4//! of the same executable. The child carries the [`CHILD_ENV_MARKER`]
5//! environment variable, so the worker-side stdin watcher inside the child
6//! knows it is supervised and can watch standard input to detect daemon
7//! death.
8//!
9//! Protocol summary:
10//!
11//! - Exit code [`EXIT_RESTART`] asks the loop to start a fresh child.
12//! - Exit code [`EXIT_QUIT`] and every other terminal status end the loop.
13//! - Exit code [`EXIT_BOOT`] reports a failed boot. The loop treats it like
14//!   any normal terminal status and returns `Ok`; the caller mirrors the
15//!   real child exit code to its own process exit, so the service manager
16//!   still sees the non-zero failure.
17//!
18//! [`spawn_self_supervised`] creates the child and hands back the write end
19//! of its standard input. Dropping that handle closes the pipe, the child's
20//! standard input reaches end-of-file, and the worker-side watcher performs
21//! a graceful teardown.
22//!
23//! Two safeguards keep the daemon responsive. Respawns after runs too short
24//! to prove health pace themselves exponentially (`next_backoff`: 100 ms
25//! doubling to a 5 s cap) instead of hammering at full speed, and a worker
26//! that has been asked to stop but ignores the request is force-killed once
27//! [`WORKER_SHUTDOWN_GRACE`] elapses ([`wait_with_grace`]).
28
29use std::future::Future;
30use std::io;
31use std::time::Duration;
32
33/// Environment variable that marks a supervised child process.
34///
35/// Same literal as the worker-side `SUPERVISED_ENV` constant. Presence
36/// alone matters; the value is ignored.
37pub const CHILD_ENV_MARKER: &str = "CORDIS_SUPERVISED";
38
39/// Child exit code: restart me with a fresh process.
40pub const EXIT_RESTART: i32 = 51;
41
42/// Child exit code: shut down for good.
43pub const EXIT_QUIT: i32 = 52;
44
45/// Child exit code: boot failed; do not restart.
46pub const EXIT_BOOT: i32 = 53;
47
48/// Time window checked by the rapid-restart guard.
49const RAPID_RESTART_WINDOW: Duration = Duration::from_secs(30);
50
51/// Number of restarts inside [`RAPID_RESTART_WINDOW`] that stops the loop.
52const RAPID_RESTART_LIMIT: usize = 5;
53
54/// A child run that outlived this duration before exiting counts as a
55/// healthy cadence: the restart ladder resets so the next crash sequence
56/// starts from zero instead of inheriting stale strikes.
57const HEALTHY_RUN_DURATION: Duration = Duration::from_secs(10 * 60);
58
59/// A child run that exits sooner than this never proved the worker could
60/// serve, so it counts as unhealthy: the next respawn is delayed by
61/// `next_backoff`.
62const UNHEALTHY_RUN_DURATION: Duration = Duration::from_secs(10);
63
64/// Delay before the first respawn that follows an unhealthy run; each
65/// further consecutive unhealthy run doubles it, up to `BACKOFF_MAX_DELAY`.
66const BACKOFF_INITIAL_DELAY: Duration = Duration::from_millis(100);
67
68/// Upper bound for the exponentially growing respawn delay.
69const BACKOFF_MAX_DELAY: Duration = Duration::from_secs(5);
70
71/// Grace window granted to a worker that has been asked to stop (its
72/// standard input reached end-of-file) before the daemon force-kills it.
73/// Bounds the goodbye, never the working lifetime.
74pub const WORKER_SHUTDOWN_GRACE: Duration = Duration::from_secs(10);
75
76/// Waits for `child` to exit, granting it [`WORKER_SHUTDOWN_GRACE`] once its
77/// standard input has been dropped (the stop request). A worker that exits
78/// within the window yields its real code; one that outstays the grace is
79/// force-killed and the resulting status supplies the code instead, so a
80/// hung child can never park the daemon forever.
81///
82/// Callers must already have released the child's stdin handle: the grace
83/// bounds the goodbye, not the working lifetime.
84pub async fn wait_with_grace(child: &mut tokio::process::Child) -> Option<i32> {
85    match tokio::time::timeout(WORKER_SHUTDOWN_GRACE, child.wait()).await {
86        Ok(Ok(status)) => status.code(),
87        // Wait error (polling failure): nothing more to reap.
88        Ok(Err(_)) => None,
89        // Grace elapsed while the child ignored EOF: kill it and take the
90        // code from the forced-exit status.
91        Err(_) => {
92            tracing::warn!("worker exceeded shutdown grace, killed");
93            let _ = child.kill().await;
94            child.wait().await.ok().and_then(|s| s.code())
95        }
96    }
97}
98
99/// Respawn delay after `consecutive_unhealthy` runs that each exited before
100/// [`UNHEALTHY_RUN_DURATION`]: 100 ms doubling per strike, capped at 5 s —
101/// 100 ms, 200 ms, 400 ms, 800 ms, 1.6 s, 3.2 s, 5 s, 5 s, ...
102fn next_backoff(consecutive_unhealthy: u32) -> Duration {
103    let shift = consecutive_unhealthy.min(16);
104    BACKOFF_INITIAL_DELAY
105        .checked_mul(1u32 << shift)
106        .unwrap_or(BACKOFF_MAX_DELAY)
107        .min(BACKOFF_MAX_DELAY)
108}
109
110/// Wall-clock milliseconds since the Unix epoch; the loop's single time
111/// source. Tests override it via [`NOW_OVERRIDE`] to simulate long-lived
112/// children without sleeping.
113fn now() -> u64 {
114    #[cfg(test)]
115    if let Some(ms) = NOW_OVERRIDE.lock().clone() {
116        return ms;
117    }
118    std::time::SystemTime::now()
119        .duration_since(std::time::UNIX_EPOCH)
120        .map(|d| d.as_millis() as u64)
121        .unwrap_or(0)
122}
123
124#[cfg(test)]
125static NOW_OVERRIDE: parking_lot::Mutex<Option<u64>> = parking_lot::Mutex::new(None);
126
127/// Respawn delays requested by [`supervise`], recorded so tests assert the
128/// pacing instead of measuring slept wall-clock time. Guarded by the tests'
129/// `ENV_LOCK`: every `supervise` caller holds it, so mutations serialise.
130#[cfg(test)]
131static BACKOFF_DELAYS: parking_lot::Mutex<Vec<Duration>> = parking_lot::Mutex::new(Vec::new());
132
133/// A running child together with the write end of its standard input.
134///
135/// # Lifetime contract
136///
137/// Hold [`stdin`](SupervisedChild::stdin) for as long as the child should
138/// live. Dropping it closes the pipe: the child sees end-of-file on its
139/// standard input, and the worker-side watcher tears the child down
140/// gracefully. The drop alone does not kill the child; the shutdown path
141/// relies on the pipe close.
142pub struct SupervisedChild {
143    /// The running child process.
144    pub child: std::process::Child,
145    /// Write end of the child's standard input pipe.
146    pub stdin: std::process::ChildStdin,
147}
148
149/// Returns true when this process itself runs as a supervised child.
150///
151/// Children never self-supervise; see [`supervise`].
152pub fn is_supervised() -> bool {
153    std::env::var_os(CHILD_ENV_MARKER).is_some()
154}
155
156/// Runs the restart loop around `run_child`.
157///
158/// `run_child` starts one child run and yields its exit code as
159/// `Option<i32>`: `Some(code)` for a known code, `None` when the status
160/// carries no code (death by signal, for example). Translate
161/// [`std::process::ExitStatus`] with [`std::process::ExitStatus::code`] and
162/// pass its result straight through; `None` behaves like a normal terminal
163/// status and ends the loop.
164///
165/// Behaviour:
166///
167/// - When this process is already a supervised child, nested supervision is
168///   refused: the function returns `Ok` at once and never calls
169///   `run_child`.
170/// - [`EXIT_RESTART`] respawns the child.
171/// - Every other outcome ends the loop with `Ok`: [`EXIT_QUIT`],
172///   [`EXIT_BOOT`], any other code, and unknown statuses. [`EXIT_BOOT`]
173///   therefore surfaces as an ordinary return; the caller should exit with
174///   the child's real code so the service manager observes the failure.
175/// - [`RAPID_RESTART_LIMIT`] restarts packed inside
176///   [`RAPID_RESTART_WINDOW`] (a plugin that crashes at boot, for example)
177///   stop the loop with an error instead of spinning.
178/// - A child that ran for at least [`HEALTHY_RUN_DURATION`] before exiting
179///   clears the accumulated restart ladder first: a long-lived run proves a
180///   healthy cadence, so old strikes never doom the fresh process.
181/// - Respawn after a run shorter than [`UNHEALTHY_RUN_DURATION`] is delayed
182///   by [`next_backoff`] (100 ms doubling per consecutive unhealthy run,
183///   capped at 5 s); any run at or beyond the healthy threshold resets the
184///   delay to the first step. The pacing counter and the rapid-restart
185///   ladder share one health definition.
186pub async fn supervise<F, Fut>(run_child: F) -> Result<(), io::Error>
187where
188    F: Fn() -> Fut,
189    Fut: Future<Output = Option<i32>>,
190{
191    if is_supervised() {
192        return Ok(());
193    }
194
195    // Restart ladder: wall-clock milliseconds (via [`now`]) of the last
196    // respawns. Milliseconds keep the arithmetic testable through the clock
197    // seam without `Instant` subtraction.
198    let mut restarts: Vec<u64> = Vec::new();
199    // When the current child run started; compared against [`now`] at exit
200    // to detect a healthy long-lived run.
201    let mut spawned_at = now();
202
203    // Consecutive runs that each ended inside [`UNHEALTHY_RUN_DURATION`];
204    // drives respawn pacing through [`next_backoff`] until a run proves
205    // healthy again.
206    let mut consecutive_unhealthy: u32 = 0;
207
208    loop {
209        let code = run_child().await;
210        let ran_for = now().saturating_sub(spawned_at);
211        let healthy_run = ran_for >= UNHEALTHY_RUN_DURATION.as_millis() as u64;
212        if healthy_run {
213            consecutive_unhealthy = 0;
214        }
215
216        match code {
217            Some(EXIT_RESTART) => {}
218            // EXIT_QUIT, EXIT_BOOT, other codes, and unknown statuses all
219            // end the loop. The caller mirrors the child's real exit code,
220            // so EXIT_BOOT still reaches the service manager as a failure.
221            _ => return Ok(()),
222        }
223
224        // A run that outlived [`HEALTHY_RUN_DURATION`] proves the cadence is
225        // healthy: stale strikes say nothing about the fresh process, so the
226        // ladder starts over.
227        if ran_for >= HEALTHY_RUN_DURATION.as_millis() as u64 {
228            restarts.clear();
229            tracing::info!(
230                ran_for_ms = ran_for,
231                "supervisor: long-lived worker exited cleanly; restart backoff reset"
232            );
233        }
234
235        // Pace respawns after unhealthy runs so a crash-loop burns time
236        // exponentially instead of respawning at full speed until the
237        // rapid-restart cap trips.
238        if !healthy_run {
239            let delay = next_backoff(consecutive_unhealthy);
240            tracing::warn!(
241                delay_ms = delay.as_millis() as u64,
242                consecutive_unhealthy,
243                "supervisor: worker exited before proving health; backing off before respawn"
244            );
245            #[cfg(test)]
246            BACKOFF_DELAYS.lock().push(delay);
247            #[cfg(not(test))]
248            tokio::time::sleep(delay).await;
249            consecutive_unhealthy += 1;
250        }
251
252        let stamp = now();
253        restarts.retain(|at| stamp.saturating_sub(*at) < RAPID_RESTART_WINDOW.as_millis() as u64);
254        restarts.push(stamp);
255        if restarts.len() >= RAPID_RESTART_LIMIT {
256            return Err(io::Error::new(
257                io::ErrorKind::InvalidData,
258                "rapid restart loop detected",
259            ));
260        }
261        spawned_at = now();
262    }
263}
264
265/// Re-execs the current executable as a supervised child.
266///
267/// The child inherits every command-line argument after the program name,
268/// the whole environment, and the parent's standard output and standard
269/// error. Two things change:
270///
271/// - [`CHILD_ENV_MARKER`] is set, so the child knows it is supervised and
272///   its stdin watcher can detect daemon death.
273/// - Standard input becomes a pipe whose write end is returned inside the
274///   [`SupervisedChild`] handle.
275///
276/// Drop the [`SupervisedChild::stdin`] handle when the child should stop;
277/// see the lifetime contract on [`SupervisedChild`].
278pub fn spawn_self_supervised() -> Result<SupervisedChild, io::Error> {
279    use std::process::{Command, Stdio};
280
281    let exe = std::env::current_exe()?;
282    let mut command = Command::new(exe);
283    command
284        .args(std::env::args_os().skip(1))
285        .env(CHILD_ENV_MARKER, "1")
286        .stdin(Stdio::piped())
287        .stdout(Stdio::inherit())
288        .stderr(Stdio::inherit());
289
290    let mut child = command.spawn()?;
291    let stdin = child.stdin.take().ok_or_else(|| {
292        io::Error::new(
293            io::ErrorKind::InvalidInput,
294            "child standard input pipe was not created",
295        )
296    })?;
297
298    Ok(SupervisedChild { child, stdin })
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304
305    use std::sync::atomic::{AtomicUsize, Ordering};
306    use std::sync::Arc;
307
308    /// Serialises every test that touches the process-wide environment.
309    ///
310    /// A Tokio mutex because the guard is deliberately held across
311    /// `supervise(...)` await points.
312    static ENV_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
313
314    /// Status provider for [`supervise`]: serves one queued status per
315    /// call and records how often it ran.
316    #[derive(Clone)]
317    struct Script {
318        statuses: Arc<Vec<Option<i32>>>,
319        calls: Arc<AtomicUsize>,
320    }
321
322    impl Script {
323        fn new(statuses: Vec<Option<i32>>) -> Self {
324            Self {
325                statuses: Arc::new(statuses),
326                calls: Arc::new(AtomicUsize::new(0)),
327            }
328        }
329
330        fn calls(&self) -> usize {
331            self.calls.load(Ordering::SeqCst)
332        }
333
334        /// One child run: hand out the next queued status. Exhausted
335        /// scripts report a clean exit so loops cannot spin past the queue.
336        async fn step(self) -> Option<i32> {
337            let nth = self.calls.fetch_add(1, Ordering::SeqCst);
338            match self.statuses.get(nth) {
339                Some(code) => *code,
340                None => Some(0),
341            }
342        }
343    }
344
345    #[tokio::test]
346    async fn child_exit_codes_drive_loop() {
347        let _guard = ENV_LOCK.lock().await;
348        std::env::remove_var(CHILD_ENV_MARKER);
349
350        let script = Script::new(vec![Some(EXIT_RESTART), Some(0)]);
351        let result = supervise(|| script.clone().step()).await;
352
353        assert!(result.is_ok());
354        assert_eq!(script.calls(), 2);
355    }
356
357    #[tokio::test]
358    async fn rapid_restart_cap_trips() {
359        let _guard = ENV_LOCK.lock().await;
360        std::env::remove_var(CHILD_ENV_MARKER);
361
362        let script = Script::new(vec![Some(EXIT_RESTART); RAPID_RESTART_LIMIT]);
363        let result = supervise(|| script.clone().step()).await;
364
365        let err = result.expect_err("five rapid restarts must stop the loop");
366        assert!(err.to_string().contains("rapid restart loop"));
367        assert_eq!(script.calls(), RAPID_RESTART_LIMIT);
368    }
369
370    /// Scripted statuses plus scripted exit timestamps, driving the [`now`]
371    /// clock seam so child-run durations are simulated without sleeping.
372    #[derive(Clone)]
373    struct ClockScript {
374        statuses: Arc<Vec<Option<i32>>>,
375        /// Fake wall-clock milliseconds at which each run exits.
376        exits: Arc<Vec<u64>>,
377        calls: Arc<AtomicUsize>,
378    }
379
380    impl ClockScript {
381        fn new(statuses: Vec<Option<i32>>, exits: Vec<u64>) -> Self {
382            Self {
383                statuses: Arc::new(statuses),
384                exits: Arc::new(exits),
385                calls: Arc::new(AtomicUsize::new(0)),
386            }
387        }
388
389        fn calls(&self) -> usize {
390            self.calls.load(Ordering::SeqCst)
391        }
392
393        async fn step(self) -> Option<i32> {
394            let nth = self.calls.fetch_add(1, Ordering::SeqCst);
395            // This run "finishes" at its scripted exit time.
396            if let Some(at) = self.exits.get(nth) {
397                *NOW_OVERRIDE.lock() = Some(*at);
398            }
399            match self.statuses.get(nth) {
400                Some(code) => *code,
401                None => Some(0),
402            }
403        }
404    }
405
406    /// Three sub-second crash loops, then a worker that outlived
407    /// [`HEALTHY_RUN_DURATION`]: the accumulated ladder clears, so two more
408    /// rapid crashes stay under the cap instead of tripping it.
409    #[tokio::test]
410    async fn long_lived_run_resets_rapid_restart_ladder() {
411        let _guard = ENV_LOCK.lock().await;
412        std::env::remove_var(CHILD_ENV_MARKER);
413
414        const HEALTHY_MS: u64 = HEALTHY_RUN_DURATION.as_millis() as u64;
415        const BASE: u64 = 1_000_000_000;
416
417        // Exits: three 1ms-apart crashes, one healthy-length run, then two
418        // more rapid crashes. The exhausted script then reports a clean exit,
419        // ending the loop.
420        let exits = vec![
421            BASE + 1,
422            BASE + 2,
423            BASE + 3,
424            BASE + 3 + HEALTHY_MS,
425            BASE + 3 + HEALTHY_MS + 4,
426        ];
427        let script = ClockScript::new(vec![Some(EXIT_RESTART); 5], exits);
428
429        *NOW_OVERRIDE.lock() = Some(BASE);
430        let result = supervise(|| script.clone().step()).await;
431        *NOW_OVERRIDE.lock() = None;
432
433        assert!(
434            result.is_ok(),
435            "two post-reset crashes must stay under the cap: {result:?}"
436        );
437        // Five queued runs plus the final exhausted-script probe.
438        assert_eq!(script.calls(), 6);
439    }
440
441    /// Counterfactual: the same crash cadence WITHOUT the long-lived run
442    /// still trips the cap — the reset, not the clock seam, changed the
443    /// outcome.
444    #[tokio::test]
445    async fn all_rapid_runs_without_reset_still_trip_cap() {
446        let _guard = ENV_LOCK.lock().await;
447        std::env::remove_var(CHILD_ENV_MARKER);
448
449        const BASE: u64 = 2_000_000_000;
450        let exits: Vec<u64> = (1..=5).map(|i| BASE + i).collect();
451        let script = ClockScript::new(vec![Some(EXIT_RESTART); 5], exits);
452
453        *NOW_OVERRIDE.lock() = Some(BASE);
454        let result = supervise(|| script.clone().step()).await;
455        *NOW_OVERRIDE.lock() = None;
456
457        let err = result.expect_err("five rapid restarts must stop the loop");
458        assert!(err.to_string().contains("rapid restart loop"));
459        assert_eq!(script.calls(), RAPID_RESTART_LIMIT);
460    }
461
462    #[tokio::test]
463    async fn supervised_mode_short_circuits() {
464        let _guard = ENV_LOCK.lock().await;
465        std::env::set_var(CHILD_ENV_MARKER, "1");
466        assert!(is_supervised());
467
468        let script = Script::new(Vec::new());
469        let result = supervise(|| script.clone().step()).await;
470
471        assert!(result.is_ok());
472        assert_eq!(script.calls(), 0);
473
474        std::env::remove_var(CHILD_ENV_MARKER);
475    }
476
477    /// Grace constant is part of the daemon's operational contract: a hung
478    /// worker must never hold the daemon past this window.
479    #[test]
480    fn shutdown_grace_is_ten_seconds() {
481        assert_eq!(WORKER_SHUTDOWN_GRACE, Duration::from_secs(10));
482    }
483
484    /// A child that exits well inside the grace window yields its real exit
485    /// code unchanged — the kill path never fires for cooperative workers.
486    #[tokio::test]
487    async fn wait_with_grace_returns_fast_child_code() {
488        let mut child = tokio::process::Command::new("true")
489            .stdin(std::process::Stdio::null())
490            .spawn()
491            .expect("spawn true");
492        let code = wait_with_grace(&mut child).await;
493        assert_eq!(code, Some(0));
494    }
495
496    /// A child that ignores EOF outstays the grace: it is force-killed and
497    /// the code comes from the forced-exit status (signal death → None).
498    /// Uses a short-lived `sleep` child only to prove the timeout branch is
499    /// reachable; the 10 s wall cost is bounded by the grace itself.
500    #[tokio::test]
501    async fn wait_with_grace_kills_child_after_timeout() {
502        // `cat` with no input would also hang, but `sleep` ignores nothing
503        // we rely on; both work. SIGKILL on Linux yields status.code() ==
504        // None, so the observable outcome is the absence of a code plus the
505        // fact that this returns instead of hanging forever.
506        let mut child = tokio::process::Command::new("sleep")
507            .arg("60")
508            .stdin(std::process::Stdio::null())
509            .stdout(std::process::Stdio::null())
510            .spawn()
511            .expect("spawn sleep");
512        let code = wait_with_grace(&mut child).await;
513        // Killed by SIGKILL: no exit code, but crucially no hang.
514        assert_eq!(code, None);
515    }
516
517    /// Backoff table: first unhealthy respawn waits 100 ms, each further
518    /// consecutive unhealthy run doubles it, capped at 5 s.
519    #[test]
520    fn backoff_doubles_and_caps() {
521        let cases = [
522            (0u32, Duration::from_millis(100)),
523            (1, Duration::from_millis(200)),
524            (2, Duration::from_millis(400)),
525            (3, Duration::from_millis(800)),
526            (4, Duration::from_millis(1600)),
527            (5, Duration::from_millis(3200)),
528            (6, Duration::from_secs(5)),
529            (7, Duration::from_secs(5)),
530            (100, Duration::from_secs(5)),
531            (u32::MAX, Duration::from_secs(5)),
532        ];
533        for (n, expected) in cases {
534            assert_eq!(
535                next_backoff(n),
536                expected,
537                "next_backoff({n}) must be {expected:?}"
538            );
539        }
540    }
541
542    /// A crash sequence through the full loop paces its respawns: delays
543    /// follow the doubling table while runs stay unhealthy, and one healthy
544    /// (HEALTHY_RUN_DURATION-length) run resets the counter so the next
545    /// crash starts over at 100 ms — same reset condition as the ladder.
546    #[tokio::test]
547    async fn healthy_run_resets_backoff_counter() {
548        let _guard = ENV_LOCK.lock().await;
549        std::env::remove_var(CHILD_ENV_MARKER);
550
551        const HEALTHY_MS: u64 = HEALTHY_RUN_DURATION.as_millis() as u64;
552        const BASE: u64 = 3_000_000_000;
553
554        BACKOFF_DELAYS.lock().clear();
555        // Two rapid crashes (delays 100 ms, 200 ms), then a healthy-length
556        // run, then another rapid crash whose delay must restart at 100 ms.
557        // The exhausted script then reports a clean exit ending the loop.
558        const UNHEALTHY_MS: u64 = UNHEALTHY_RUN_DURATION.as_millis() as u64;
559        // Run 4 spawns when run 3 exits (clock = BASE+2+HEALTHY_MS) and must
560        // end one tick BEFORE UNHEALTHY_RUN_DURATION to count as unhealthy.
561        let exits = vec![
562            BASE + 1,
563            BASE + 2,
564            BASE + 2 + HEALTHY_MS,
565            BASE + 2 + HEALTHY_MS + UNHEALTHY_MS - 1,
566        ];
567        let script = ClockScript::new(
568            vec![
569                Some(EXIT_RESTART),
570                Some(EXIT_RESTART),
571                Some(EXIT_RESTART),
572                Some(EXIT_RESTART),
573            ],
574            exits,
575        );
576
577        *NOW_OVERRIDE.lock() = Some(BASE);
578        let result = supervise(|| script.clone().step()).await;
579        *NOW_OVERRIDE.lock() = None;
580
581        assert!(result.is_ok(), "exhausted script ends clean: {result:?}");
582        assert_eq!(
583            *BACKOFF_DELAYS.lock(),
584            vec![
585                Duration::from_millis(100),
586                Duration::from_millis(200),
587                // Healthy-length run: no backoff recorded, counter cleared.
588                // Post-reset crash: first step again.
589                Duration::from_millis(100),
590            ]
591        );
592
593        BACKOFF_DELAYS.lock().clear();
594    }
595}