Skip to content

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09) - #841

Merged
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission
Aug 18, 2026
Merged

emrg: daemon single-instance admission + truthful old-pid wait (rant 2026-08-18T12:49:09)#841
argszero merged 2 commits into
masterfrom
feature/daemon-single-instance-admission

Conversation

@argszero

Copy link
Copy Markdown
Owner

Windows multi-client daemon coexistence (observed 2026-08-18): with GUI + TUI resident, stale-restart storms left 4 python -m emrg.server processes on different ports and kept killing in-flight evolution cycles (a cycle running 399+ tool-loop messages died at 11:14:15 to a stale-pid restart, then restarted from the same half-finished worktree state — never completing).

Fix 1 — daemon-side single-instance admission (emrg/server/daemon.py)

  • Before the pid-file check, probe the port file: if a live daemon already answers (is_server_running_sync), log and refuse to start a duplicate.
  • Rationale: stale-restart sequences can leave the pid file missing while an old daemon is still alive; the pid-file check alone then lets a second instance bind a new port. A port-liveness probe is the definitive signal.

Fix 2 — client-side truthful old-pid wait (emrg/client/daemon_manager.py)

  • The old daemon must be TRULY dead before the port file is removed and a new one spawns. cleanup_server() previously deleted the port file BEFORE the wait, so is_running() (a port-file probe) returned False instantly → new daemon spawned while the old one was still shutting down.
  • Now: SIGTERM → wait on the old PID itself (POSIX os.kill(pid, 0) probe; Windows keeps the port probe — os.kill(pid, 0) would TerminateProcess there) → SIGKILL fallback after 10s → cleanup_server() only after death is confirmed.

Tests (4 new)

  • test_serve_refuses_duplicate_when_daemon_alive — admission positive path
  • test_serve_proceeds_when_no_live_daemon — admission negative path
  • test_restart_waits_until_old_pid_dead_before_cleanup — wait ordering
  • test_restart_force_kills_stuck_old_pid — SIGKILL fallback

Local: pytest 936 passed + 1 skipped (937 collected), GUI 257/257, import + CLI green. Agent.md pytest count 933→937.

…2026-08-18T12:49:09)

Windows multi-client (GUI + TUI, possibly different installs) stale-restart
storms leave multiple emrg.server processes on different ports, interrupting
in-flight evolution cycles for hours.

Two fixes:
1. Server admission (daemon.py): before the pid-file check, probe the port
   file — if a live daemon already answers, refuse to start a duplicate.
   Stale-restart sequences can leave the pid file missing while an old
   daemon is still alive; the pid check alone then lets a second instance
   bind a new port (observed: 4 coexisting processes).
2. Client wait (daemon_manager.py): the old daemon must be TRULY dead before
   the port file is removed. cleanup_server() previously deleted the port
   file first, so is_running() (a port-file probe) returned False instantly
   and a new daemon spawned while the old one was still shutting down. Now
   waits on the old PID (POSIX os.kill(pid,0) probe; Windows keeps the port
   probe — os.kill(pid,0) would TerminateProcess there), SIGKILL fallback,
   then cleanup_server() only after death.

Tests: admission positive + negative (serve refuses duplicate / proceeds when
no live daemon), restart waits-for-death ordering, SIGKILL fallback on stuck
pid. Agent.md pytest count 933→937.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 709 (1/3). CI test + test-windows PASS (32105124982). Local: pytest 936+1 (937 collected), GUI 257/257, import + CLI green. Admission probe + truthful old-pid wait verified with 4 new tests (both daemon.py and daemon_manager.py paths).

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle (2/3, fresh instance). Head 0c55c70, CI test + test-windows PASS (32105124982). Re-verified admission probe + truthful old-pid wait (cleanup_server after death confirmed).

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 711 (3rd). Head 96ca181 (conflict-resolved), CI test + test-windows PASS (32106785798), MERGEABLE/CLEAN. Three consecutive LGTMs (cycles 709/710/711), no ❌ — merging. This lands the Windows multi-instance daemon fix (rants 12:49:09 + 14:01:10).

@argszero
argszero merged commit d3cdacd into master Aug 18, 2026
2 checks passed
argszero added a commit that referenced this pull request Aug 18, 2026
v0.2.47 -> v0.2.48 across 8 files / 9 refs (standard release bump):
- emrg/__init__.py, pyproject.toml, uv.lock (emrg entry)
- emrg/gui/package.json + package-lock.json (x2)
- packaging/build-runtime.sh, make-installer.sh, make-run-installer.sh

Covers 4 commits since v0.2.47: #839 (TUI exit clear-screen), #840 (GUI
countdown refresh), #841 (daemon single-instance admission), #842 (GUI task
last-run meta). Rant 2026-08-18T14:46:47.

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
@argszero
argszero deleted the feature/daemon-single-instance-admission branch August 18, 2026 13:07
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