Skip to content

Commit 77a322e

Browse files
committed
fix: harden uv daemon cache sync, target safety, and fs watcher stability
This patch stabilizes the daemonized uv execution path and fixes several cache coherency and filesystem race issues discovered during real-world use. Changes: - Added uv executable healing / fallback resolution for bare PATH names and bundled interpreter-local uv binaries. - Added explicit --target safety bypass: target installs now always use subprocess uv instead of the daemonized FFI path to avoid corrupting the main environment. - Exposed get_site_packages_cache() from uv_ffi and pre-populate the Rust site-packages cache during worker warm-up so patch_cache works from the first operation. - Improved patch_cache logging to distinguish successful patches from normal cache-not-yet-seeded cases. - Increased filesystem watcher debounce windows to correctly absorb pip’s uninstall/temp-write/rename behavior. - Ignored pip temporary ~dist-info artifacts and excluded .omnipkg_versions bubble paths from external site-packages watcher handling. - Fixed move-event handling so only final rename destinations trigger package state updates. - Stopped forwarding patch_cache updates to normal CLI workers; only uv FFI workers participate in cache coherency now. - Added idle reaping for daemonized uv workers after long inactivity. - Prevented unsafe auto-reinstall of uv_ffi into live interpreters while workers may still have the extension loaded. Net result: - safer target installs - fewer false external-change patches - better daemon cache coherency - reduced watcher race conditions - more stable long-lived uv workers Modified: • src/omnipkg/_vendor/uv_ffi/__init__.py (+3/-0 lines) • src/omnipkg/core.py (+27/-1 lines) • src/omnipkg/isolation/fs_watcher.py (+27/-6 lines) • src/omnipkg/isolation/worker_daemon.py (+44/-27 lines) [gitship-generated]
1 parent 834b027 commit 77a322e

4 files changed

Lines changed: 102 additions & 35 deletions

File tree

src/omnipkg/_vendor/uv_ffi/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ def _try_import(name: str):
3232

3333
_patch = _try_import('patch_site_packages_cache')
3434
if _patch is not None:
35-
patch_site_packages_cache = _patch
35+
patch_site_packages_cache = _patch
36+
_gspc = _try_import('get_site_packages_cache')
37+
if _gspc is not None:
38+
get_site_packages_cache = _gspc

