mlua_swarm/core/config.rs
1//! EngineCfg + LongHoldConfig.
2
3use std::time::Duration;
4
5fn random_token_secret() -> Vec<u8> {
6 let mut buf = vec![0u8; 32];
7 getrandom::fill(&mut buf).expect("OS RNG unavailable");
8 buf
9}
10
11/// Lock acquisition + max-hold guard configuration.
12#[derive(Debug, Clone)]
13pub struct EngineCfg {
14 /// When `true`, `Engine::with_state` never retries on a busy lock — it
15 /// fails fast with `EngineError::LockBusy` on the very first
16 /// `try_lock` miss instead of backing off and retrying.
17 pub try_only: bool,
18 /// Max number of `try_lock` retries before giving up with
19 /// `EngineError::LockBusyAfterRetry` (ignored when `try_only = true`).
20 pub max_retry: u32,
21 /// Linear backoff step (ms) between retries; the sleep duration for
22 /// attempt `n` is `backoff_ms_step * (n + 1)`.
23 pub backoff_ms_step: u64,
24 /// R4 guard threshold: if a single `with_state` closure holds the lock
25 /// longer than this (ms), `with_state` panics — a signal that a long
26 /// operation leaked inside the lock in violation of the R3 discipline.
27 pub max_hold_ms: u128,
28 /// HMAC secret used by `TokenSigner` to sign/verify `CapToken`s.
29 ///
30 /// `Default` generates this fresh (32 random bytes from the OS RNG) on
31 /// every call when the caller does not supply one — set it explicitly
32 /// for tokens to stay valid across restarts, or when multiple
33 /// independently-constructed engines/signers must accept each other's
34 /// tokens.
35 pub token_secret: Vec<u8>,
36 /// Long-hold session tuning (idle keepalive, heartbeat cadence).
37 pub long_hold: LongHoldConfig,
38 /// Worker recursive spawn depth ceiling (guards against unbounded spawn).
39 ///
40 /// When `Ctx.meta.runtime["spawn_depth"]` has already reached this value
41 /// and a Worker token tries to call `start_task`, the engine raises
42 /// `EngineError::SpawnDepthExceeded`. `0` = root (a task launched
43 /// directly by an Operator); `4` = default, which allows four levels of
44 /// nested sub-tasks.
45 pub max_spawn_depth: u32,
46}
47
48impl EngineCfg {
49 /// Strict variant: `try_only = true` (no retry/backoff) and a tight
50 /// `max_hold_ms = 10`. Useful for tests that want lock contention to
51 /// fail fast rather than silently wait.
52 pub fn strict() -> Self {
53 Self {
54 try_only: true,
55 max_retry: 0,
56 backoff_ms_step: 0,
57 max_hold_ms: 10,
58 ..Self::default()
59 }
60 }
61
62 /// Relaxed variant: higher `max_retry` / `backoff_ms_step` /
63 /// `max_hold_ms` than the default, for environments where lock
64 /// contention is expected to be more frequent or operations slower.
65 pub fn relaxed() -> Self {
66 Self {
67 try_only: false,
68 max_retry: 10,
69 backoff_ms_step: 50,
70 max_hold_ms: 200,
71 ..Self::default()
72 }
73 }
74}
75
76impl Default for EngineCfg {
77 /// Baseline configuration: bounded retry with backoff, a generous
78 /// (but non-zero) `max_hold_ms`, and `max_spawn_depth = 4`.
79 /// `token_secret` is generated fresh per call — see the field doc.
80 fn default() -> Self {
81 Self {
82 try_only: false,
83 max_retry: 3,
84 backoff_ms_step: 10,
85 max_hold_ms: 50,
86 token_secret: random_token_secret(),
87 long_hold: LongHoldConfig::default(),
88 max_spawn_depth: 4,
89 }
90 }
91}
92
93/// Tuning for long-running (suspend/resume-capable) sessions and tasks —
94/// how long a poll/suspend may hold, how often heartbeats are expected,
95/// and whether idle tasks are kept alive across a detach.
96#[derive(Debug, Clone)]
97pub struct LongHoldConfig {
98 /// Default wait duration used by long-poll style waits when the
99 /// caller does not specify one explicitly.
100 pub default_hold: Duration,
101 /// Upper bound on how long a single suspend/poll wait may block.
102 pub max_hold: Duration,
103 /// Expected cadence of `Engine::heartbeat` calls from an attached
104 /// session; consumed by `Engine::start_detach_loop`.
105 pub heartbeat_interval: Duration,
106 /// Number of missed heartbeat intervals tolerated before the detach
107 /// loop flips a session's `attached` flag to `false`.
108 pub heartbeat_miss_threshold: u32,
109 /// When `true`, a task survives (its state is retained) across a
110 /// session detach, so a later reattach can resume it in place.
111 pub keepalive_on_idle: bool,
112}
113
114impl Default for LongHoldConfig {
115 fn default() -> Self {
116 Self {
117 default_hold: Duration::from_secs(3600),
118 max_hold: Duration::from_secs(48 * 3600),
119 heartbeat_interval: Duration::from_secs(300),
120 heartbeat_miss_threshold: 3,
121 keepalive_on_idle: true,
122 }
123 }
124}