Skip to content

Fix non-atomic async operation creation#619

Merged
nicoloboschi merged 2 commits intomainfrom
fix/atomic-async-operation-creation
Mar 19, 2026
Merged

Fix non-atomic async operation creation#619
nicoloboschi merged 2 commits intomainfrom
fix/atomic-async-operation-creation

Conversation

@cdbartholomew
Copy link
Contributor

Problem

_submit_async_operation created database rows in two separate, non-atomic steps:

  1. INSERT into async_operations with task_payload = NULL
  2. submit_task → separate UPDATE to set task_payload

A process crash or network error between steps 1 and 2 left a permanently unclaimable row. The worker's claim query requires task_payload IS NOT NULL, so these orphaned rows would remain pending forever. Failures of this kind have been observed during periods of high load or API restarts.

Fix

Build full_payload before the INSERT and include task_payload in the same INSERT statement, making operation creation atomic in a single database round-trip.

submit_task is still called afterwards:

  • SyncTaskBackend: executes the task immediately — unchanged behaviour
  • BrokerTaskBackend: performs an idempotent UPDATE (payload already set by the INSERT), kept for symmetry

Result

No more null-payload ghost rows can be created. Existing null-payload rows from prior deployments can be cleaned up by marking them failed (they cannot be claimed regardless).

Previously the method performed two separate database round-trips:
1. INSERT into async_operations with no task_payload (null)
2. submit_task → UPDATE to set task_payload

A process crash or network error between steps 1 and 2 left a row with
task_payload IS NULL permanently. The worker's claim query requires
task_payload IS NOT NULL, so these orphaned rows could never be picked up
and the queue appeared degraded indefinitely.

Fix: build full_payload before the INSERT and include task_payload in the
same INSERT statement, making operation creation atomic. submit_task is
still called afterwards — for SyncTaskBackend it executes the task
immediately (unchanged behaviour); for BrokerTaskBackend it becomes an
idempotent UPDATE (payload already set) kept for symmetry.
Copy link
Collaborator

@nicoloboschi nicoloboschi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nicoloboschi nicoloboschi merged commit 94cf89b into main Mar 19, 2026
38 of 40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants