mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-09 00:12:21 +02:00
fix(runtime): contain and supervise detached Bash jobs (ODY-145)
This commit is contained in:
+40
-1
@@ -16,9 +16,48 @@ from __future__ import annotations
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
import functools
|
||||
import threading
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
_STORE_LOCKS: dict[str, threading.RLock] = {}
|
||||
_STORE_LOCKS_GUARD = threading.Lock()
|
||||
|
||||
|
||||
def store_transaction(path_factory):
|
||||
"""Serialize a JSON read/modify/write across runtime threads and processes."""
|
||||
def decorate(function):
|
||||
@functools.wraps(function)
|
||||
def locked(*args, **kwargs):
|
||||
path = os.path.abspath(str(path_factory())) + ".lock"
|
||||
with _STORE_LOCKS_GUARD:
|
||||
lock = _STORE_LOCKS.setdefault(path, threading.RLock())
|
||||
with lock:
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "a+b") as handle:
|
||||
if os.name == "nt":
|
||||
import msvcrt
|
||||
if os.fstat(handle.fileno()).st_size == 0:
|
||||
handle.write(b"0")
|
||||
handle.flush()
|
||||
handle.seek(0)
|
||||
msvcrt.locking(handle.fileno(), msvcrt.LK_LOCK, 1)
|
||||
else:
|
||||
import fcntl
|
||||
fcntl.flock(handle, fcntl.LOCK_EX)
|
||||
try:
|
||||
return function(*args, **kwargs)
|
||||
finally:
|
||||
if os.name == "nt":
|
||||
handle.seek(0)
|
||||
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
|
||||
else:
|
||||
fcntl.flock(handle, fcntl.LOCK_UN)
|
||||
return locked
|
||||
return decorate
|
||||
|
||||
|
||||
def atomic_write_json(path: str, data: Any, *, indent: Optional[int] = None) -> None:
|
||||
"""Atomically persist `data` as JSON at `path`.
|
||||
|
||||
@@ -64,4 +103,4 @@ def atomic_write_text(path: str, text: str) -> None:
|
||||
try:
|
||||
os.unlink(tmp)
|
||||
except OSError:
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -124,7 +124,7 @@ def pid_alive(pid: Optional[int]) -> bool:
|
||||
return True # EPERM and other inspection failures are not ESRCH.
|
||||
|
||||
|
||||
def kill_process_tree(pid: Optional[int]):
|
||||
def kill_process_tree(pid: Optional[int], *, start_token=None, pgid=None, require_identity=False):
|
||||
"""Use the runtime's shared escalating teardown and return verified death.
|
||||
|
||||
Callers retaining durable PIDs must validate their recorded identity before
|
||||
@@ -140,9 +140,9 @@ def kill_process_tree(pid: Optional[int]):
|
||||
id="", mechanism="windows_tree" if IS_WINDOWS else "process_group",
|
||||
workspace=spec.workspace, enforced=frozenset(), degraded=(),
|
||||
unenforced_required=(), owner="compatibility", mode=containment.CONTAINMENT_MODE,
|
||||
spec=spec, pid=int(pid), pgid=containment._pgid_of(int(pid)),
|
||||
spec=spec, pid=int(pid), pgid=pgid or containment._pgid_of(int(pid)),
|
||||
)
|
||||
return containment.release(grant)
|
||||
return containment.release(grant, start_token=start_token, require_identity=require_identity)
|
||||
|
||||
|
||||
# ── Shell / executable resolution ───────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user