Skip to content

grafel stop follow-ups: confirm on pid not socket, Linux installer un-does persistent stop, 60s vs 5s budget asymmetry #6050

Description

@cajasmota

Three residuals disclosed in #6049 (which fixed #6044). All LOW, none blocked the merge, all worth closing.

1. Probe() proves socket-death, not process-death

internal/daemon/service/manager.go:151 confirms a stop by dialling the socket. The shutdown
sequence is beginDrain()waitDrain(3s)listener.Close()<-acceptDone
connWG.Wait() (internal/daemon/server.go:794-800). Probe() goes false at listener.Close(),
while the process is still alive through the remaining tail — still holding the pidfile, still
supervising a live engine child.

Scenario: grafel stop with a stalled Rebuild RPC in flight — the issue's own stated worst case,
"mid-index, mid-link-pass, blocked on a long group-algo run". The socket closes, waitStopped
returns nil immediately, the CLI prints daemon stopped and exits 0, and the process is still up.

Bounded, which is why it is LOW rather than MEDIUM: defaultShutdownWatchdog = 5 * time.Second
(internal/daemon/server.go:867) force-exits the entire graceful tail, so the false-success window
is ≤~5s, and AcquirePIDFile reclaim arbitrates a racing grafel start.

Fix: the definitive axis is already in-tree and unused here — daemon.ReadPIDFile +
pidStillAlive, exactly what runDaemonRestart uses at internal/cli/watcher_ctl.go:121,128-136.
StatusInfo.PID is already populated. Confirm on the pid, not (or as well as) the socket.

2. The Linux half of the installer asymmetry

install.sh:254-264 was not touched by #6049. Linux does
systemctl --user list-unit-files | awk '/grafel/' then systemctl --user restart "$unit". A
disabled unit still appears in list-unit-files, and restart works fine on a disabled unit.

Net effect after #6049 made stop persistent:

  • macOS: upgrade enables + bootstraps → persistent stop fully reverted.
  • Linux: upgrade restarts → persistent stop partially reverted — running now, still
    disabled at boot. An inconsistent hybrid state.
  • Neither path tells the user their deliberate stop was undone; both print
    grafel updated and daemon restarted (install.sh:389).

There is also a policy question #6049 decided silently and should be stated: should upgrading the
binary resurrect a daemon the user persistently stopped?
grafel stop --help says the way back is
grafel start; curl | sh is now a second, undocumented way back on macOS. The macOS behaviour is
deliberate and pinned by TestInstallSh_RestartDaemon_MacOS_RecoversFromPriorStop — it just isn't
documented, and Linux does something different. No Linux equivalent test exists.

3. waitStopped's 60s budget versus the RPC path's 5s

internal/daemon/service/manager.go:71-76defaultReadiness.budget = 60s, used by all three
stopService implementations. The RPC path uses stopConfirmTimeout = 5 * time.Second
(internal/cli/watcher_ctl.go:585).

The success path is fast: Unload already waits for the socket, so the first probe()
short-circuits at manager.go:158. But on the failure path grafel stop now blocks for a full
minute before reporting. Correct-over-fast is defensible; the 12x asymmetry between the two paths
is unexplained, and a 60s hang on a stubborn daemon will read as a wedge. Either align them or
record why they differ.

Provenance

All three surfaced during the second adversarial review round on #6049 and were disclosed in that
PR rather than fixed, to keep the release-blocking fix scoped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Real defect, bounded blast radius: perf, test gaps, narrow correctness

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions