Skip to main content

max / makenotwork

server: fix test harness for spawn_pool shutdown-receiver signature The Run 17 Resilience change gave spawn_pool a shutdown receiver and a JoinHandle return; the integration + load test harnesses still called the old no-arg form. Add spawn_pool_detached() (retains the shutdown sender so the pool runs for the test's lifetime) and point the harness AppState/WorkerContext builders at it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-02 17:01 UTC
Commit: 74739f1d84bb6cc78ce66af31e0ed652bc75145d
Parent: 0e17f53
3 files changed, +16 insertions, -3 deletions
@@ -97,6 +97,19 @@ pub fn spawn_pool(mut shutdown: watch::Receiver<()>) -> (BackgroundTx, JoinHandl
97 97 (BackgroundTx { tx }, handle)
98 98 }
99 99
100 + /// Spawn a background pool with no external shutdown wiring, for test harnesses
101 + /// and embeddings that manage lifecycle purely via `AppState` drop. The shutdown
102 + /// sender is retained internally (its `changed()` never fires), so the pool
103 + /// drains only when every `BackgroundTx` clone is dropped and the channel closes.
104 + pub fn spawn_pool_detached() -> BackgroundTx {
105 + let (tx, rx) = watch::channel(());
106 + // Deliberately keep the sender alive so the drainer's shutdown arm never
107 + // fires; the pool then behaves exactly as it did before shutdown draining
108 + // existed (runs until the queue closes). Only reachable off the request path.
109 + std::mem::forget(tx);
110 + spawn_pool(rx).0
111 + }
112 +
100 113 /// Acquire a permit and spawn `task`, releasing the permit when it finishes.
101 114 async fn run_task(sem: &Arc<Semaphore>, task: BoxFuture) {
102 115 let Ok(permit) = sem.clone().acquire_owned().await else {
@@ -400,7 +400,7 @@ impl TestHarness {
400 400 makenotwork::constants::GIT_SMART_HTTP_MAX_CONCURRENT,
401 401 )),
402 402 page_view_tx: makenotwork::db::page_views::spawn_batcher(pool.clone()),
403 - bg: makenotwork::background::spawn_pool(),
403 + bg: makenotwork::background::spawn_pool_detached(),
404 404 };
405 405
406 406 // Capture scan deps before `build_app` consumes `state`.
@@ -547,7 +547,7 @@ impl TestHarness {
547 547 pipeline: deps.pipeline.clone(),
548 548 scan_semaphore: deps.semaphore.clone(),
549 549 wam: None,
550 - bg: makenotwork::background::spawn_pool(),
550 + bg: makenotwork::background::spawn_pool_detached(),
551 551 // No Cloudflare purge in tests; quarantine still deletes from origin.
552 552 cloudflare: None,
553 553 cdn_base_url: None,
@@ -138,7 +138,7 @@ pub async fn run(config: LoadConfig) {
138 138 makenotwork::constants::GIT_SMART_HTTP_MAX_CONCURRENT,
139 139 )),
140 140 page_view_tx: makenotwork::db::page_views::spawn_batcher(pool.clone()),
141 - bg: makenotwork::background::spawn_pool(),
141 + bg: makenotwork::background::spawn_pool_detached(),
142 142 };
143 143
144 144 let app = build_app(state, session_layer);