fix(runtime): contain and supervise detached Bash jobs (ODY-145)

This commit is contained in:
Alexandre Teixeira
2026-10-01 21:23:21 +01:00
parent ba7c733741
commit 7892f7f650
11 changed files with 438 additions and 101 deletions
+40 -1
View File
@@ -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