mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-12 19:50:57 +05:00
Trust ready OpenCode processes
This commit is contained in:
parent
f5234360d7
commit
57ee874d6e
@ -38,7 +38,6 @@ _PROCESS_READY: subprocess.Popen | None = None
|
||||
_PROCESS_LOCK = threading.Lock()
|
||||
_SESSION_LOCK = threading.Lock()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_PROCESS_HEALTH_GRACE = 5
|
||||
_PROXY_PREFIX = "/admin/agent/opencode"
|
||||
_PROXY_PREFIX_REGEX = _PROXY_PREFIX.replace("/", r"\/")
|
||||
_PROXY_PREFIX_NO_SLASH_REGEX = _PROXY_PREFIX.lstrip("/").replace("/", r"\/")
|
||||
@ -383,11 +382,11 @@ def _owned_process_ready() -> bool:
|
||||
return process is not None and process is _PROCESS and process.poll() is None
|
||||
|
||||
|
||||
def _health(settings: dict, timeout: float = 2) -> bool:
|
||||
def _health(settings: dict) -> bool:
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{settings['origin']}/global/health",
|
||||
timeout=timeout,
|
||||
timeout=2,
|
||||
)
|
||||
return response.status_code == 200
|
||||
except requests.RequestException:
|
||||
@ -413,23 +412,14 @@ def _ensure_opencode(settings: dict) -> tuple[bool, str]:
|
||||
workdir = settings["workdir"].resolve()
|
||||
|
||||
with _PROCESS_LOCK:
|
||||
if _owned_process_running():
|
||||
deadline = time.monotonic() + min(_PROCESS_HEALTH_GRACE, settings["timeout"])
|
||||
while _owned_process_running():
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
break
|
||||
if _health(settings, timeout=min(2, remaining)):
|
||||
if time.monotonic() <= deadline:
|
||||
_PROCESS_READY = _PROCESS
|
||||
return True, ""
|
||||
break
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining > 0:
|
||||
time.sleep(min(0.25, remaining))
|
||||
_stop_owned_process(_PROCESS)
|
||||
elif _health(settings):
|
||||
if _owned_process_ready():
|
||||
return True, ""
|
||||
if _health(settings):
|
||||
if _owned_process_running():
|
||||
_PROCESS_READY = _PROCESS
|
||||
return True, ""
|
||||
if _owned_process_running():
|
||||
_stop_owned_process(_PROCESS)
|
||||
|
||||
try:
|
||||
binary, git_binary, binary_env = _resolve_binary(
|
||||
|
||||
@ -3,7 +3,6 @@ import os
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
@ -128,13 +127,14 @@ def test_opencode_disabled_route_does_not_start_server(client, initialized_archi
|
||||
assert views._PROCESS is None or views._PROCESS.poll() is not None
|
||||
|
||||
|
||||
def test_stop_owned_process_falls_back_when_process_has_no_dedicated_group():
|
||||
def test_stop_owned_process_falls_back_for_stopped_process_without_dedicated_group():
|
||||
from archivebox.opencode import views
|
||||
|
||||
process = subprocess.Popen(["sleep", "60"])
|
||||
try:
|
||||
process.send_signal(signal.SIGSTOP)
|
||||
views._stop_owned_process(process)
|
||||
assert process.poll() is not None
|
||||
assert process.returncode == -signal.SIGTERM
|
||||
finally:
|
||||
if process.poll() is None:
|
||||
process.kill()
|
||||
@ -287,47 +287,19 @@ def test_concurrent_opencode_startup_waits_until_server_is_ready(live_opencode):
|
||||
assert views._health(live_opencode.settings)
|
||||
|
||||
|
||||
def test_opencode_restarts_an_unhealthy_owned_process(live_opencode):
|
||||
def test_opencode_does_not_probe_or_replace_a_ready_owned_process(live_opencode):
|
||||
from archivebox.opencode import views
|
||||
|
||||
old_process = views._PROCESS
|
||||
process = views._PROCESS
|
||||
settings = {**live_opencode.settings, "port": _free_port()}
|
||||
settings["origin"] = f"http://{settings['host']}:{settings['port']}"
|
||||
|
||||
ok, error = views._ensure_opencode(settings)
|
||||
|
||||
assert ok, error
|
||||
assert old_process is not None
|
||||
assert old_process.poll() is not None
|
||||
assert views._PROCESS is not old_process
|
||||
assert views._health(settings)
|
||||
|
||||
|
||||
def test_opencode_preserves_a_transiently_unhealthy_owned_process(live_opencode):
|
||||
from archivebox.opencode import views
|
||||
|
||||
process = views._PROCESS
|
||||
assert process is not None
|
||||
views._signal_owned_process(process, signal.SIGSTOP)
|
||||
|
||||
def resume_process():
|
||||
time.sleep(1.5)
|
||||
views._signal_owned_process(process, signal.SIGCONT)
|
||||
|
||||
try:
|
||||
with ThreadPoolExecutor(max_workers=1) as executor:
|
||||
resumed = executor.submit(resume_process)
|
||||
started_at = time.monotonic()
|
||||
ok, error = views._ensure_opencode(live_opencode.settings)
|
||||
elapsed = time.monotonic() - started_at
|
||||
resumed.result()
|
||||
finally:
|
||||
views._signal_owned_process(process, signal.SIGCONT)
|
||||
|
||||
assert ok, error
|
||||
assert views._PROCESS is process
|
||||
assert process.poll() is None
|
||||
assert elapsed < views._PROCESS_HEALTH_GRACE
|
||||
|
||||
|
||||
def test_opencode_proxy_does_not_wait_for_recovery_lock(admin_client, live_opencode):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user