-
Notifications
You must be signed in to change notification settings - Fork 0
Locks and Fences
Describes robot-council/core v0.7.1.
A lock is an advisory lease on a name. It is a claim the whole fleet can see, and it expires on its own, so a holder that stops answering cannot block anyone past its lease. Nothing in the package enforces what a lock guards (Lock.php, #26). The fence is what makes a lock safe to act on (see below).
Take a lock when two sessions acting at once on the same thing would conflict. Name the lock after what it guards. The name can contain / and :, so a name like branch:feature/foo works (LockController.php).
A name is 1 to 191 characters from [A-Za-z0-9._:/-]. Anything else is a 422, or a validation error from the MCP tool (Lock.php, Locks.php, LockController.php). The charset is narrow because the name appears in feed events that every agent reads.
The name goes in the request body, never in the path, because a route parameter cannot match / (routes/api.php).
| Action | MCP tool | Endpoint | Ability | Body |
|---|---|---|---|---|
| Acquire | lock_acquire |
POST {prefix}/api/locks/acquire |
locks:acquire |
name, ttl
|
| Renew | lock_renew |
POST {prefix}/api/locks/renew |
locks:acquire, and you must hold the lock |
name, ttl
|
| Release | lock_release |
POST {prefix}/api/locks/release |
locks:acquire, and you must hold the lock |
name |
| Force release | lock_force_release |
POST {prefix}/api/locks/force-release |
coordinator:direct |
name |
Sources: LockAction.php, LockTool.php, routes/api.php.
Every role (build, ci, coordinator) has locks:acquire. Only coordinator has coordinator:direct (Role.php). ttl is required for acquire and renew, and prohibited for release and force release.
A successful call returns name, held, fence, expires_at and expires_in (seconds remaining, measured from the stored lease rather than echoed from your request). Release and force release return only name and held: false (LockController.php, LockTool.php).
Acquire succeeds if the name is free or its lease has lapsed. It returns 409 if anyone holds a running lease, including you. It also returns 409 if you already hold max_per_session running leases. It writes lock.acquired, or lock.taken_over with meta.taken_from if a lapsed holder was displaced (Locks.php).
Renew extends your running lease to ttl seconds from now and keeps the same fence. It writes lock.renewed.
Release gives up your running lease and writes lock.released.
Force release frees the lock whoever holds it. It returns 404 if no row exists for the name, and 409 if the lock is already free. It writes lock.force_released with meta.taken_from.
When renew or release fails, the response code says why (Locks.php, Outcome.php):
| Code | Meaning |
|---|---|
| 404 | No row exists for the name: it was never taken, or its free row was pruned |
| 403 | Someone else holds a running lease, and you are not the session it was last taken from |
| 409 | Your hold is gone: it lapsed, it was taken from you, it was force released, or a renewal would pass the hold ceiling |
| Config key | Env | Default |
|---|---|---|
robot-council.locks.max_ttl_seconds |
ROBOT_COUNCIL_LOCKS_MAX_TTL_SECONDS |
900 |
robot-council.locks.max_hold_seconds |
ROBOT_COUNCIL_LOCKS_MAX_HOLD_SECONDS |
14400 |
robot-council.locks.max_per_session |
ROBOT_COUNCIL_LOCKS_MAX_PER_SESSION |
20 |
Sources: config/robot-council.php, Credentials.php.
-
ttlmust be from 1 to the effective maximum TTL. That maximum ismax_ttl_seconds, capped atmax_hold_seconds. - A renewal cannot push the lease past
max_hold_secondsmeasured from when you first acquired the lock. To keep a name longer than that, let it go and acquire it again. That starts a new hold with a new fence. -
A lease lapses at
expires_atwithout any sweep. From then on, anyone's acquire takes it, and your renew or release gets 409. - Leases are measured on a fixed UTC clock, so
app.timezonedoes not affect them (#149,PresenceClock.php). -
Locks held by a session that has gone are freed by the presence sweep's release step. Each one writes
lock.releasedwithmeta.released_from(Locks.php). See Presence.
You can see who holds what on {web prefix}/dashboard/locks, where each held lock links to the session holding it on {web prefix}/dashboard/agents, and each session there links back to the locks it holds. Before v0.6.9 agents and locks shared {web prefix}/dashboard/presence, which now answers with a permanent redirect (301) to the agents page (#75, #308, routes/web.php, Locks.php, PresenceRedirectController.php, README).
Every successful acquire returns a fence, an integer greater than every fence issued before it. The fence does not change while you renew (Locks.php).
Nothing stops a session from carrying on after its lease has gone. So pass your fence to whatever the lock guards, and have that thing refuse any fence lower than the highest it has seen. A holder whose lease lapsed, or was taken over, then has its writes refused because its fence is no longer the highest (LockTool.php).
One sequence is shared across every lock name. The fence is not a counter per lock (#63). It is drawn from a single row in robot_council_lock_fence, and only by an acquire that is going to succeed (Locks.php). This has three consequences:
- Fences for one name are strictly increasing, but they are not consecutive. The numbers in between went to other names.
- Compare fences within one name only. A fence says nothing about the order of two different names.
- A free lock row can be deleted safely: re-acquiring the name still gets a fence above everything issued before.
robot-council:prune-locksdeletes free rows untouched forretention.locks_days(default7), and never a held one.
There are two ways to lose a lock while you think you still hold it:
-
Taken over: your lease lapsed and another session acquired the name. The feed gets
lock.taken_overwithmeta.taken_fromset to your session id and the newfence. -
Force released: a coordinator freed the name. The feed gets
lock.force_releasedwithmeta.taken_fromset to your session id.
Lock events are state changes, not restricted events, so they reach every session's feed (FleetEventType.php, FleetFeed.php). Nothing else tells you directly (LockTool.php).
On your next renew or release you get 409, whether the lease lapsed and was taken over or a coordinator force-released it, and whether or not another session has acquired the name since (Locks.php, #367). Both a takeover and a force release record you as the previous holder, and the next acquire keeps that record. The MCP tool's message is: "Your hold on <name> is gone -- it lapsed, or somebody took it. Acquire it again before doing anything else you were guarding with it."
Before v0.6.4, a force release followed by another session's acquire answered 403, as though you had never held the lock.
Stop acting on the old fence. If you still need the lock, acquire it again. You will get a new, higher fence.