mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-13 03:52:00 +00:00
fix(oauth,gateway): monotonic deadlines for polling/timeout loops
Widen PR #20314's fix to the other timeout-polling sites in the codebase that share the same wall-clock-jump bug class. All of these measure elapsed timeout duration, not civil time, so they belong on time.monotonic(). - hermes_cli/auth.py: auth-store file-lock timeout, Spotify OAuth callback wait, Nous portal device-auth token poll. - hermes_cli/copilot_auth.py: Copilot OAuth device-flow token poll. - hermes_cli/gateway.py: gateway systemd restart wait. - hermes_cli/web_server.py: dashboard Codex device-auth user_code wait, dashboard Nous device-auth token poll. (sess["expires_at"] stays on time.time() — it's a persisted absolute timestamp, not a local deadline-polling variable.) - agent/copilot_acp_client.py: Copilot ACP JSON-RPC request timeout.
This commit is contained in:
parent
6e8f1e09a9
commit
2e00bcaaab
5 changed files with 16 additions and 16 deletions
|
|
@ -477,8 +477,8 @@ class CopilotACPClient:
|
||||||
proc.stdin.write(json.dumps(payload) + "\n")
|
proc.stdin.write(json.dumps(payload) + "\n")
|
||||||
proc.stdin.flush()
|
proc.stdin.flush()
|
||||||
|
|
||||||
deadline = time.time() + timeout_seconds
|
deadline = time.monotonic() + timeout_seconds
|
||||||
while time.time() < deadline:
|
while time.monotonic() < deadline:
|
||||||
if proc.poll() is not None:
|
if proc.poll() is not None:
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -894,7 +894,7 @@ def _file_lock(
|
||||||
lock_path.write_text(" ", encoding="utf-8")
|
lock_path.write_text(" ", encoding="utf-8")
|
||||||
|
|
||||||
with lock_path.open("r+" if msvcrt else "a+") as lock_file:
|
with lock_path.open("r+" if msvcrt else "a+") as lock_file:
|
||||||
deadline = time.time() + max(1.0, timeout_seconds)
|
deadline = time.monotonic() + max(1.0, timeout_seconds)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
if fcntl:
|
if fcntl:
|
||||||
|
|
@ -904,7 +904,7 @@ def _file_lock(
|
||||||
msvcrt.locking(lock_file.fileno(), msvcrt.LK_NBLCK, 1)
|
msvcrt.locking(lock_file.fileno(), msvcrt.LK_NBLCK, 1)
|
||||||
break
|
break
|
||||||
except (BlockingIOError, OSError, PermissionError):
|
except (BlockingIOError, OSError, PermissionError):
|
||||||
if time.time() >= deadline:
|
if time.monotonic() >= deadline:
|
||||||
raise TimeoutError(timeout_message)
|
raise TimeoutError(timeout_message)
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
|
@ -1974,9 +1974,9 @@ def _spotify_wait_for_callback(
|
||||||
|
|
||||||
thread = threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.1}, daemon=True)
|
thread = threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.1}, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
deadline = time.time() + max(5.0, timeout_seconds)
|
deadline = time.monotonic() + max(5.0, timeout_seconds)
|
||||||
try:
|
try:
|
||||||
while time.time() < deadline:
|
while time.monotonic() < deadline:
|
||||||
if result["code"] or result["error"]:
|
if result["code"] or result["error"]:
|
||||||
return result
|
return result
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
@ -2739,10 +2739,10 @@ def _poll_for_token(
|
||||||
poll_interval: int,
|
poll_interval: int,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Poll the token endpoint until the user approves or the code expires."""
|
"""Poll the token endpoint until the user approves or the code expires."""
|
||||||
deadline = time.time() + max(1, expires_in)
|
deadline = time.monotonic() + max(1, expires_in)
|
||||||
current_interval = max(1, min(poll_interval, DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS))
|
current_interval = max(1, min(poll_interval, DEVICE_AUTH_POLL_INTERVAL_CAP_SECONDS))
|
||||||
|
|
||||||
while time.time() < deadline:
|
while time.monotonic() < deadline:
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"{portal_base_url}/api/oauth/token",
|
f"{portal_base_url}/api/oauth/token",
|
||||||
data={
|
data={
|
||||||
|
|
|
||||||
|
|
@ -212,9 +212,9 @@ def copilot_device_code_login(
|
||||||
print(" Waiting for authorization...", end="", flush=True)
|
print(" Waiting for authorization...", end="", flush=True)
|
||||||
|
|
||||||
# Step 3: Poll for completion
|
# Step 3: Poll for completion
|
||||||
deadline = time.time() + timeout_seconds
|
deadline = time.monotonic() + timeout_seconds
|
||||||
|
|
||||||
while time.time() < deadline:
|
while time.monotonic() < deadline:
|
||||||
time.sleep(interval + _DEVICE_CODE_POLL_SAFETY_MARGIN)
|
time.sleep(interval + _DEVICE_CODE_POLL_SAFETY_MARGIN)
|
||||||
|
|
||||||
poll_data = urllib.parse.urlencode({
|
poll_data = urllib.parse.urlencode({
|
||||||
|
|
|
||||||
|
|
@ -585,10 +585,10 @@ def _wait_for_systemd_service_restart(
|
||||||
|
|
||||||
svc = get_service_name()
|
svc = get_service_name()
|
||||||
scope_label = _service_scope_label(system).capitalize()
|
scope_label = _service_scope_label(system).capitalize()
|
||||||
deadline = time.time() + timeout
|
deadline = time.monotonic() + timeout
|
||||||
printed_runtime_wait = False
|
printed_runtime_wait = False
|
||||||
|
|
||||||
while time.time() < deadline:
|
while time.monotonic() < deadline:
|
||||||
props = _read_systemd_unit_properties(system=system)
|
props = _read_systemd_unit_properties(system=system)
|
||||||
active_state = props.get("ActiveState", "")
|
active_state = props.get("ActiveState", "")
|
||||||
sub_state = props.get("SubState", "")
|
sub_state = props.get("SubState", "")
|
||||||
|
|
|
||||||
|
|
@ -1877,8 +1877,8 @@ async def _start_device_code_flow(provider_id: str) -> Dict[str, Any]:
|
||||||
name=f"oauth-codex-{sid[:6]}",
|
name=f"oauth-codex-{sid[:6]}",
|
||||||
).start()
|
).start()
|
||||||
# Block briefly until the worker has populated the user_code, OR error.
|
# Block briefly until the worker has populated the user_code, OR error.
|
||||||
deadline = time.time() + 10
|
deadline = time.monotonic() + 10
|
||||||
while time.time() < deadline:
|
while time.monotonic() < deadline:
|
||||||
with _oauth_sessions_lock:
|
with _oauth_sessions_lock:
|
||||||
s = _oauth_sessions.get(sid)
|
s = _oauth_sessions.get(sid)
|
||||||
if s and (s.get("user_code") or s["status"] != "pending"):
|
if s and (s.get("user_code") or s["status"] != "pending"):
|
||||||
|
|
@ -2012,10 +2012,10 @@ def _codex_full_login_worker(session_id: str) -> None:
|
||||||
sess["expires_at"] = time.time() + sess["expires_in"]
|
sess["expires_at"] = time.time() + sess["expires_in"]
|
||||||
|
|
||||||
# Step 2: poll until authorized
|
# Step 2: poll until authorized
|
||||||
deadline = time.time() + sess["expires_in"]
|
deadline = time.monotonic() + sess["expires_in"]
|
||||||
code_resp = None
|
code_resp = None
|
||||||
with httpx.Client(timeout=httpx.Timeout(15.0)) as client:
|
with httpx.Client(timeout=httpx.Timeout(15.0)) as client:
|
||||||
while time.time() < deadline:
|
while time.monotonic() < deadline:
|
||||||
time.sleep(poll_interval)
|
time.sleep(poll_interval)
|
||||||
poll = client.post(
|
poll = client.post(
|
||||||
f"{issuer}/api/accounts/deviceauth/token",
|
f"{issuer}/api/accounts/deviceauth/token",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue