mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-14 11:06:13 +05:00
Keep OpenCode proxy traffic out of recovery
This commit is contained in:
parent
7f1767a2db
commit
944907bfee
@ -105,6 +105,7 @@ def _stop_owned_process(process: subprocess.Popen | None = None) -> None:
|
||||
if owned_process is None:
|
||||
return
|
||||
if owned_process.poll() is None:
|
||||
_signal_owned_process(owned_process, signal.SIGCONT)
|
||||
_signal_owned_process(owned_process, signal.SIGTERM)
|
||||
try:
|
||||
owned_process.wait(timeout=5)
|
||||
@ -369,11 +370,15 @@ def _ensure_default_session(settings: dict) -> str:
|
||||
return session_id
|
||||
|
||||
|
||||
def _health(settings: dict) -> bool:
|
||||
def _owned_process_running() -> bool:
|
||||
return _PROCESS is not None and _PROCESS.poll() is None
|
||||
|
||||
|
||||
def _health(settings: dict, timeout: float = 2) -> bool:
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{settings['origin']}/global/health",
|
||||
timeout=2,
|
||||
timeout=timeout,
|
||||
)
|
||||
return response.status_code == 200
|
||||
except requests.RequestException:
|
||||
@ -399,15 +404,20 @@ def _ensure_opencode(settings: dict) -> tuple[bool, str]:
|
||||
workdir = settings["workdir"].resolve()
|
||||
|
||||
with _PROCESS_LOCK:
|
||||
if _health(settings):
|
||||
return True, ""
|
||||
if _PROCESS is not None and _PROCESS.poll() is None:
|
||||
if _owned_process_running():
|
||||
deadline = time.monotonic() + min(_PROCESS_HEALTH_GRACE, settings["timeout"])
|
||||
while time.monotonic() < deadline and _PROCESS.poll() is None:
|
||||
if _health(settings):
|
||||
while _owned_process_running():
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
break
|
||||
if _health(settings, timeout=min(2, remaining)):
|
||||
return True, ""
|
||||
time.sleep(0.25)
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining > 0:
|
||||
time.sleep(min(0.25, remaining))
|
||||
_stop_owned_process(_PROCESS)
|
||||
elif _health(settings):
|
||||
return True, ""
|
||||
|
||||
try:
|
||||
binary, git_binary, binary_env = _resolve_binary(
|
||||
@ -609,11 +619,12 @@ async def _event_chunks(
|
||||
params: tuple[tuple[str, str], ...],
|
||||
headers: dict[str, str],
|
||||
):
|
||||
ok, error = await sync_to_async(_ensure_opencode, thread_sensitive=False)(settings)
|
||||
if not ok:
|
||||
_LOGGER.warning("OpenCode event stream unavailable: %s", error)
|
||||
yield b'event: error\ndata: {"error":"OpenCode upstream unavailable"}\n\n'
|
||||
return
|
||||
if not _owned_process_running():
|
||||
ok, error = await sync_to_async(_ensure_opencode, thread_sensitive=False)(settings)
|
||||
if not ok:
|
||||
_LOGGER.warning("OpenCode event stream unavailable: %s", error)
|
||||
yield b'event: error\ndata: {"error":"OpenCode upstream unavailable"}\n\n'
|
||||
return
|
||||
|
||||
timeout = httpx.Timeout(settings["timeout"], read=None)
|
||||
url = _proxy_url(settings, path)
|
||||
@ -735,9 +746,10 @@ def opencode_proxy_view(request: HttpRequest, path: str | None = None):
|
||||
response.headers["X-Accel-Buffering"] = "no"
|
||||
return response
|
||||
|
||||
ok, error = _ensure_opencode(settings)
|
||||
if not ok:
|
||||
return _proxy_error_response(error)
|
||||
if path == "global/health" or not _owned_process_running():
|
||||
ok, error = _ensure_opencode(settings)
|
||||
if not ok:
|
||||
return _proxy_error_response(error)
|
||||
|
||||
try:
|
||||
method = request.method or "GET"
|
||||
|
||||
@ -317,7 +317,9 @@ def test_opencode_preserves_a_transiently_unhealthy_owned_process(live_opencode)
|
||||
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)
|
||||
@ -325,6 +327,29 @@ def test_opencode_preserves_a_transiently_unhealthy_owned_process(live_opencode)
|
||||
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):
|
||||
from archivebox.opencode import views
|
||||
|
||||
workdir = quote(str(live_opencode.config.data_dir.resolve()))
|
||||
executor = ThreadPoolExecutor(max_workers=1)
|
||||
views._PROCESS_LOCK.acquire()
|
||||
try:
|
||||
request = executor.submit(
|
||||
admin_client.get,
|
||||
f"/admin/agent/opencode/path?directory={workdir}",
|
||||
HTTP_HOST=ADMIN_TEST_HOST,
|
||||
HTTP_SEC_FETCH_SITE="same-origin",
|
||||
)
|
||||
response = request.result(timeout=5)
|
||||
finally:
|
||||
views._PROCESS_LOCK.release()
|
||||
executor.shutdown(wait=True)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert str(live_opencode.config.data_dir.resolve()).encode() in response.content
|
||||
|
||||
|
||||
def test_opencode_proxy_sse_response_is_unbuffered(admin_client, live_opencode):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user