fix(execute_code): stop Windows kernels with backend parent

This commit is contained in:
Dolverin
2026-09-01 19:06:21 +02:00
committed by Teknium
parent 0177c16903
commit 32d5e9d357
2 changed files with 280 additions and 12 deletions
+114 -12
View File
@@ -78,12 +78,67 @@ import io
import json
import os
import sys
import threading
import traceback
_SENTINEL = os.environ["HERMES_KERNEL_SENTINEL"]
_CAPTURE_LIMIT = {capture_limit}
_SPILL_DIR = os.environ.get("HERMES_KERNEL_SPILL_DIR", "")
_SPILL_CAP = {spill_cap}
_PARENT_PROCESS_HANDLE = os.environ.pop("HERMES_KERNEL_PARENT_PROCESS_HANDLE", "")
def _start_parent_process_watchdog():
"""Exit when the exact Windows parent process object is signaled.
The inherited SYNCHRONIZE handle names a process object, not a reusable
PID. Missing or invalid handles fail open so watchdog setup can never kill
an otherwise healthy kernel.
"""
global _PARENT_PROCESS_HANDLE
raw_handle = _PARENT_PROCESS_HANDLE
_PARENT_PROCESS_HANDLE = ""
if sys.platform != "win32" or not raw_handle:
return
try:
import ctypes
from ctypes import wintypes
handle = int(raw_handle)
if handle <= 0:
return
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.WaitForSingleObject.argtypes = [wintypes.HANDLE, wintypes.DWORD]
kernel32.WaitForSingleObject.restype = wintypes.DWORD
kernel32.SetHandleInformation.argtypes = [
wintypes.HANDLE,
wintypes.DWORD,
wintypes.DWORD,
]
kernel32.SetHandleInformation.restype = wintypes.BOOL
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
kernel32.CloseHandle.restype = wintypes.BOOL
# This process needs the handle, but user code spawned by a cell must
# not pass it any further. If Windows refuses to clear inheritance,
# disable the watchdog rather than leak the handle into cell children.
if not kernel32.SetHandleInformation(handle, 0x00000001, 0):
kernel32.CloseHandle(handle)
return
except (ImportError, OSError, TypeError, ValueError):
return
def _wait():
try:
result = kernel32.WaitForSingleObject(handle, 0xFFFFFFFF)
finally:
kernel32.CloseHandle(handle)
if result == 0x00000000: # WAIT_OBJECT_0: the parent exited
os._exit(0)
threading.Thread(target=_wait, name="hermes-parent-watchdog", daemon=True).start()
_start_parent_process_watchdog()
# The persistent cell namespace. `__name__` is `__main__` so scripts behave
# like the per-call path; builtins resolve normally through exec.
@@ -610,18 +665,65 @@ def _spawn(kernel: SessionKernel, *, task_id: str, child_python: str,
# timeout — a kernel outlives the 300s window between cells.
child_env["HERMES_RPC_PERSISTENT"] = "1"
kernel.proc = subprocess.Popen(
[child_python, runner_path],
# Strict mode resolves an empty cwd: the kernel's own staging dir
# then plays the per-call tmpdir's role.
cwd=child_cwd or kernel.tmpdir,
env=child_env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
start_new_session=True,
creationflags=subprocess.CREATE_NO_WINDOW if _IS_WINDOWS else 0,
)
parent_process_handle = None
close_parent_process_handle = None
startupinfo = None
if _IS_WINDOWS:
try:
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
kernel32.GetCurrentProcessId.argtypes = []
kernel32.GetCurrentProcessId.restype = wintypes.DWORD
kernel32.OpenProcess.argtypes = [
wintypes.DWORD,
wintypes.BOOL,
wintypes.DWORD,
]
kernel32.OpenProcess.restype = wintypes.HANDLE
kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
kernel32.CloseHandle.restype = wintypes.BOOL
close_parent_process_handle = kernel32.CloseHandle
parent_process_handle = kernel32.OpenProcess(
0x00100000, # SYNCHRONIZE
True, # inherited only by the explicitly allow-listed child
kernel32.GetCurrentProcessId(),
)
if parent_process_handle:
child_env["HERMES_KERNEL_PARENT_PROCESS_HANDLE"] = str(
int(parent_process_handle)
)
startupinfo = subprocess.STARTUPINFO()
startupinfo.lpAttributeList = {
"handle_list": [int(parent_process_handle)]
}
except (AttributeError, ImportError, OSError, TypeError, ValueError):
if parent_process_handle and close_parent_process_handle is not None:
close_parent_process_handle(parent_process_handle)
child_env.pop("HERMES_KERNEL_PARENT_PROCESS_HANDLE", None)
parent_process_handle = None
close_parent_process_handle = None
startupinfo = None
try:
kernel.proc = subprocess.Popen(
[child_python, runner_path],
# Strict mode resolves an empty cwd: the kernel's own staging dir
# then plays the per-call tmpdir's role.
cwd=child_cwd or kernel.tmpdir,
env=child_env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
start_new_session=True,
creationflags=subprocess.CREATE_NO_WINDOW if _IS_WINDOWS else 0,
close_fds=True,
startupinfo=startupinfo,
)
finally:
if parent_process_handle and close_parent_process_handle is not None:
close_parent_process_handle(parent_process_handle)
# Deliberately NOT propagate_context_to_thread: that would freeze the
# spawning cell's context/callbacks into the server thread for the