Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02b6f-7db1-7222-918b-e813a6bda38d
77 lines
2.3 KiB
Python
77 lines
2.3 KiB
Python
"""Observable lease state for long-running executions.
|
|
|
|
The monitor is deliberately independent of an adapter. A heartbeat worker can
|
|
mark a lease lost, while the executor observes that fact and decides how to
|
|
cancel or refuse acceptance at its own boundary.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timezone
|
|
from typing import Callable
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LeaseLoss:
|
|
"""Bounded evidence that the worker no longer owns a lease."""
|
|
|
|
error_type: str
|
|
observed_at: str
|
|
|
|
|
|
class LeaseMonitor:
|
|
"""Thread-safe active/lost/stopped state with a one-shot loss callback."""
|
|
|
|
def __init__(self, on_lost: Callable[[LeaseLoss], None] | None = None) -> None:
|
|
self._lock = threading.Lock()
|
|
self._lost = threading.Event()
|
|
self._stopped = False
|
|
self._evidence: LeaseLoss | None = None
|
|
self._on_lost = on_lost
|
|
|
|
@property
|
|
def lost(self) -> bool:
|
|
return self._lost.is_set()
|
|
|
|
@property
|
|
def stopped(self) -> bool:
|
|
with self._lock:
|
|
return self._stopped
|
|
|
|
@property
|
|
def evidence(self) -> LeaseLoss | None:
|
|
with self._lock:
|
|
return self._evidence
|
|
|
|
def wait_lost(self, timeout: float | None = None) -> bool:
|
|
"""Wait until lease loss is observed; return whether it occurred."""
|
|
return self._lost.wait(timeout)
|
|
|
|
def mark_lost(self, error_type: str) -> LeaseLoss:
|
|
"""Record the first loss and invoke the callback once.
|
|
|
|
Error text is intentionally not retained: provider responses can carry
|
|
credentials, prompts, or other unbounded data.
|
|
"""
|
|
callback: Callable[[LeaseLoss], None] | None = None
|
|
with self._lock:
|
|
if self._evidence is None:
|
|
evidence = LeaseLoss(
|
|
error_type=error_type[:80] or "OpsRunError",
|
|
observed_at=datetime.now(timezone.utc).isoformat(),
|
|
)
|
|
self._evidence = evidence
|
|
self._lost.set()
|
|
callback = self._on_lost
|
|
else:
|
|
evidence = self._evidence
|
|
if callback is not None:
|
|
callback(evidence)
|
|
return evidence
|
|
|
|
def stop(self) -> None:
|
|
with self._lock:
|
|
self._stopped = True
|
|
|