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.