Stop the full OpenCode process group (#1858)

* Stop the full OpenCode process group

* Fall back when process-group signaling fails

* Guarantee fallback test process cleanup
This commit is contained in:
Nick Sweeting 2026-09-02 21:31:03 -07:00 committed by GitHub
parent 378133e44c
commit ce529842e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 6 deletions

View File

@ -5,6 +5,7 @@ import base64
import logging
import os
import re
import signal
import shutil
import subprocess
import threading
@ -85,17 +86,27 @@ You are running inside an ArchiveBox collection directory.
"""
def _signal_owned_process(process: subprocess.Popen, sig: signal.Signals) -> None:
try:
os.killpg(process.pid, sig)
except OSError:
try:
process.send_signal(sig)
except ProcessLookupError:
pass
def _stop_owned_process(process: subprocess.Popen | None = None) -> None:
global _PROCESS
owned_process = process or _PROCESS
if owned_process is None:
return
if owned_process.poll() is None:
owned_process.terminate()
_signal_owned_process(owned_process, signal.SIGTERM)
try:
owned_process.wait(timeout=5)
except subprocess.TimeoutExpired:
owned_process.kill()
_signal_owned_process(owned_process, signal.SIGKILL)
owned_process.wait()
if _PROCESS is owned_process:
_PROCESS = None

View File

@ -1,5 +1,6 @@
import os
import socket
import subprocess
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from types import SimpleNamespace
@ -105,10 +106,7 @@ def live_opencode(opencode_archive_config):
try:
yield SimpleNamespace(config=opencode_archive_config, settings=settings, process=process)
finally:
if views._PROCESS and views._PROCESS.poll() is None:
views._PROCESS.terminate()
views._PROCESS.wait(timeout=10)
views._PROCESS = None
views._stop_owned_process()
def test_opencode_disabled_route_does_not_start_server(client, initialized_archive):
@ -126,6 +124,19 @@ 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():
from archivebox.opencode import views
process = subprocess.Popen(["sleep", "60"])
try:
views._stop_owned_process(process)
assert process.poll() is not None
finally:
if process.poll() is None:
process.kill()
process.wait()
def test_opencode_agent_requires_superuser_when_enabled(client, db, django_user_model, live_opencode):
response = client.get("/admin/agent", HTTP_HOST=ADMIN_TEST_HOST)
assert response.status_code == 302