US02-03: Execute Jobs with Leases, Locks, and Recovery (#56)

This commit was merged in pull request #56.
This commit is contained in:
2026-07-15 23:13:25 +02:00
parent 0ebacfa544
commit d054dcbe61
10 changed files with 543 additions and 2 deletions

View File

@@ -0,0 +1,44 @@
"""Lock hierarchy and acquisition ordering.
Locks are acquired broad → narrow to prevent deadlocks: never take a broader lock
while holding a narrower one (concept §16). The coordinator already enforces
one active job per lock key; this guards multi-lock operations.
library lease → stage/job lease → album/folder lease → asset lease
"""
from __future__ import annotations
from collections.abc import Iterable
# Lower rank = broader scope. Mutating lanes map onto these tiers.
LOCK_RANK = {
"library": 0,
"library_write": 0,
"rename": 1,
"upload": 1,
"archive": 1,
"album": 2,
"asset": 3,
"exif": 3,
}
class LockOrderError(RuntimeError):
pass
def rank(lock: str) -> int:
if lock not in LOCK_RANK:
raise LockOrderError(f"unknown lock {lock!r}")
return LOCK_RANK[lock]
def validate_acquisition(held: Iterable[str], acquiring: str) -> None:
"""Reject acquiring a broader lock than one already held (deadlock risk)."""
new_rank = rank(acquiring)
for lock in held:
if rank(lock) > new_rank:
raise LockOrderError(
f"cannot acquire broader lock {acquiring!r} while holding narrower {lock!r}"
)