Handle retries with backoff¶
Return WorkResult.Retry from your worker when a transient failure should retry per the request's BackoffPolicy. The library converts Retry to Failure automatically once BackoffPolicy.maxAttempts is exhausted.
override suspend fun execute(context: WorkerContext): WorkResult {
return try {
api.upload(payload)
WorkResult.Success
} catch (e: TransientHttpError) {
log.w { "transient error on attempt ${context.attempt}; retrying" }
WorkResult.Retry
} catch (e: PermanentHttpError) {
WorkResult.Failure("HTTP ${e.code}: ${e.message}")
}
}
Backoff policies¶
BackoffPolicy.linear(initialDelay = 30.seconds, maxAttempts = 5)
// delay = initialDelay + initialDelay * attempt
// 30s → 60s → 90s → 120s → 150s
BackoffPolicy.exponential(initialDelay = 30.seconds, maxAttempts = 10)
// delay = initialDelay * 2^attempt, capped at MAX_BACKOFF (5 hours)
// 30s → 60s → 120s → 240s → 480s ...
Floors and ceilings:
initialDelaymust be>= 10 seconds(matches WorkManager'sMIN_BACKOFF_MILLIS).maxAttemptsis in1..30. Default is 10.MAX_BACKOFFis 5 hours; exponential growth clamps there so iOS doesn't parkearliestBeginDatea week into the future.
How the cap is enforced¶
- Android.
RegistryDispatchWorker.doWork()readsrunAttemptCount + 1and compares againstBackoffPolicy.maxAttempts(carried throughinputData). When the cap is reached, the worker returnsResult.failure()regardless of what the user code returned, so WorkManager doesn't reschedule. - iOS / macOS. The library tracks attempts in its state store (
tasks.<id>.attempt). OnRetry, it increments and checks the cap; on cap-hit it stops resubmitting and treats the result asFailure. - Periodic. For periodic workers,
maxAttemptsis a per-cycle cap that resets after eachSuccess. Exhausting the cap mid-cycle resumes the regular cadence at the next interval rather than killing the schedule.
Picking a policy¶
- Network ops:
exponential(30.seconds, maxAttempts = 8)— most transient errors clear within minutes. - IO ops on local resources:
linear(10.seconds, maxAttempts = 3)— failure is more often structural than transient. - Idempotent batch processing:
exponential(60.seconds, maxAttempts = 5)— burst long enough to clear, but don't grind on a permanent fault.