Reliability Engineering Intermediate

How to Give a Long-Running Job an Escape Hatch

Add visible progress, no-progress stall detection, and a safe manual restart so users can recover from stalled long-running jobs.

45 min Intermediate Octacer Engineering September 2, 2026
A stalled progress indicator with a restart control unlocking after no progress.

How to give a long-running job an escape hatch

Goal

Make a long-running job — a scan, an analysis, a batch import — recoverable when it silently stalls, so a user is never stuck staring at a spinner with no way out. You'll add a progress signal, a stall detector, and a user-triggered restart. Use this guide for any operation that runs long enough to fail invisibly and that a user is waiting on.

Prerequisites

  • [ ] A job that can take long enough to appear stuck (seconds to minutes).
  • [ ] A way to observe the job's progress (an item count, a percentage, a heartbeat).
  • [ ] A safe way to restart the job without corrupting state or duplicating side effects.
  • [ ] UI surface to show progress and, when needed, a restart control.

Steps

  1. 1

    Emit progress

    Emit a progress signal, not just start/finish. A job that only reports "running" is indistinguishable from a job that has wedged. Surface something that advances — items processed, current depth, a heartbeat timestamp — so both the user and your stall detector can tell moving from stuck.

  2. 2

    Show progress

    Show the progress in the open. Put the meaningful signal (for example scan depth) directly in the UI rather than hiding it behind a settings gear. When a job feels slow, the first question is "is it doing anything?" — answer it without a click.

  3. 3

    Detect stalls

    Detect "no progress," not "still running." Track the last time progress advanced. Define stalled as no progress for N minutes, not elapsed time — a legitimately long job keeps advancing; a wedged one stops:

  4. 4

    Enable restart

    Unlock a manual restart after the stall threshold. Keep the restart control disabled while progress is advancing so users don't kill healthy runs, and unlock it only once the job has made no progress for the threshold (for example several minutes). Label it plainly, e.g. "Restart scan."

  5. 5

    Restart safely

    Make restart safe and idempotent. Ensure a restart cancels the stuck run cleanly and starts fresh without duplicating completed work or side effects. The escape hatch must not create a worse mess than the stall.

  6. 6

    Tune threshold

    Tune the threshold to the job's real variance. Set the stall timeout above the job's normal slowest stretch so healthy runs never offer the restart, but low enough that a genuinely stuck user isn't trapped for long.

   stalled = (now - lastProgressAt) > STALL_TIMEOUT

Configuration

Setting Recommended Tradeoff
Progress signal A real advancing metric (items, depth, heartbeat) Truthful stall detection; needs the job to report it
Progress visibility Shown inline, not hidden Answers "is it working?"; a little more UI
Stall definition No progress for N minutes Distinguishes slow from stuck; must pick N per job
Restart unlock Enabled only after the stall threshold Prevents killing healthy runs; user waits N before acting
Restart semantics Idempotent, cancels the old run Safe recovery; more implementation care

Base the definition of "stalled" on lack of progress, not raw elapsed time — otherwise you either interrupt slow-but-healthy runs or leave stuck ones running forever.

Verification

  • During a healthy run, progress visibly advances and the restart control stays disabled.
  • When progress stops for the threshold, the restart control unlocks.
  • Restarting cancels the stuck run and starts a clean one with no duplicated work or side effects.
  • The progress signal is visible without opening settings.
  • No healthy long run ever offers a restart prematurely.

Common problems

Symptom Likely cause Fix
Users stuck on a spinner with no recourse No escape hatch for a stalled job Add a stall-triggered manual restart
Restart offered on healthy long runs Threshold based on elapsed time Base "stalled" on no-progress, not total time
Restart duplicates work or side effects Non-idempotent restart Cancel the old run cleanly; make restart idempotent
Users can't tell if it's working Progress hidden behind a gear Surface the progress signal inline
Threshold trips too early or too late Timeout not tuned to job variance Set N above the normal slowest stretch

Production checklist

  • [ ] Job emits an advancing progress signal, not just running/done.
  • [ ] Progress is visible inline, not hidden.
  • [ ] Stall detection is based on no-progress-for-N, not elapsed time.
  • [ ] Restart unlocks only after the stall threshold.
  • [ ] Restart is idempotent and cancels the stuck run cleanly.
  • [ ] Threshold tuned against the job's real worst-case healthy duration.
  • The same trio — visible progress, stall detection, safe restart — applies to any long operation: batch imports, report generation, media processing.
  • Pair the manual escape hatch with server-side logging of stalls so recurring wedges get fixed at the root, not just restarted by users.

Ready to Implement This Guide?

Our team can implement these strategies for you, tailored to your specific business needs.

Schedule Consultation