oc_chat
Loading...
Searching...
No Matches
oc_chat.registry.regenerate.lock Namespace Reference

Classes

class  LockReclaimEvent
class  LockHandle
class  _LockRecord

Functions

str _default_clock ()
"tuple[str, Optional[str]]" _probe_posix (int pid)
Optional[str] _proc_start_time_posix (int pid)
"tuple[str, Optional[str]]" _probe_windows (int pid)
"tuple[str, Optional[str]]" _default_probe (int pid)
Optional[_LockRecord] _read_record (Path lock_path)
None _atomic_create (Path lock_path, int pid, str start_time, str acquired_at)
LockReclaimEvent _make_event (Path lock_path, Optional[_LockRecord] record, str reason, *, bool force, Clock clock)
LockReclaimEvent _decide_reclaim (Path lock_path, Optional[_LockRecord] record, bool force, Probe probe, Clock clock)
LockHandle acquire (Path lock_path, *, bool force=False, Optional[Clock] clock=None, Optional[Probe] probe=None)
None release (LockHandle handle)

Variables

str PROBE_DEAD = "dead"
str PROBE_ALIVE = "alive"
str PROBE_INCONCLUSIVE = "inconclusive"
str REASON_DEAD_PID = "dead_pid"
str REASON_START_TIME_MISMATCH = "start_time_mismatch"
str REASON_FORCED = "forced_live_or_inconclusive"
 Clock = Callable[[], str]
 Probe = Callable[[int], "tuple[str, Optional[str]]"]

Detailed Description

The single-worktree ``regenerate`` file lock ([OP-7], §5.3, Plan 142i R5).

Purpose
-------
Serialize concurrent ``regenerate`` runs in one worktree with an atomic
(``O_CREAT|O_EXCL``) lock file carrying the holder's **PID + process-start-time +
acquire-timestamp**. A second live holder fails closed with ``REGEN_LOCK_HELD``.
A lock is reclaimed **only on provable holder death** — a **dead PID** or a
**process-start-time mismatch** (PID reuse) — never on elapsed time ([OP-7]: a
renewal-less file lock cannot tell a slow-but-alive holder from a dead one, so a
staleness auto-reclaim would let a second run steal a live lock and corrupt the
swap). A **live-or-inconclusive** holder is never auto-reclaimed; operator
``--force`` is the sole override and every reclamation is surfaced as a typed
``LockReclaimEvent`` (never an unobserved log line).

Responsibilities
----------------
- ``acquire(lock_path, *, force, clock, probe)`` — atomically take the lock or
  fail closed / reclaim-on-provable-death; return a ``LockHandle``.
- ``release(handle)`` — drop our own lock.
- ``LockReclaimEvent`` — the typed, assertable reclamation record.

Testability
-----------
``acquire`` takes injectable **clock** + **process-probe** seams so the tests
cover live-PID / dead-PID / start-time-mismatch / live-but-slow / malformed-lock
/ ``--force`` paths without real sleeps, real PID reuse, or live-PID races.

Diagnostics
-----------
Domain: OC_CHAT.MAIN
Levels:
    L2 - acquire/release lifecycle
    L3 - reclaim classification
    L4 - liveness-probe details

