| 386 |
386 |
|
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(());
|
| 387 |
387 |
|
let _monitor_handle = makenotwork::monitor::spawn_monitor(state.clone(), shutdown_rx);
|
| 388 |
388 |
|
let scheduler_shutdown_rx = shutdown_tx.subscribe();
|
| 389 |
|
- |
let _scheduler_handle = makenotwork::scheduler::spawn_scheduler(state.clone(), scheduler_shutdown_rx);
|
|
389 |
+ |
let scheduler_handle = makenotwork::scheduler::spawn_scheduler(state.clone(), scheduler_shutdown_rx);
|
| 390 |
390 |
|
|
| 391 |
391 |
|
// Start scan worker pool. Only meaningful if a scanner is configured;
|
| 392 |
392 |
|
// otherwise enqueue_scan_for never enqueues (trust-gate fast path).
|
| 472 |
472 |
|
.await
|
| 473 |
473 |
|
.expect("Server error");
|
| 474 |
474 |
|
|
| 475 |
|
- |
drop(shutdown_tx); // signal monitor to stop
|
|
475 |
+ |
drop(shutdown_tx); // signal monitor, scheduler, and scan workers to stop
|
|
476 |
+ |
|
|
477 |
+ |
// Drain the scheduler's in-flight tick before returning. Its jobs
|
|
478 |
+ |
// (platform-credit settlement, pending-S3-deletion retries, webhook
|
|
479 |
+ |
// re-drives) are engineered idempotent, so a truncated tick is recoverable —
|
|
480 |
+ |
// but awaiting a bounded window lets an in-progress tick finish cleanly
|
|
481 |
+ |
// instead of being cut off by the armed hard-exit deadline. If it overruns,
|
|
482 |
+ |
// we proceed and rely on idempotency as the backstop.
|
|
483 |
+ |
match tokio::time::timeout(Duration::from_secs(5), scheduler_handle).await {
|
|
484 |
+ |
Ok(Ok(())) => tracing::info!("scheduler drained"),
|
|
485 |
+ |
Ok(Err(e)) => tracing::warn!(error = ?e, "scheduler task ended abnormally on shutdown"),
|
|
486 |
+ |
Err(_) => tracing::warn!(
|
|
487 |
+ |
"scheduler did not drain within 5s of shutdown; proceeding (jobs are idempotent)"
|
|
488 |
+ |
),
|
|
489 |
+ |
}
|
|
490 |
+ |
|
| 476 |
491 |
|
tracing::info!("Server shut down gracefully");
|
| 477 |
492 |
|
}
|
| 478 |
493 |
|
|