fix(openviking): don't spawn a second server onto a live port (#74846) - #74959
fix(openviking): don't spawn a second server onto a live port (#74846)#74959jeff-mettel wants to merge 1 commit into
Conversation
`_start_local_openviking_server()` spawned `openviking-server` unconditionally. Both callers — `initialize()` and the runtime unreachable handler — reach it from a health probe, and that probe can time out client-side while the server is up and serving. The spawned process then loses the data-directory lock and exits immediately with `DataDirectoryLocked`; because the probe keeps timing out, the cycle repeats every cooldown window (~5 min observed). The existing 30s `_failed_refresh` cooldown paces the loop but cannot stop it, since it expires while the underlying condition persists. Probe the target host:port before spawning and treat an occupied port as already-started. This guards both call sites at their single convergence point. The probe deliberately tests only that a listener owns the port — enough to know a second server would lose the lock — and says nothing about that listener's health. The parse/probe now precedes the PATH lookup, so a reachable server is reported as running even when `openviking-server` is not on PATH. Fixes NousResearch#74846 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for the focused fix. Current main still calls Automated hermes-sweeper review. |
|
Merged via #77747 — your commits cherry-picked with authorship preserved (rebase merge). This PR consolidated your port-occupancy guard and compression re-arm fixes together with 4 other OpenViking fixes into one coherent integration so the complete runtime behavior could be validated as a unit. Thanks for the contributions! |
What & why
Fixes #74846.
_start_local_openviking_server()spawnsopenviking-serverunconditionally. Both call sites reach it from a health probe:initialize()_handle_runtime_openviking_unreachable()unreachable_VikingClient.health()can time out client-side while the server is genuinely up (curl /healthreturns 200). That producesunreachable, which spawns a second server; the new process loses the data-directory lock and exits immediately withDataDirectoryLocked. Since the probe keeps timing out, this repeats every cooldown window — the reporter measured a spawn roughly every 5 minutes, ~18DataDirectoryLockedper day.The existing 30s
_failed_refreshcooldown paces the loop but can't stop it: it expires while the underlying condition persists. A longer cooldown would only slow the bleeding, so this fixes the spawn decision itself.The change
Probe the target
host:portbefore spawning; an occupied port means "already started". Both call sites converge on this function, so one guard covers every path.Two deliberate details:
shutil.which()lookup. A reachable server is reported as running even whenopenviking-serverisn't onPATH(e.g. started by systemd or another venv). Previously that returned "not found on PATH" despite a perfectly good server listening.No change to the fallback-to-disabled behavior when nothing is listening and no binary exists.
Tests
tests/plugins/memory/test_openviking_provider.py:..._does_not_spawn_when_port_already_open—Popenraises if called..._reports_running_server_without_cli_on_path— probe outranksPATH..._rejects_unparseable_url_before_probing— no probe on a bad endpoint_local_openviking_port_is_openagainst a real bound loopback socket, then against the same port after close (true/false without mocking the syscall)The pre-existing
..._uses_endpoint_host_and_porttest now stubs the probe explicitly rather than depending on nothing happening to listen on port 1934 in CI.Platforms
Tested on macOS 15 (Darwin 25.5.0), Python 3.11.
socket.create_connection+OSErrorhandling is platform-neutral; the 2s budget only bounds a wedged listener, since a loopback connect resolves or refuses far below it.Duplicate check
gh search prsforDataDirectoryLockedreturns no open or closed PRs, and no open PR references #74846.Authored by an AI agent (Claude Opus 5) operating autonomously on @jeff-mettel's behalf: the defect was traced, the patch written, and the tests run and verified end-to-end before submission.