1
0
Fork 0
Code Issues Pull requests Projects Releases 2 Packages Wiki Activity Actions Pages

Manage graph viewers with a supervised local service

This commit is contained in:
Andraxion 2026-07-25 00:17:21 -04:00
parent eb48ba1a51
commit 7c87536167
14 changed files with 1027 additions and 103 deletions

View file

@ -3,7 +3,6 @@
from __future__ import annotations
import atexit
import fcntl
import json
import os
import secrets
@ -25,6 +24,11 @@ from pathlib import Path
from time import monotonic
from typing import cast
try:
import fcntl
except ImportError: # pragma: no cover - retained only for the deprecated direct runner.
fcntl = None
from .errors import DocForgeError
from .index import APPLICATION_ID, INDEX_SCHEMA_VERSION, ProjectIndex, re_tokenize
from .project import project_root_fingerprint
@ -395,6 +399,7 @@ class VisualizationRunner:
self._lease_thread: threading.Thread | None = None
self._lease_stop = threading.Event()
self._lease_last_activity = monotonic()
self._activity_last_seen = time.time()
self._lease_connected = False
self._atexit_registered = False
self._reader: VisualizationIndexSnapshot | None = None
@ -462,6 +467,7 @@ class VisualizationRunner:
self._server = _VisualizationHttpServer(("127.0.0.1", 0), Handler)
self._lease_last_activity = monotonic()
self._activity_last_seen = time.time()
self._lease_connected = False
self._lease_stop.clear()
self._thread = threading.Thread(
@ -544,6 +550,7 @@ class VisualizationRunner:
if self._server is None:
return
self._lease_last_activity = monotonic()
self._activity_last_seen = time.time()
self._lease_connected = True
def _monitor_lease(self) -> None:
@ -573,8 +580,8 @@ class VisualizationRunner:
include_body=include_body,
)
return
self._touch_lease()
if parsed.path in {prefix, f"{prefix}/"}:
self._touch_lease()
self._respond(
handler,
_GRAPH_BROWSER_HTML.encode("utf-8"),
@ -586,18 +593,27 @@ class VisualizationRunner:
try:
params = urllib.parse.parse_qs(parsed.query, keep_blank_values=True)
reader = self._current_reader()
if parsed.path == f"{prefix}/api/overview":
if parsed.path == f"{prefix}/api/health":
with self._lock:
last_activity = self._activity_last_seen
payload = reader.result(viewer="alive", last_activity_at=last_activity)
elif parsed.path == f"{prefix}/api/overview":
self._touch_lease()
payload = reader.overview()
elif parsed.path == f"{prefix}/api/heartbeat":
self._touch_lease()
payload = reader.result(
viewer="alive",
lease_seconds=self.lease_seconds,
)
elif parsed.path == f"{prefix}/api/search":
self._touch_lease()
payload = self._search(reader, params)
elif parsed.path == f"{prefix}/api/filter":
self._touch_lease()
payload = self._filter(reader, params)
elif parsed.path == f"{prefix}/api/node":
self._touch_lease()
payload = self._node(reader, params)
else:
self._respond_error(
@ -770,6 +786,11 @@ class PersistentVisualizationRunner:
@contextmanager
def _locked_registry(self) -> Generator[None, None, None]:
if fcntl is None:
raise DocForgeError(
"visualization_unavailable",
"The deprecated direct visualization runner is unavailable on this platform",
)
self._cache_root.mkdir(parents=True, exist_ok=True)
with self._lock_path.open("a+", encoding="utf-8") as handle:
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)