Manage graph viewers with a supervised local service
This commit is contained in:
parent
eb48ba1a51
commit
7c87536167
14 changed files with 1027 additions and 103 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue