Skip to main content

Cancellation

A running job can be asked to stop for three reasons:

  • it timed out (it ran past its timeoutSeconds),
  • the worker is shutting down, or
  • Flare returned a cancel signal on a heartbeat (for example because someone cancelled the job from the dashboard or API).

In every case the job's CancellationToken is tripped.

Cancellation is cooperative​

There is no safe way to forcibly kill a running thread on the JVM without risking corrupt locks or half-written resources, so cancellation is cooperative: a handler that never checks the token and never yields cannot be interrupted. Such a job keeps running until it finishes on its own; only then is the slot freed. (Flare's own reaper will eventually reclaim a job that goes silent, but the in-process thread runs to completion.)

So: long-running or loop-shaped handlers should check for cancellation and pass it down to blocking calls.

public void execute(Batch payload, JobContext ctx) {
for (Item item : payload.items) {
ctx.throwIfCancellationRequested(); // stop promptly when asked
process(item);
ctx.reportProgress(progressSoFar());
}
}

The cancellation handle​

ctx.cancellationToken() returns a CancellationToken:

MemberUse
isCancellationRequested()Poll the flag in a loop.
throwIfCancellationRequested()Throw to abandon the job at a checkpoint.
sleep(Duration)Interruptible sleep; returns true if cancelled while waiting.
onCancel(Runnable)Run a callback when cancelled — e.g. close a socket so a blocked read unwinds.

Use sleep(...) instead of Thread.sleep(...) so a waiting job wakes up immediately on cancellation:

if (ctx.cancellationToken().sleep(Duration.ofSeconds(5))) {
return; // cancelled during the wait — stop here
}

For blocking I/O that ignores the token (a database driver, an HTTP client), set that call's own timeout to no more than the job's timeout, and/or register a close callback:

ctx.cancellationToken().onCancel(connection::closeQuietly);

Cancelled and timed-out jobs are acked failed​

When a job is cancelled or times out, the worker acks it as failed (not as a separate "cancelled" state). Flare then applies its normal retry policy — while attempts remain, the job comes back; otherwise it dead-letters. Because a cancelled job may be retried, and because delivery is at-least-once, handlers must be idempotent: guard side effects on ctx.jobId() so a retry after a cancellation doesn't double-apply.