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}