You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Let handlers commit their business output atomically with the job's completion in the same Postgres transaction — upgrading at-least-once execution to exactly-once state effects within the application's database.
Requested-shape example: a handler writes rows to the app's tables and the job's completed transition commits with them, or neither happens; a crash before commit means a clean retry, and the existing run_lease stale-completion guard already refuses a rescued attempt's late commit — no double-apply.
Why the pieces are already here
ADR-029 commits follow-up enqueues atomically with completion — the transactional-finalize machinery exists.
run_lease rejection is exactly the guard that makes user-owned completion safe.
Design constraints (for the ADR)
Python (and any non-Rust consumer) must be inverted. Don't pass a live sqlx transaction across FFI — expose completion as a SQL contract (awa.complete_job_compat(job_id, run_lease, …) family mirroring insert_job_compat) that the user calls inside their own transaction with their own driver (asyncpg/SQLAlchemy/psycopg). The handler then returns a new JobResult::AlreadyFinalized so the executor skips its completion. This belongs inside Stabilize the SQL enqueue contract as a public producer API #342's SQL contract — completion, not just enqueue.
Opt-in bypass of the completion batcher. Queue-storage batches completions (Compact receipt-backed completions #352); a user tx can't join a batch carrying other jobs' completions. Atomic completions write their own single terminal evidence in the user's tx — the pre-batching shape, bounded to opted-in jobs. Throughput-sensitive queues don't opt in.
Rust gets the ergonomic in-process variant: store.complete_in_tx(&mut tx, job_id, run_lease, …) + the same result variant. ADR-029 follow-ups join the same tx via existing _tx inserts.
Failure paths compose: user-tx conflict → handler error → normal retry; fail/DLQ variants already have _in_tx shapes.
Deliverables
ADR (touches ADR-026/029), Rust API + queue-storage complete_in_tx path, the SQL completion contract + cross-driver examples and conformance coverage (with #342), tests including a rescue-races-user-commit witness (TLA+ candidate: completion-vs-rescue with a user-held transaction), docs positioning ("exactly-once state effects in your Postgres").
Sequencing: M4, alongside #342. From the roadmap-gap review; requested in discussion of worker-side atomicity for Rust/Python clients.
Summary
Let handlers commit their business output atomically with the job's completion in the same Postgres transaction — upgrading at-least-once execution to exactly-once state effects within the application's database.
Requested-shape example: a handler writes rows to the app's tables and the job's
completedtransition commits with them, or neither happens; a crash before commit means a clean retry, and the existingrun_leasestale-completion guard already refuses a rescued attempt's late commit — no double-apply.Why the pieces are already here
fail_terminal_in_tx/cancel_by_unique_key_tx(refactor(admin): cancel_tx / cancel_by_unique_key_tx take &mut PgConnection (0.6.0-rc.3) #358) established the&mut PgConnectionlifecycle-op pattern.run_leaserejection is exactly the guard that makes user-owned completion safe.Design constraints (for the ADR)
awa.complete_job_compat(job_id, run_lease, …)family mirroringinsert_job_compat) that the user calls inside their own transaction with their own driver (asyncpg/SQLAlchemy/psycopg). The handler then returns a newJobResult::AlreadyFinalizedso the executor skips its completion. This belongs inside Stabilize the SQL enqueue contract as a public producer API #342's SQL contract — completion, not just enqueue.store.complete_in_tx(&mut tx, job_id, run_lease, …)+ the same result variant. ADR-029 follow-ups join the same tx via existing_txinserts.fail/DLQ variants already have_in_txshapes.Deliverables
ADR (touches ADR-026/029), Rust API + queue-storage
complete_in_txpath, the SQL completion contract + cross-driver examples and conformance coverage (with #342), tests including a rescue-races-user-commit witness (TLA+ candidate: completion-vs-rescue with a user-held transaction), docs positioning ("exactly-once state effects in your Postgres").Sequencing: M4, alongside #342. From the roadmap-gap review; requested in discussion of worker-side atomicity for Rust/Python clients.