Skip to content

epic(locks): per-PR lock lifecycle — TTL underruns the work it guards, and three paths free a live holder's lock #9466

Description

@JSONbored

Summary

The per-PR actuation lock and the AI-review lock are mis-sized on both sides simultaneously, and their lifecycle has three additional edges where a lock is freed while its owner is still running. Individually these are separate bugs; together they mean the engine's central mutual-exclusion primitive cannot be relied on to actually exclude.

Found by an adversarial audit on 2026-07-27 against origin/main @ 776c4147a, with the contender-side failure confirmed in production on edge-nl-01.

The shape of the problem

Side Defect Consequence
Contender Contention retries charge maxRetries; job dies after ~25 s Lost mutation — confirmed live (#9465)
Holder 600 s TTL is shorter than the work it now wraps; no renewal exists Lock expires mid-work → double actuation
Lifecycle Boot flush, SIGTERM release, and registry keying each free a live holder's lock Double actuation without any expiry

The contender side is filed separately as #9465 because it is independently actionable and production-confirmed. This epic covers the holder and lifecycle sides.

Why one epic

A single primitive fixes most of it: a token-checked lock renewal heartbeat. The queue already proved this pattern for its own processing lease in #9023 (heartbeatProcessingLease, src/selfhost/pg-queue.ts:1323). Applying the same shape to claimTransientLock holders resolves the two TTL-underrun defects outright and narrows the SIGTERM window.

Deployment context (verified live, 2026-07-27)

  • Single-instance deployment today: loopover-loopover-1, RestartCount=0, Postgres queue backend, Redis at 1.64 MB / 256 MB.
  • maxmemory-policy allkeys-lru — the lock keys (pr-actuation-lock:*, ai-review-lock:*, contributor-cap-lock:*) live in an LRU-evictable keyspace alongside caches. Usage is 0.6% of the cap today, so this is latent, but under memory pressure Redis may evict a held lock, silently defeating exclusivity with no expiry and no log.
  • Several lifecycle defects below are latent only because the deployment is single-instance. docker-compose.yml:14 advertises "multi-instance capable" and the pg queue backend exists precisely to support it — the moment topology changes, they become live.

Expected outcome

A lock, once acquired, is held for exactly as long as its owner is doing the work it guards — no longer (no strand) and no shorter (no silent expiry). No code path frees a lock whose owner is still running.

Acceptance criteria

  • All sub-issues closed.
  • An invariant test: a simulated holder running longer than the base TTL retains the lock, and a competing claimant is refused for the whole duration.
  • A documented statement, in transient-locks.ts's header, of what each lock guarantees under: TTL expiry, process kill, redeploy, Redis eviction, and multi-instance operation.

Related

Metadata

Metadata

Assignees

Labels

maintainer-onlyOwner-only work — yields no Gittensor points.orbGittensory Orb related - maintainer self-hosting analytics.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions