Skip to content

Fix engine detection, shutdown and test process hygiene - #56

Merged
JaredP94 merged 7 commits into
mainfrom
fix/engine-manager-hygiene
Nov 6, 2026
Merged

JaredP94 merged 7 commits into
mainfrom
fix/engine-manager-hygiene

Conversation

@JaredP94

@JaredP94 JaredP94 commented Nov 6, 2026 •

Copy link
Copy Markdown
Owner

Description

Fix five engine-manager hygiene problems found during 0.9.0 validation:

  • A stale oMLX launcher no longer counts as an installation without its app CLI target. Saved ON intent skips missing installations without attempting launch; desktop offers Install oMLX and TUI Engines offers i. No user engine files are cleaned up.
  • Command-mode stop confirms listener closure by default. Only oMLX opts into the new manifest capability runtime.stop.wait_for_exit, also confirming exit of the serving PID before restart; LM Studio's persistent host can remain alive after its HTTP server stops. The state mutex is not held across I/O or waits; existing lifecycle serialization is retained.
  • Initial Ollama downloads are pinned to v0.40.15, the latest published release at implementation time (above the 0.14.0 minimum). Vendor updates afterward are unchanged.
  • Every fake-engine invocation, copied binary and detached child is owned by its test, with process-group cleanup and survivor checks. TestMain fails and sweeps missed cleanup, scoped to this run.
  • Add minimal fail-closed identity and ordinary-wait build fallbacks for unsupported targets; FreeBSD compiles without becoming supported.

Bump engine-manager 1.4.0 → 1.4.1 relative to released 0.9.0. These are fixes, not a new service interface.

Ollama checksum provenance

All checksums are published in https://github.com/ollama/ollama/releases/download/v0.40.15/sha256sum.txt:

Platform Release artifact Published SHA-256
macOS amd64/arm64 Ollama-darwin.zip de3298b9897828480c361c101e3efd2d3c499d08882c8ee972df524064abc3db
Windows amd64 ollama-windows-amd64.zip 9ca74a7ab66ec86870b1eecac7df160df08dc4e3a291ab5d2d59bcf9310221c6
Windows arm64 ollama-windows-arm64.zip 35ce5195b57496dbc9ecbaa6593807e6ac26cbb53cf1f8bb57ac58daafa58574
Linux amd64 ollama-linux-amd64.tar.zst 628c018404a4206493efd38e46251c154ede5141b8b8dc85c659b3b027c47379
Linux arm64 ollama-linux-arm64.tar.zst 7f699ead5cdb4ae1ed89b888b148a238af188708800871e1889ab9dcee95f247

Downloaded all five artifacts and sha256sum.txt into a fresh empty directory under /tmp. Independently hashed every artifact with shasum -a 256 and Python hashlib; all match the published file. Nothing downloaded was executed or installed.

Scope

Only services/nvpair-engine-manager, its manifests/README, services/versions.json and the two fixed known-issue entries. No desktop, shared library, peer-address, product changelog or supported-platform changes. Six focused signed-off commits, including the review follow-up. User engine directories and installed apps were not touched.

Validation

Local macOS arm64, Go 1.27.3:

  • go test -race ./... — PASS (182s).
  • go test -count=1 ./... — PASS (176s).
  • go test -race -count=5 on TestOmlxStaleLauncherDoesNotRestore, TestCommandStopWaitsForProcessExitAfterPortCloses, TestCommandStopRetainsRunningOnTeardownTimeout, TestBundledOllamaDownloadsAreReleasePinned, TestFakeEngineCleanupOnFailedTest, TestFakeEngineTestMainSweepsMissedCleanup, and existing TestStopAllStopsDetachedCommandDaemon* — PASS.
  • GOOS=darwin/linux/windows/freebsd go vet ./... — PASS for all four.
  • GOOS=freebsd go build ./... — PASS.
  • gofmt -l . — empty; git diff --check — PASS.
  • node scripts/spdx-headers.mjs — 0 missing.
  • Reviewed desktop NodeEnginesInline and TUI Engines install handling: installed=false retains the existing installation affordances, so no desktop changes are needed.
  • Cross-process suite not run locally.
  • CI run 42205170749: four executed jobs passed — Desktop checks, SPDX headers, Go modules (ubuntu-latest), Cross-process tests (ubuntu-latest).
  • Downloaded cross-process-ubuntu-latest with gh run download into a new empty /tmp/pair-em-ci.* directory. python3 -I /tmp/pair-review/scripts/noskip.py <dir>/cross-process.jsonl returned 122 pass, zero skips, zero failures.
  • Cannot certify all six jobs: both Go modules (macos-15) and Cross-process tests (macos-15) never acquired a runner (empty runner name, zero steps). GitHub cancelled them after multiple acquisition attempts. Their annotations say: “The job was not acquired by Runner of type hosted even after multiple attempts” and report macOS arm64 capacity constraints. No test step failed; the overall run is unsuccessful due to these infrastructure cancellations. Local macOS module tests/race passed, but do not replace missing CI coverage.

Regression evidence before fixes:

  • TestOmlxStaleLauncherDoesNotRestore failed because Detect/Status/GetInstalled/listEngines/resolveEngine treated the stale launcher as installed, and restoration attempted execution.
  • TestCommandStopWaitsForProcessExitAfterPortCloses failed because Stop returned while the daemon was still shutting down.
  • TestBundledOllamaDownloadsAreReleasePinned failed for every platform's floating, unhashed download.
  • TestFakeEngineCleanupOnFailedTest failed because the fixture engine survived its failed test and test binary exit.
  • GOOS=freebsd go build ./... failed with undefined fileIdentity and watchDownloadExit before adding fallbacks.

Review follow-up — 8674a106a5100280793338e4e77dfb9984da4665

  • Added runtime.stop.wait_for_exit (boolean, default false), enabled only for bundled oMLX. Validation requires command mode, a stop command, and a readiness probe. Documented in MANIFEST.md and README.md. Existing deadlines and state-lock boundaries are unchanged, including failed-start cleanup.
  • TestCommandStopAndRestartWithPersistentHost failed on d51a12e with the listener closed and the host alive; passes with the fix, including start after stop and Restart on the same host PID.
  • TestCommandStopWaitsForProcessExitAfterPortCloses and TestCommandStopRetainsRunningOnTeardownTimeout retain explicit opt-in coverage; TestValidateStopWaitForExit covers accepted/rejected declarations and TestBundledStopWaitForExit checks bundled policy.
  • Full go test -race -count=1 ./... passed before rebase (179s) and after rebase (183s). Full go test -count=1 ./... passed after rebase (177s). Vet passed for darwin/linux/windows/freebsd; FreeBSD build passed. gofmt output empty, diff check clean, SPDX zero missing, and pgrep -lf fake-engine found no survivors.
  • Rebased onto origin/main including Let paired machines reconnect after address changes #55, preserving the higher version of each component; engine-manager remains at the PR's unreleased 1.4.1. Force-pushed with an explicit expected-head lease.
  • New-head CI run 42210912511, head 8674a106a5100280793338e4e77dfb9984da4665: four executed jobs passed — Desktop checks, SPDX headers, Go modules (ubuntu-latest), and Cross-process tests (ubuntu-latest). Downloaded cross-process-ubuntu-latest with gh run download into fresh empty /tmp/pair-pr56-ci-8674a106.iTtcwc; python3 -I /tmp/pair-review/scripts/noskip.py /tmp/pair-pr56-ci-8674a106.iTtcwc/cross-process.jsonl reports 123 passes, zero skips, zero failures.
  • Cannot certify all six jobs: Go modules (macos-15) and Cross-process tests (macos-15) never acquired a runner and were cancelled by GitHub at 11:46:43 UTC. Both have empty runner names and zero steps. Annotations state “The job was not acquired by Runner of type hosted even after multiple attempts” and cite macOS arm64 capacity constraints. Overall run conclusion is failure due to those infrastructure cancellations, not a failed test step. Local macOS unit/race results do not replace unexecuted CI coverage.

Risk

Process-exit confirmation is manifest-declared and enabled only for bundled oMLX. Other engines retain listener-only confirmation, so LM Studio stop/restart does not wait for its persistent host. For opted-in engines, PID lookup is best-effort: if the host cannot resolve the serving PID, stop can confirm only transport closure. Lookup remains bounded by existing platform tool timeouts; stop CLI uses max(grace_s, 5s), then shutdown confirmation uses grace_s (default 5s). A timeout does not falsely mark stopped. The lifecycle serialization lock remains in place; no state mutex is held during waits.

The Ollama pin controls only a fresh install and is not runtime artifact certification or a block on vendor self-updates. No real engine install or live-engine smoke test was performed. FreeBSD is compilation-only, not runtime support.

Checklist

  • I have read the Contributing Guidelines.
  • Every commit is signed off (git commit -s), certifying the Developer Certificate of Origin.
  • New or existing tests cover the change.
  • Relevant documentation is updated.
  • I checked the diff, changed filenames, and commit messages for credentials, private data, internal URLs, internal issue identifiers, and generated artifacts.
  • I recorded the validation commands and results above.
  • I bumped any affected component in services/versions.json, and described user-visible changes above so they reach the release notes.

