US07-04: Prove Concurrency and Crash Recovery (#86)

This commit was merged in pull request #86.
This commit is contained in:
2026-08-17 11:09:29 +02:00
parent 3fa35fe21e
commit d08d19c03c
21 changed files with 1740 additions and 40 deletions

View File

@@ -236,17 +236,32 @@ class JobService:
raise InvalidTransition(f"{job.state} -> {to_state}")
if worker_id is not None and job.lease_owner not in (None, worker_id):
raise JobConflict(f"job {job_id} owned by {job.lease_owner}, not {worker_id}")
job.state = to_state
job.version += 1
job.updated_at = now
# Compare-and-set on the version this decision was made against. Without
# it a transition validated against a row that has since been claimed,
# cancelled, or finished would overwrite that newer state (concept §16
# database rule 6) — a cancel racing a claim used to un-claim a running
# job and leave the worker finalizing a job it no longer owned.
values = {
"state": to_state,
"version": job.version + 1,
"updated_at": now,
}
if error:
job.error_code, job.error_message = error
values["error_code"], values["error_message"] = error
if to_state in TERMINAL_STATES:
job.finished_at = now
job.lease_owner = None
job.lease_expires_at = None
values.update(finished_at=now, lease_owner=None, lease_expires_at=None)
result = session.execute(
update(Job).where(Job.id == job_id, Job.version == job.version).values(**values)
)
if result.rowcount != 1:
session.rollback()
raise JobConflict(
f"job {job_id} changed while transitioning to {to_state}; retry"
)
self._event(session, job_id, f"state:{to_state}", error[1] if error else None)
session.commit()
session.expire_all() # the core UPDATE bypassed the identity map
return self._snapshot(session, job_id)
def cancel(self, job_id: str) -> dict: