mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-22 16:25:58 +00:00
fix(cli): serialize concurrent web-UI builds with an exclusive flock
Concurrent dashboard boots (desktop retry loop) each spawned their own npm install + vite build over the same tree; parallel builds starved each other, the dist sentinel never advanced, and every boot re-triggered the build — cascading into orphan backends, port collisions, and CPU storms that also knocked out the Telegram gateway's heartbeat (2026-07-12). One process now builds under flock; the rest serve the existing dist (stale is acceptable) or wait for the first-ever build to finish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e404d6cef9
commit
3a9b2f2f84
1 changed files with 45 additions and 0 deletions
|
|
@ -5151,6 +5151,51 @@ def _run_npm_install_deterministic(
|
|||
|
||||
|
||||
def _build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
|
||||
"""Build the web UI frontend if npm is available, serializing across processes.
|
||||
|
||||
Concurrent dashboard boots (e.g. the desktop app's retry loop after a
|
||||
readiness timeout) used to each spawn their own ``npm install`` +
|
||||
``vite build`` over the same tree; the parallel builds starved each
|
||||
other, none finished, the dist sentinel never advanced, and every new
|
||||
boot re-triggered the build. One process builds under an exclusive
|
||||
flock; the rest serve the existing dist (stale is acceptable) or, when
|
||||
no dist exists yet, block until the builder finishes.
|
||||
"""
|
||||
if not (web_dir / "package.json").exists():
|
||||
return True
|
||||
if not _web_ui_build_needed(web_dir):
|
||||
return True
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
# Windows: no flock — fall through to the unserialized build.
|
||||
return _do_build_web_ui(web_dir, fatal=fatal)
|
||||
project_root = web_dir.parent.parent if web_dir.parent.name == "apps" else web_dir.parent
|
||||
dist_index = project_root / "hermes_cli" / "web_dist" / "index.html"
|
||||
try:
|
||||
lock_file = open(project_root / ".web_ui_build.lock", "a")
|
||||
except OSError:
|
||||
return _do_build_web_ui(web_dir, fatal=fatal)
|
||||
try:
|
||||
try:
|
||||
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except OSError:
|
||||
if dist_index.exists():
|
||||
# Another process is already building — serve the current
|
||||
# dist instead of piling a second build onto the same tree.
|
||||
return True
|
||||
# No dist at all (first-ever build): wait for the builder.
|
||||
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
||||
# Re-check under the lock: the previous holder may have finished
|
||||
# the build while this process was waiting or racing for it.
|
||||
if not _web_ui_build_needed(web_dir):
|
||||
return True
|
||||
return _do_build_web_ui(web_dir, fatal=fatal)
|
||||
finally:
|
||||
lock_file.close()
|
||||
|
||||
|
||||
def _do_build_web_ui(web_dir: Path, *, fatal: bool = False) -> bool:
|
||||
"""Build the web UI frontend if npm is available.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue