Skip to main content

Task Lifecycle and State Management

In this codebase, task lifecycle and state management are governed by a strict precedence system and a set of built-in states that track a task from submission to completion. The system is designed to handle both automatic transitions managed by the worker and manual updates triggered by task authors.

Task States and Precedence​

Task states are represented by the state class in celery.states, which is a subclass of str. This class implements comparison operators (<, >, etc.) based on a predefined hierarchy. This hierarchy ensures that state transitions follow a logical progression and prevents late-arriving updates from overwriting final results.

Built-in States​

The core states defined in celery.states include:

  • PENDING: The default state for tasks waiting for execution.
  • STARTED: The task has been picked up by a worker (requires track_started to be enabled).
  • SUCCESS: The task completed successfully.
  • FAILURE: The task failed with an exception.
  • RETRY: The task failed but is being retried.
  • REVOKED: The task was cancelled or terminated.

Precedence Rules​

The PRECEDENCE list in celery/states.py defines the order of importance. A lower index in this list indicates higher precedence:

PRECEDENCE = [
'SUCCESS',
'FAILURE',
None, # Placeholder for custom states
'REVOKED',
'STARTED',
'RECEIVED',
'REJECTED',
'RETRY',
'PENDING',
]

Custom states (like 'PROGRESS') fall into the None category. This means:

  • SUCCESS and FAILURE always have the highest precedence.
  • Custom states have higher precedence than STARTED or PENDING.
  • A task in a SUCCESS state will ignore any subsequent state updates to prevent race conditions.

Example of state comparison:

from celery.states import state, PENDING, SUCCESS, STARTED

assert state(SUCCESS) > state(STARTED) # True
assert state('PROGRESS') > state(STARTED) # True
assert state('PROGRESS') < state(SUCCESS) # True

The Task Lifecycle​

The lifecycle of a task typically follows this flow:

  1. Submission: When apply_async() or delay() is called, the task is created in the PENDING state.
  2. Execution: When a worker starts the task, it may transition to STARTED if the Task.track_started attribute is set to True.
  3. Completion: The task ends in a "ready" state, which is one of SUCCESS, FAILURE, or REVOKED.

Enabling Execution Tracking​

By default, Celery does not report when a task has started to reduce backend traffic. To enable this, set track_started on the task class:

class MyTask(Task):
track_started = True

Manual State Management​

Tasks can manually update their state using the update_state method. This is most commonly used for progress tracking in long-running tasks.

Updating Progress​

The update_state method accepts a state string and a meta dictionary. The meta data is stored in the result backend and can be retrieved by the client.

# Example from t/unit/tasks/test_tasks.py
@app.task(bind=True)
def long_running_task(self):
for i in range(100):
# Perform work...
self.update_state(
state='PROGRESS',
meta={'current': i, 'total': 100}
)

When update_state is called without a task_id, it defaults to the current task's ID found in self.request.id.

State Persistence Rules​

The BaseBackend class in celery/backends/base.py enforces the finality of the SUCCESS state. In the _store_result method, the backend checks the current state before applying an update:

# Logic from celery/backends/base.py
def _store_result(self, task_id, result, state, ...):
current_meta = self._get_task_meta_for(task_id)

if current_meta['status'] == states.SUCCESS:
return result # Ignore update if already successful

# Otherwise, proceed with storing the new state
self._set_with_state(...)

This mechanism is critical for handling network partitioning or worker failures, ensuring that a "lost" worker cannot overwrite a successful result with an older state (like STARTED or a custom PROGRESS state).

Retries and State​

When a task is retried via Task.retry(), its state transitions to RETRY. This state is considered "unready," meaning the task is still active in the system. The retry method internally creates a new signature and sends it to the broker, while the current execution instance raises a Retry exception to signal the worker to update the state accordingly.