Contracts
---------
- Atomic create — ``O_CREAT|O_EXCL`` so the lock file has no create race.
- Reclaim ⇔ provable death (dead PID or start-time mismatch) OR ``force``.
- ``force`` reclaims but NEVER skips validation/canonicalisation/derive-verify
  (that is the pipeline's job); it only reclaims the lock and logs the event.
- Every probe FAILURE ⇒ inconclusive ⇒ fail-closed (never mapped to "dead").

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.registry.regenerate.envelope (REGEN_LOCK_HELD / RegenerateError)
- oc_chat.registry.clock (RealClock wall time for the default acquire timestamp)

Function Documentation

◆ _atomic_create()

None oc_chat.registry.regenerate.lock._atomic_create ( Path lock_path,
int pid,
str start_time,
str acquired_at )
protected
Create the lock file atomically (``O_CREAT|O_EXCL``) — raises
``FileExistsError`` if a lock already exists (the no-race create).

Definition at line 218 of file lock.py.

Here is the caller graph for this function:

◆ _decide_reclaim()

LockReclaimEvent oc_chat.registry.regenerate.lock._decide_reclaim ( Path lock_path,
Optional[_LockRecord] record,
bool force,
Probe probe,
Clock clock )
protected
Return a ``LockReclaimEvent`` if reclaimable; else raise ``REGEN_LOCK_HELD``.

Reclaimable ⇔ provable holder death (dead PID or start-time mismatch), OR
``force`` on a live/inconclusive holder. A malformed record (``None``) is
inconclusive — never auto-reclaimed.

Definition at line 246 of file lock.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _default_clock()

str oc_chat.registry.regenerate.lock._default_clock ( )
protected

Definition at line 131 of file lock.py.

◆ _default_probe()

"tuple[str, Optional[str]]" oc_chat.registry.regenerate.lock._default_probe ( int pid)
protected

Definition at line 199 of file lock.py.

Here is the call graph for this function:

◆ _make_event()

LockReclaimEvent oc_chat.registry.regenerate.lock._make_event ( Path lock_path,
Optional[_LockRecord] record,
str reason,
* ,
bool force,
Clock clock )
protected

Definition at line 232 of file lock.py.

Here is the caller graph for this function:

◆ _probe_posix()

"tuple[str, Optional[str]]" oc_chat.registry.regenerate.lock._probe_posix ( int pid)
protected

Definition at line 139 of file lock.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _probe_windows()

"tuple[str, Optional[str]]" oc_chat.registry.regenerate.lock._probe_windows ( int pid)
protected

Definition at line 160 of file lock.py.

Here is the caller graph for this function:

◆ _proc_start_time_posix()

Optional[str] oc_chat.registry.regenerate.lock._proc_start_time_posix ( int pid)
protected

Definition at line 151 of file lock.py.

Here is the caller graph for this function:

◆ _read_record()

Optional[_LockRecord] oc_chat.registry.regenerate.lock._read_record ( Path lock_path)
protected
Parse the lock record, or ``None`` if missing/unreadable/malformed (H6).

Definition at line 205 of file lock.py.

Here is the caller graph for this function:

◆ acquire()

LockHandle oc_chat.registry.regenerate.lock.acquire ( Path lock_path,
* ,
bool force = False,
Optional[Clock] clock = None,
Optional[Probe] probe = None )
Acquire the regenerate lock at ``lock_path``.

Raises ``REGEN_LOCK_HELD`` when the lock is held by a live-or-inconclusive
process and ``force`` is false. Creates ``lock_path.parent`` first (H5).

Definition at line 278 of file lock.py.

Here is the call graph for this function:

◆ release()

None oc_chat.registry.regenerate.lock.release ( LockHandle handle)
Release a held lock (best-effort; only removes our own lock record).

Definition at line 320 of file lock.py.

Here is the call graph for this function:

Variable Documentation

◆ Clock

oc_chat.registry.regenerate.lock.Clock = Callable[[], str]

Definition at line 85 of file lock.py.

◆ Probe

oc_chat.registry.regenerate.lock.Probe = Callable[[int], "tuple[str, Optional[str]]"]

Definition at line 87 of file lock.py.

◆ PROBE_ALIVE

str oc_chat.registry.regenerate.lock.PROBE_ALIVE = "alive"

Definition at line 76 of file lock.py.

◆ PROBE_DEAD

str oc_chat.registry.regenerate.lock.PROBE_DEAD = "dead"

Definition at line 75 of file lock.py.

◆ PROBE_INCONCLUSIVE

str oc_chat.registry.regenerate.lock.PROBE_INCONCLUSIVE = "inconclusive"

Definition at line 77 of file lock.py.

◆ REASON_DEAD_PID

str oc_chat.registry.regenerate.lock.REASON_DEAD_PID = "dead_pid"

Definition at line 80 of file lock.py.

◆ REASON_FORCED

str oc_chat.registry.regenerate.lock.REASON_FORCED = "forced_live_or_inconclusive"

Definition at line 82 of file lock.py.

◆ REASON_START_TIME_MISMATCH

str oc_chat.registry.regenerate.lock.REASON_START_TIME_MISMATCH = "start_time_mismatch"

Definition at line 81 of file lock.py.