Skip to content

Commit e743bed

Browse files
committed
moar tests
1 parent 515c72c commit e743bed

1 file changed

Lines changed: 136 additions & 1 deletion

File tree

lib/saluki-core/src/runtime/supervisor.rs

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,7 @@ mod tests {
11681168
init_behavior: InitBehavior,
11691169
run_behavior: RunBehavior,
11701170
start_count: Arc<AtomicUsize>,
1171+
brutal_shutdown: bool,
11711172
}
11721173

11731174
impl MockWorker {
@@ -1178,6 +1179,7 @@ mod tests {
11781179
init_behavior: InitBehavior::Instant,
11791180
run_behavior: RunBehavior::UntilShutdown,
11801181
start_count: Arc::new(AtomicUsize::new(0)),
1182+
brutal_shutdown: false,
11811183
}
11821184
}
11831185

@@ -1188,6 +1190,7 @@ mod tests {
11881190
init_behavior: InitBehavior::Instant,
11891191
run_behavior: RunBehavior::FailAfter(delay, "worker failed"),
11901192
start_count: Arc::new(AtomicUsize::new(0)),
1193+
brutal_shutdown: false,
11911194
}
11921195
}
11931196

@@ -1198,6 +1201,7 @@ mod tests {
11981201
init_behavior: InitBehavior::Instant,
11991202
run_behavior: RunBehavior::CompleteAfter(delay),
12001203
start_count: Arc::new(AtomicUsize::new(0)),
1204+
brutal_shutdown: false,
12011205
}
12021206
}
12031207

@@ -1208,6 +1212,7 @@ mod tests {
12081212
init_behavior: InitBehavior::Instant,
12091213
run_behavior: RunBehavior::SlowShutdown(delay),
12101214
start_count: Arc::new(AtomicUsize::new(0)),
1215+
brutal_shutdown: false,
12111216
}
12121217
}
12131218

@@ -1218,6 +1223,7 @@ mod tests {
12181223
init_behavior: InitBehavior::Instant,
12191224
run_behavior: RunBehavior::IgnoreShutdown,
12201225
start_count: Arc::new(AtomicUsize::new(0)),
1226+
brutal_shutdown: false,
12211227
}
12221228
}
12231229

@@ -1228,6 +1234,7 @@ mod tests {
12281234
init_behavior: InitBehavior::Instant,
12291235
run_behavior: RunBehavior::PanicAfter(delay),
12301236
start_count: Arc::new(AtomicUsize::new(0)),
1237+
brutal_shutdown: false,
12311238
}
12321239
}
12331240

@@ -1238,6 +1245,7 @@ mod tests {
12381245
init_behavior: InitBehavior::Fail("init failed"),
12391246
run_behavior: RunBehavior::UntilShutdown,
12401247
start_count: Arc::new(AtomicUsize::new(0)),
1248+
brutal_shutdown: false,
12411249
}
12421250
}
12431251

@@ -1248,13 +1256,20 @@ mod tests {
12481256
init_behavior: InitBehavior::Slow(init_delay),
12491257
run_behavior: RunBehavior::UntilShutdown,
12501258
start_count: Arc::new(AtomicUsize::new(0)),
1259+
brutal_shutdown: false,
12511260
}
12521261
}
12531262

12541263
/// Returns a shared handle to the start count for this worker.
12551264
fn start_count(&self) -> Arc<AtomicUsize> {
12561265
Arc::clone(&self.start_count)
12571266
}
1267+
1268+
/// Configures this worker to use a `Brutal` shutdown strategy (immediate abort, no graceful wait).
1269+
fn with_brutal_shutdown(mut self) -> Self {
1270+
self.brutal_shutdown = true;
1271+
self
1272+
}
12581273
}
12591274

12601275
#[async_trait]
@@ -1264,7 +1279,11 @@ mod tests {
12641279
}
12651280

12661281
fn shutdown_strategy(&self) -> ShutdownStrategy {
1267-
ShutdownStrategy::Graceful(Duration::from_millis(500))
1282+
if self.brutal_shutdown {
1283+
ShutdownStrategy::Brutal
1284+
} else {
1285+
ShutdownStrategy::Graceful(Duration::from_millis(500))
1286+
}
12681287
}
12691288

12701289
async fn initialize(&self, process_shutdown: ShutdownHandle) -> Result<SupervisorFuture, InitializationError> {
@@ -1521,6 +1540,39 @@ mod tests {
15211540
);
15221541
}
15231542

1543+
#[tokio::test]
1544+
async fn transient_abnormal_exit_triggers_one_for_all() {
1545+
// A transient child's *own* abnormal exit is restartable, so under one-for-all it triggers a whole-group
1546+
// restart -- the sibling is restarted too, not just the transient.
1547+
let transient = MockWorker::failing("transient-worker", Duration::from_millis(50));
1548+
let transient_count = transient.start_count();
1549+
1550+
let stable = MockWorker::long_running("stable-worker");
1551+
let stable_count = stable.start_count();
1552+
1553+
let mut sup = Supervisor::new("test-sup").unwrap().with_restart_strategy(
1554+
RestartStrategy::one_for_all().with_intensity_and_period(20, Duration::from_secs(10)),
1555+
);
1556+
sup.add_worker(ChildSpecification::worker(transient).with_restart_type(RestartType::Transient));
1557+
sup.add_worker(stable);
1558+
1559+
let (tx, handle) = run_supervisor_with_trigger(sup).await;
1560+
1561+
sleep(Duration::from_millis(300)).await;
1562+
let _ = tx.send(());
1563+
1564+
let result = timeout(Duration::from_secs(2), handle).await.unwrap().unwrap();
1565+
assert!(result.is_ok());
1566+
assert!(
1567+
transient_count.load(Ordering::SeqCst) >= 2,
1568+
"transient worker must be restarted after its own abnormal exit"
1569+
);
1570+
assert!(
1571+
stable_count.load(Ordering::SeqCst) >= 2,
1572+
"the transient's abnormal exit must trigger a one-for-all that also restarts the sibling"
1573+
);
1574+
}
1575+
15241576
#[tokio::test]
15251577
async fn restart_limit_exceeded_shuts_down_supervisor() {
15261578
let mut sup = Supervisor::new("test-sup")
@@ -1686,6 +1738,47 @@ mod tests {
16861738
}
16871739
}
16881740