@JaredP94
JaredP94 force-pushed the fix/engine-manager-hygiene branch from d51a12e to 8674a10 Compare November 6, 2026 11:31
Require the app CLI target as well as a declared launcher. Missing installations keep saved ON intent but are skipped at launch, leaving Install available in desktop and TUI without modifying user files. Bump engine-manager from released 1.4.0 to 1.4.1 for these fixes.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
A control CLI can close its listener before the serving process finishes tearing down. Capture that PID before stop and require its exit as well as consecutive connection refusals within the existing stop grace, without holding the state mutex. Preserve running state on timeout rather than allowing immediate restart to race pending shutdown.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Use Ollama v0.40.15, the newest published release, for every bundled platform. The five artifact hashes come from its GitHub release sha256sum.txt and match fresh downloads independently hashed without execution. Initial installation is pinned; vendor self-updates are unchanged.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Give every fake invocation and copied executable a per-test owner, with early PID registration inherited by detached children. Test cleanup kills owned process groups and checks for survivors, and TestMain fails and sweeps any missed registered children or launchers under this run directory. Identity checks keep sweeps scoped to this run. Exercise both fatal-test cleanup and the final missed-cleanup safety net.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
FreeBSD compilation had no fileIdentity or watchDownloadExit implementation. Fail closed for install identity and use ordinary reaping on unsupported targets, without claiming engine or platform support.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
@JaredP94
JaredP94 force-pushed the fix/engine-manager-hygiene branch from 8674a10 to 9a6dbbb Compare November 6, 2026 11:59
Settings rollback replaces the entire runtime while a canceled health goroutine may still be starting. Read and copy its probe under the same state mutex used by rollback, then release the mutex before timers or network probes.

Add a concurrent runtime-restoration regression that exposes the race on both main and the original PR head. The existing rollback test passed 50 race-enabled repetitions on both revisions; after the fix it still passes 50 repetitions, as does the new regression. Two uncached full race suites and darwin, linux, windows, and freebsd vet checks pass. Keep the PR's existing engine-manager version bump.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
…ders

Settings rollback replaces Runtime under the state mutex while an already
admitted passive status probe can still be detecting executables. Revision
fencing prevents stale publication but does not synchronize those reads.

Add engineState.platformSnapshot and use it for passive status/Detect, CLI
and remove-path actions, profile residency observations, health startup and
live-platform capabilities. Move capability filesystem probes outside mu.
The copy is shallow: Detect, DetectRequires, Install, Uninstall, and all
Runtime slice/map/pointer referents (Ready, Health, Stop, EditableLaunch,
Args, Env, Start, LaunchArgs and LaunchEnv) are immutable after publication.
Settings replace argument/environment pointers or Runtime as a whole;
SetPort replaces only Port. installDir/modelsDir are fixed at construction.
Test fixtures that customize referents do so before concurrent consumers.

Audit of direct platform readers retained with their existing locks:
- st.mu: platformSnapshot; refreshPassiveStatus's Ready and mode captures;
  reconcilePresence's Ready capture; stopEngines's shutdown mode check;
  launchStateLocked's ServerPort capture; SetPort's platform nil checks
  and Port restoration/update; ConfigureLaunch's apply/rollback writes.
- st.opMu: Install's mode and Install reads; Uninstall's Uninstall/mode
  reads and installCreates(st.plat); doStart's port/Runtime reads and
  controlCLIPath(st.plat); doStop's Runtime capture; runCommandStop's
  controlCLIPath(st.plat); reconcileFailedCommandStop's Runtime capture;
  reconcilePresence's mode check; runLMSLoad's controlCLIPath(st.plat)
  (called by locked lmsModelAction or doStart -> restoreLMSModels);
  launchForState's Runtime; launchStateLocked's editable-launch and
  adopted-policy checks; previewLaunchLocked's port/Runtime; and
  ConfigureLaunch's previousRuntime, stopAttempt Runtime and persistence
  comparisons. All settings apply/rollback paths hold the same opMu.
- state initializes from a local Platform copy before publication under
  Executor.mu. Registry PlatformFor/resolveEngine/capabilitiesFor consume
  the immutable registry, not engineState's replaceable Runtime.

Readiness uses the Runtime captured by doStart under opMu; its cancellation
watcher reads no platform. Process-output callbacks retain that private
Runtime copy; exit watchers and process-stop goroutines read no platform.
Command-stop cleanup remains synchronous under opMu. All three production
health launch sites check Health first, and settings updates preserve that
immutable pointer, including rollback. runHealth additionally snapshots and
returns safely for nil Health instead of relying on the caller invariant.

TestPassiveStatusConcurrentRuntimeRestore reproduces the status and Detect
races on 7fea529 under -race. TestHealthStartupWithoutProbe reproduces the
previous nil dereference. Keep TestSettingsApplyRestoresPreviousState and
TestHealthStartupConcurrentRuntimeRestore unchanged. Validate the four
regressions with -race -count=30, run the full race suite twice without the
test cache, and vet darwin/linux/windows/freebsd. No additional version bump:
the PR already contains the engine-manager bump.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
@JaredP94
JaredP94 merged commit c04fd85 into main Nov 6, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant