Skip to main content

max / makenotwork

Drain scheduler on shutdown; escape filename in upload-queue row - Graceful shutdown now awaits the scheduler's in-flight tick (bounded 5s) instead of dropping its handle and letting the armed hard-exit truncate a tick mid-write. Background jobs stay idempotent as the backstop. - static/item-upload.js: escape the file name before interpolating it into the version-upload queue row's innerHTML (the sibling label was already escaped). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-01 18:17 UTC
Commit: e0b8582f1cd59b760cfee32c4bf2674f144a348f
Parent: 28927b4
2 files changed, +18 insertions, -3 deletions
@@ -386,7 +386,7 @@ async fn main() {
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,7 +472,22 @@ async fn main() {
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
@@ -228,7 +228,7 @@
228 228 li.style.cssText = 'display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; font-size: 0.85rem;';
229 229 var labelInput = document.querySelector('.version-label-input[data-idx="' + entries[q].idx + '"]');
230 230 var labelText = labelInput ? labelInput.value.trim() : '';
231 - var displayName = entries[q].file.name + (labelText ? ' (' + escapeHtml(labelText) + ')' : '');
231 + var displayName = escapeHtml(entries[q].file.name) + (labelText ? ' (' + escapeHtml(labelText) + ')' : '');
232 232 li.innerHTML = '<span class="queue-status" style="width: 1.5em; text-align: center; opacity: 0.5;">-</span><span style="flex: 1;">' + displayName + '</span><span class="queue-size" style="opacity: 0.5;">' + formatSize(entries[q].file.size) + '</span>';
233 233 queueEl.appendChild(li);
234 234 }