Skip to content

How to make a task reschedule itself

dura has no built-in cron or scheduler. This guide shows you how to build a recurring task from a handler that spawns the next occurrence of itself before returning. An idempotency key keeps the chain from forking or stalling.

Spawn the next occurrence from the handler

Have the handler spawn its own next run before returning, with available_after controlling the delay. Give each occurrence a unique idempotency_key, computed from something that changes every time, so a retry of the current run cannot spawn two next-occurrences:

from datetime import timedelta

def poll_orders(engine, task):
    run_number = task.params.get("run_number", 0) + 1

    new_orders = fetch_new_orders()
    handle(new_orders)

    engine.spawn_task(
        name="poll_orders",
        params={"run_number": run_number},
        available_after=timedelta(seconds=30),
        idempotency_key=f"poll_orders:{run_number}",
    )
    return {"processed": len(new_orders)}

Seed the first occurrence once, at startup, the same way:

engine.spawn_task(name="poll_orders", params={"run_number": 0}, idempotency_key="poll_orders:0")

Derive the key from something that never repeats

Don't derive the key from data that might repeat between runs (a cursor that can go a whole cycle without moving, for instance). If the next occurrence's key ever matches the current task's own key, spawn_task returns the current task itself instead of creating a new one, and the chain silently stops. run_number (or any strictly-incrementing counter) avoids that.

Clean up duplicates from before you had a key

If a chain like this was ever seeded more than once, say by application code that called spawn_task unconditionally on every deploy, collapse the duplicates once with cancel_duplicate_tasks. See How to avoid enqueuing the same task twice.