← Commit history

dockbar guide: correct the stale-serve root cause (a race, measured), argv survives the unlink and exe does not

John Lauer ·f3f0c0688d ·2mo ago ·parent fa525fa
1 file changed +21−13
docs/dockbar.md+21−13
@@ -201,25 +201,33 @@ Version-aware serve is what makes an update actually reach the user: a fresh bui the older instance on its port and stand it down. The fragile part is HOW you identify the victim. -A package update **unlinks the old binary and writes a new file at the same path**. From-that moment the still-running old process resolves `/proc/self/exe` (and anything derived-from its own path) to `"<path> (deleted)"`, and `stat` or `exec` of that string fails. So-victim-matching that compares executable paths or exact argv silently stops matching exactly-when it matters: right after an update, which is the only time you need it. The old build-keeps the port, the dock's Hard Refresh Open re-runs your steps, your serve decides there is-nothing to retire, and the user's tab renders yesterday's code while `pkg update` reports-success. (Diagnosed on chip-fetcher, 2026-08-24.)+A package update **unlinks the old binary and writes a new file at the same path**. Measured+on chip-fetcher (2026-08-24), not inferred: after that unlink-rewrite, `/proc/<pid>/cmdline`+argv[0] stays CLEAN, while `/proc/<pid>/exe` gains a `" (deleted)"` suffix and `stat` or+`exec` of that string fails. So **exe-path matching breaks and argv matching survives** the+rewrite itself.++The stale server that started this investigation had a different cause, worth knowing because+it defeats correct victim-matching: **a race**. The package is updated while the launch steps+run, so the `serve` you invoke can be a process still running the OLD binary; it finds a+server on its port reporting a version that matches its own (old) idea of current, decides+there is nothing to retire, and reuses it. The user's tab then renders yesterday's code while+`pkg update` reports success. The old build keeps the port and nothing in the chain is+obviously wrong.  Do this instead: -- Identify the victim by **who owns the port**, not by argv equality.+- Identify the victim by **who owns the port**: resolve the LISTEN socket's inode from+  `/proc/net/tcp{,6}` and match it against `/proc/*/fd`. Never yourself, never your parent.+- **Check whether YOU are the installed build** before deciding anything, since you may be+  the stale process. If you are not, hand off rather than reuse (guard against loops). - Confirm identity by asking the candidate what it is (`--version`, or a `/health` that   reports `installed_version`), rather than by string-matching its command line.-- If you must compare paths, **strip a trailing `" (deleted)"`** before comparing.-- Never retire a process you cannot positively identify as your own older build, and never-  retire yourself or your parent.+- If you must compare paths, **strip a trailing `" (deleted)"`** before comparing, and prefer+  argv[0] over `/proc/<pid>/exe`, which is the one that gains the suffix. - Belt and braces: have the running server watch its own executable and hand the port over-  once a replacement verifies, so a user who never re-clicks still converges.+  once a replacement verifies, so a user who never re-clicks still converges. chip-fetcher's+  laptop converged 1.4.2 to 1.4.4 unaided in about 40 seconds this way.  ### The three rules that break every first attempt