1741+
#[tokio::test]
1742+
async fn transient_clean_exits_do_not_consume_restart_intensity() {
1743+
// With intensity=1, two *restartable* exits within the period would shut the supervisor down. Here several
1744+
// transient workers all complete cleanly. A transient child's clean exit isn't eligible for restart, so it
1745+
// must not consume the restart-intensity budget, and the supervisor must stay up.
1746+
let mut sup = Supervisor::new("test-sup")
1747+
.unwrap()
1748+
.with_restart_strategy(RestartStrategy::one_to_one().with_intensity_and_period(1, Duration::from_secs(10)));
1749+
1750+
let workers = [
1751+
MockWorker::completing("transient-0", Duration::from_millis(20)),
1752+
MockWorker::completing("transient-1", Duration::from_millis(20)),
1753+
MockWorker::completing("transient-2", Duration::from_millis(20)),
1754+
MockWorker::completing("transient-3", Duration::from_millis(20)),
1755+
MockWorker::completing("transient-4", Duration::from_millis(20)),
1756+
];
1757+
let counts: Vec<_> = workers.iter().map(|w| w.start_count()).collect();
1758+
for worker in workers {
1759+
sup.add_worker(ChildSpecification::worker(worker).with_restart_type(RestartType::Transient));
1760+
}
1761+
// A long-running worker so the supervisor doesn't simply idle once the transients have completed.
1762+
sup.add_worker(MockWorker::long_running("stable-worker"));
1763+
1764+
let (tx, handle) = run_supervisor_with_trigger(sup).await;
1765+
sleep(Duration::from_millis(300)).await;
1766+
let _ = tx.send(());
1767+
1768+
let result = timeout(Duration::from_secs(2), handle).await.unwrap().unwrap();
1769+
assert!(
1770+
result.is_ok(),
1771+
"supervisor must not trip its restart limit on clean transient exits"
1772+
);
1773+
for count in counts {
1774+
assert_eq!(
1775+
count.load(Ordering::SeqCst),
1776+
1,
1777+
"each transient worker runs exactly once"
1778+
);
1779+
}
1780+
}
1781+
16891782
#[tokio::test]
16901783
async fn supervisor_idles_when_all_temporary_children_exit() {
16911784
// When every child is temporary and they all exit, the worker set drains. The supervisor must not panic or exit
@@ -2146,4 +2239,46 @@ mod tests {
21462239
"stuck child must be aborted at the deadline (took {elapsed:?})"
21472240
);
21482241
}
2242+
2243+
#[tokio::test]
2244+
async fn ordered_shutdown_aborts_unresponsive_child() {
2245+
// Under the default `ShutdownMode::Ordered`, a child that never reacts to shutdown must be aborted once its
2246+
// graceful deadline (500ms) elapses, rather than hanging the supervisor.
2247+
let mut sup = Supervisor::new("test-sup").unwrap();
2248+
sup.add_worker(MockWorker::ignore_shutdown("stuck"));
2249+
2250+
let (tx, handle) = run_supervisor_with_trigger(sup).await;
2251+
2252+
let start = std::time::Instant::now();
2253+
tx.send(()).unwrap();
2254+
let result = timeout(Duration::from_secs(2), handle).await.unwrap().unwrap();
2255+
let elapsed = start.elapsed();
2256+
2257+
assert!(result.is_ok());
2258+
assert!(
2259+
elapsed < Duration::from_secs(1),
2260+
"unresponsive child must be aborted at its deadline under ordered shutdown (took {elapsed:?})"
2261+
);
2262+
}
2263+
2264+
#[tokio::test]
2265+
async fn brutal_shutdown_aborts_child_immediately() {
2266+
// A child with a `Brutal` shutdown strategy is aborted at once on shutdown, with no graceful wait -- so even a
2267+
// child that ignores shutdown is torn down promptly rather than after the graceful deadline.
2268+
let mut sup = Supervisor::new("test-sup").unwrap();
2269+
sup.add_worker(MockWorker::ignore_shutdown("brutal-stuck").with_brutal_shutdown());
2270+
2271+
let (tx, handle) = run_supervisor_with_trigger(sup).await;
2272+
2273+
let start = std::time::Instant::now();
2274+
tx.send(()).unwrap();
2275+
let result = timeout(Duration::from_secs(2), handle).await.unwrap().unwrap();
2276+
let elapsed = start.elapsed();
2277+
2278+
assert!(result.is_ok());
2279+
assert!(
2280+
elapsed < Duration::from_millis(200),
2281+
"brutal-shutdown child must be aborted immediately, not after a graceful wait (took {elapsed:?})"
2282+
);
2283+
}
21492284
}

0 commit comments

Comments
 (0)