src/omnipkg/core.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15514,6 +15514,15 @@ def _run_pip_install(
1551415514
if not hasattr(self, "_uv_exe_cached"):
1551515515
import shutil as _shutil_init
1551615516
_exe = self.config.get("uv_executable") or _shutil_init.which("uv") or ""
15517+
# Heal bare name (e.g. "uv") that fails os.path.exists() — resolve via PATH
15518+
if _exe and not os.path.isabs(_exe):
15519+
_exe = _shutil_init.which(_exe) or _exe
15520+
# If still not found, try to find uv bundled next to the python executable
15521+
if not _exe or not os.path.exists(_exe):
15522+
import sys as _sys
15523+
_bundled = os.path.join(os.path.dirname(_sys.executable), "uv")
15524+
if os.path.exists(_bundled):
15525+
_exe = _bundled
1551715526
self._uv_exe_cached = _exe if (_exe and os.path.exists(_exe)) else ""
1551815527
import tempfile as _tf
1551915528
_default_cache = (
@@ -15593,7 +15602,24 @@ def _run_pip_install(
1559315602
else:
1559415603
safe_print(f"[UV-PATH] FFI skipped (unavailable or --target) — trying daemon", file=sys.stderr)
1559515604

15596-
# ── PATH 2: daemon run_uv (~IPC overhead) ──────────────────
15605+
# ── TARGET BYPASS: subprocess uv only, never daemon for --target ──
15606+
# Daemon UV FFI worker state is tied to main site-packages and corrupts
15607+
# host env when processing --target installs. Subprocess uv is safe.
15608+
if "--target" in _uv_args:
15609+
safe_print(f"[UV-PATH] --target — subprocess uv (daemon bypass)", file=sys.stderr)
15610+
try:
15611+
import subprocess as _sp
15612+
_t0 = time.perf_counter()
15613+
_sp_result = _sp.run([uv_exe] + _uv_args, capture_output=True, text=True, timeout=120)
15614+
_sp_ms = (time.perf_counter() - _t0) * 1000
15615+
safe_print(f"[UV-TIMING] subprocess-target: {_sp_ms:.2f}ms rc={_sp_result.returncode}", file=sys.stderr)
15616+
if _sp_result.stdout: safe_print(_sp_result.stdout, end="")
15617+
if _sp_result.stderr: safe_print(_sp_result.stderr, end="", file=sys.stderr)
15618+
return _sp_result.returncode, {"stdout": _sp_result.stdout, "stderr": _sp_result.stderr}
15619+
except Exception as _sp_ex:
15620+
safe_print(f"[UV-PATH] subprocess-target failed ({_sp_ex}) — falling through", file=sys.stderr)
15621+
15622+
# ── PATH 2: daemon run_uv (~IPC overhead) ──────────────
1559715623
_t0 = time.perf_counter()
1559815624
try:
1559915625
from omnipkg.isolation.worker_daemon import DaemonClient

src/omnipkg/isolation/fs_watcher.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ def unlink(self):
260260
#: of any FFI write, making rapid back-to-back swaps appear as stale cache hits.
261261
OUR_WRITE_GRACE_MS = 100 # 100ms — generously covers <10ms install + inotify lag
262262

263+
# Minimum debounce for dist-info events. pip's uninstall→temp-write→rename
264+
# sequence spans ~100ms; firing at 5ms catches the temp file mid-op.
265+
# 150ms lets the full sequence settle before we parse the delta.
266+
_DIST_INFO_DEBOUNCE_S = 0.150 # 150ms — covers pip's full install sequence
267+
_OTHER_DEBOUNCE_S = 0.250 # 250ms — noisy __pycache__ / .pth writes
268+
263269

264270
class SitePackagesEventHandler(FileSystemEventHandler):
265271
"""
@@ -323,16 +329,26 @@ def _is_dist_info(self, path: str) -> bool:
323329
def _is_relevant(self, path: str) -> bool:
324330
"""
325331
Trigger on depth-1 events inside site-packages (dist-info, .data, .pth, packages).
326-
Ignore __pycache__ and nested writes inside already-installed packages.
332+
Ignore:
333+
- __pycache__ and nested writes inside already-installed packages
334+
- ~ prefixed temp files (pip writes ~pkg-x.y.z.dist-info then renames)
335+
- .omnipkg_versions/ subtree (our own bubble dir — never external)
336+
- direct writes inside existing package dirs (depth > 1, non-dist-info)
327337
"""
328338
try:
329339
p = Path(path).resolve()
330340
rel = _relative_to_win(p, self._sp_path)
331341
parts = rel.parts
332342
if not parts:
333343
return False
344+
name = parts[0]
345+
# Skip pip temp files (~rich-14.3.3.dist-info style)
346+
if name.startswith("~"):
347+
return False
348+
# Skip our own bubble directory entirely
349+
if name == ".omnipkg_versions":
350+
return False
334351
if len(parts) == 1:
335-
name = parts[0]
336352
return (
337353
name.endswith(".dist-info")
338354
or name.endswith(".data")
@@ -376,8 +392,9 @@ def _schedule_invalidation(self, path: str, created: bool):
376392
from inotify can't each spawn their own timer.
377393
"""
378394
is_di = self._is_dist_info(path)
379-
# Tighter window for dist-info (actionable immediately), wider for noise
380-
debounce_s = 0.005 if is_di else 0.05
395+
# dist-info: wait for pip's full uninstall→temp→rename sequence to settle
396+
# other: wider window to absorb noisy __pycache__ / .pth bursts
397+
debounce_s = _DIST_INFO_DEBOUNCE_S if is_di else _OTHER_DEBOUNCE_S
381398

382399
with self._debounce_lock:
383400
# Accumulate .dist-info dirs only — they carry name+version for free
@@ -455,12 +472,16 @@ def _handle(self, event):
455472
on_modified = _handle
456473

457474
def on_moved(self, event):
458-
# Moves happen when pip writes .tmp then renames — treat dest as created
475+
# pip pattern: write ~pkg-x.y.z.dist-info (temp) → rename to pkg-x.y.z.dist-info
476+
# We only care about the FINAL rename destination, not the temp src.
477+
# _is_relevant() already filters ~ names so src fires are silently dropped.
459478
if self._is_our_write():
460479
return
461480
if self._is_relevant(event.dest_path):
462481
self._schedule_invalidation(event.dest_path, created=True)
463-
if self._is_relevant(event.src_path):
482+
# src: only fire deleted if it was a real dist-info (not a ~ temp rename)
483+
src_name = Path(event.src_path).name
484+
if not src_name.startswith("~") and self._is_relevant(event.src_path):
464485
self._schedule_invalidation(event.src_path, created=False)
465486

466487

src/omnipkg/isolation/worker_daemon.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,19 @@ def fatal_error(msg, error=None):
905905
_wpos.close(_wp_dn)
906906
_UV_FFI_RUN = _uv_ffi_run
907907
globals()['_UV_FFI_RUN'] = _uv_ffi_run
908+
# ── Populate Rust site-packages cache immediately after warm-up ──
909+
# pip freeze only warms the Tokio runtime — it does NOT populate the
910+
# cache. patch_cache returns False until the cache has been seeded.
911+
# We call get_site_packages_cache() here to force the initial scan
912+
# so patch_cache works correctly from the very first message.
913+
try:
914+
from omnipkg._vendor.uv_ffi import get_site_packages_cache as _gspc
915+
_gspc()
916+
sys.stderr.write('[WORKER-PRELOAD] ✅ site-packages cache populated' + chr(10))
917+
sys.stderr.flush()
918+
except Exception as _gsp_ex:
919+
sys.stderr.write('[WORKER-PRELOAD] cache populate skipped: ' + str(_gsp_ex) + chr(10))
920+
sys.stderr.flush()
908921
# ── FIX 1: wire FFI onto the pre-warmed core object so that
909922
# core._run_pip_install() can find it via getattr(self, '_uv_ffi_run', None)
910923
# instead of crashing with AttributeError, which kills the worker and
@@ -1144,9 +1157,12 @@ def flush(self_buf):
11441157
_inst_t = [tuple(x) for x in _inst]
11451158
_rem_t = [tuple(x) for x in _rem]
11461159
_patched = _pspc(_inst_t, _rem_t)
1147-
sys.stderr.write(f'[RUN-UV] patch_cache: patched={_patched} inst={_inst_t} rem={_rem_t}\\n')
1148-
except Exception as _pc_ex:
1149-
sys.stderr.write(f'[RUN-UV] patch_cache failed: {_pc_ex}\\n')
1160+
if _patched:
1161+
sys.stderr.write(f'[RUN-UV] patch_cache: ✅ patched inst={_inst_t} rem={_rem_t}\\n')
1162+
else:
1163+
# False = cache not yet populated (no run() call yet on this worker)
1164+
# This is normal on a fresh worker — cache populates on first install
1165+
sys.stderr.write(f'[RUN-UV] patch_cache: skipped (cache not yet populated) inst={_inst_t} rem={_rem_t}\\n')
11501166
except Exception as _pc_ex:
11511167
sys.stderr.write(f'[RUN-UV] patch_cache failed: {_pc_ex}\\n')
11521168
sys.stderr.flush()
@@ -2950,6 +2966,7 @@ def __init__(self, max_workers: int = 10, max_idle_time: int = 300, warmup_specs
29502966
self.uv_workers: Dict[str, "PersistentWorker"] = {}
29512967
self.uv_worker_locks: Dict[str, threading.Lock] = defaultdict(threading.Lock)
29522968
self._uv_last_install_ts: Dict[str, float] = {} # py_exe → monotonic time of last completed install
2969+
self._UV_WORKER_IDLE_TIMEOUT = 86400.0 # 1 day — lazy respawn ~130ms, fine for infrequent use
29532970
# ────────────────────────────────────────────────────────────────────
29542971

29552972
self.running = True
@@ -3150,6 +3167,19 @@ def _maintain_idle_pool(self):
31503167
time.sleep(1.0)
31513168
except Exception:
31523169
pass
3170+
# Reap UV workers idle longer than 1 day
3171+
try:
3172+
_now = time.monotonic()
3173+
for _py_exe in list(self.uv_workers.keys()):
3174+
_last = self._uv_last_install_ts.get(_py_exe, 0.0)
3175+
if _now - _last > self._UV_WORKER_IDLE_TIMEOUT:
3176+
_w = self.uv_workers.pop(_py_exe, None)
3177+
if _w is not None:
3178+
try: _w.force_shutdown()
3179+
except Exception: pass
3180+
safe_print(f"[RUN-UV] reaped idle UV worker for {_py_exe} (idle >1d)", file=sys.stderr)
3181+
except Exception:
3182+
pass
31533183
time.sleep(0.5)
31543184

31553185
def set_idle_config(self, python_exe: str, count: int):
@@ -3640,30 +3670,8 @@ def _handle_client(self, conn: socket.socket):
36403670
except Exception:
36413671
pass
36423672

3643-
# ── CLI workers (idle pool) — same patch_cache handler, just never got sent one ──
3644-
for py_exe, pool in list(self.idle_pools.items()):
3645-
try:
3646-
worker_sp = _resolve_target_paths(self.cm, py_exe).get("site_packages_path")
3647-
if sp_path is not None and worker_sp != sp_path:
3648-
continue
3649-
# Snapshot pool contents non-destructively
3650-
items = []
3651-
while not pool.empty():
3652-
try: items.append(pool.get_nowait())
3653-
except: break
3654-
for w in items:
3655-
try: pool.put_nowait(w)
3656-
except: pass
3657-
for w in items:
3658-
try:
3659-
w.process.stdin.write(patch_msg)
3660-
w.process.stdin.flush()
3661-
forwarded += 1
3662-
except Exception:
3663-
pass
3664-
except Exception:
3665-
pass
3666-
3673+
# CLI workers (idle_pools) excluded from patch_cache.
3674+
# They have no uv_ffi — only UV workers need cache coherency.
36673675
if forwarded:
36683676
safe_print(
36693677
f"[FS-WATCHER] External change — patched {forwarded} UV worker(s) "
@@ -4150,6 +4158,15 @@ def _run_uv(self, req: dict, conn: socket.socket):
41504158
# Fire in a daemon thread so we don't hold the UV lock.
41514159
def _install_ffi_bg(py_exe=python_exe):
41524160
try:
4161+
# Only install if genuinely missing — never reinstall a live .so
4162+
# Reinstalling mid-flight corrupts memory-mapped .so in other workers
4163+
try:
4164+
import omnipkg._vendor.uv_ffi as _chk
4165+
safe_print(f"[RUN-UV] uv_ffi already present — skipping auto-install",
4166+
file=sys.stderr)
4167+
return # already installed, nothing to do
4168+
except ImportError:
4169+
pass # genuinely missing, proceed with install
41534170
import subprocess as _isp, shutil as _ish
41544171
_uv = _ish.which("uv") or "uv"
41554172
_r = _isp.run(

0 commit comments

Comments
 (0)