Skip to content

Retries, acks and dead letters

This page is the rulebook for what happens to a message after its handler runs. The concept page Retries and failure handling explains when to use each tool.

import 'dart:io';
import 'package:gisila_queue/gisila_queue.dart';
app.task<void>('emails.send', (ctx) async {
try {
await mailer.send(ctx.arg(0) as String);
} on SocketException catch (e) {
ctx.retry(cause: e); // backoff: ~2 s, 4 s, 8 s, …
}
}, maxRetries: 5, retryBackoff: const Duration(seconds: 2));
Handler outcome Retries left? Stored state Message Event
Returns a value n/a success (with result) acked taskSucceeded
ctx.retry() / throws RetryTask yes retry new copy enqueued with retries + 1 and ETA = now + delay; original acked taskRetried
ctx.retry() no failure (retries exhausted (n): cause) dead-lettered, acked taskFailed
Throws anything else, autoRetry: true yes retry as for ctx.retry() with the task’s backoff taskRetried
Throws anything else no, or autoRetry: false failure (error.toString()) dead-lettered, acked taskFailed
Passes timeLimit as above handled like a thrown TaskTimeLimitExceededException as above as above
Throws RejectTask(requeue: true) n/a unchanged re-enqueued with requeues + 1 (no delay); original acked. Past maxRequeues: failure, dead-lettered taskRetried (requeued: true)
Throws RejectTask() n/a revoked (with the reason) acked, not dead-lettered taskRevoked
Worker crashes mid-task n/a stays started unacked; redelivered by the broker with redeliveries + 1. Past maxRedeliveries: failure, dead-lettered taskReceived again

The retry delay is countdown if passed to ctx.retry(), otherwise Task.backoffFor(retries + 1): retryBackoff × 2^(n-1), capped at retryBackoffMax, jittered into [d/2, d] when retryJitter is on.

maxRetries comes from the message, not the registry. See the caution on Tasks.

The worker acks after the outcome is recorded and any retry copy is enqueued, in a finally. Two consequences:

  • A crash after the handler finished but before the ack runs the task again on another worker. Design handlers to be idempotent (see Delivery guarantees).
  • A retry copy enqueued just before a crash and the redelivered original can both run.

InMemoryBroker removes a message when it is reserved, so a crash loses it.

Task.timeLimit makes the worker stop waiting: the run is recorded as timed out and retried or failed right away, and ctx.isAbandoned becomes true. Dart cannot preempt a running handler in an isolate, so the handler keeps running and its concurrency slot stays busy until it returns; the worker logs a warning every timeLimit while it does. Anything it returns afterwards is discarded.

Task.softTimeLimit is only a flag: poll ctx.softTimeLimitExceeded and wind down.

A message past its expires time, or whose id was revoked (live broadcast or revoked in the backend), is recorded as revoked and acked without running. These checks happen before the handler starts; a running handler only sees ctx.isRevoked.

A message that fails for good is copied to the worker’s deadLetterQueue ('dead_letter' by default) with its ETA cleared and deadLetter set to:

{"reason": "retries exhausted (5): ...", "queue": "emails", "at": "2026-10-31T09:00:00.000Z"}

Reasons: failure with no retries left, unknown task name after maxRequeues, too many RejectTask(requeue: true), and more than maxRedeliveries broker redeliveries. A RejectTask() without requeue, an expiry or a revocation is not dead-lettered.

The dead-letter queue is an ordinary queue on the same broker. Don’t consume it with a normal worker. Inspect its depth with broker.length('dead_letter') or gisila_queue inspect --queues=dead_letter.

Future<int> replayDeadLetters({String queue = Worker.defaultDeadLetterQueue, int limit = 100})

Moves up to limit messages back to the queue in their deadLetter['queue'] (or the app’s default queue), with retries, requeues and redeliveries reset to 0 and deadLetter cleared, stores pending for each, and returns the count.

final moved = await app.replayDeadLetters();
print('replayed $moved');

See also: Handle retries and failures, Workers, Errors.