The emacs flake, and what the error text already rules out

This commit is contained in:
Joseph Ferano 2026-09-14 07:32:21 +07:00
parent 0251cf4aaa
commit 330bfa1578

60
HANDOFF-emacs-flake.md Normal file
View File

@ -0,0 +1,60 @@
# The `test_emacs` flake
## The symptom
One run of `dune test --root .` exited 1; the next run on the same tree exited 0.
```
FAIL the client notices a stop nobody asked about
FAIL and the modeline says so, with the condition
Error: error ("flan dev: cannot reconnect to /tmp/build_997a01_dune/flan-emacs-dev.sock: make client process failed: Connection refused, ...")
```
## What the error text already rules out
Two mechanisms have produced something like this here before: a genuine
listen/connect ordering bug, and a five-second await against a cold
`llc`-and-link. Neither fits this text, and the reason is in the branches:
- **A 20s poll deadline that simply expired** leaves the connection *live*.
The two `FAIL`s would print and `flan-dev-restarts` would then succeed.
There would be no `Error:` line at all.
- **The daemon exiting** — the child program finishing, `serve` returning
`closed`, or any OCaml exception — runs `Fun.protect`'s finally in
`Dev.two_process`, which unlinks the socket. The client then takes the
`(not (file-exists-p ...))` branch and says *"nothing is listening on ...;
start `flan dev program.flan` again"*. A different message.
- **`Connection refused`** on a unix socket needs the path to exist *and* no
socket in listen state behind it. In `two_process` (lib/dev.ml:2514-2526)
`bind` and `listen` are adjacent, nothing else ever creates that path, and
the only close of the listening fd is in the same finally that unlinks. So
it is reachable essentially one way: **the daemon died without running its
finally — that is, it was killed by a signal.**
The machine was loaded (several compiler lanes building at once) and `/tmp`
filled to 100% the day before, so the OOM killer is the first candidate.
## The plan
1. Fix the observability first. `test_emacs.ml` throws away the one fact that
discriminates: the cleanup `waitpid` matches `| _ -> true` and drops the
status, and the temporary files are removed *before* the exit code is
checked, so the program's output is gone by the time anyone looks.
2. Reproduce by running `_build/default/test/test_emacs.exe` in a loop under
artificial load, recording the daemon's wait status, `df /tmp` and
`dmesg | tail` each iteration.
3. Fix the mechanism the evidence names.
4. Sweep the siblings — `test_repl.ml`, `test_dev.ml`, `test_cider.ml`. What
they share with `test_emacs.ml` is the discarded wait status, not a missing
connect retry: they already have retrying `connect` helpers.
## Open questions
- `lib/cimport.ml:1138` still spells its own `cachedir` under `$TMPDIR`, which
NEXT.md:694 flags as the last remnant of the cold-cache bug. Another lane
holds that file. It is both a per-run cold cache and a plausible filler of
`/tmp`.
## Status
Stub. Investigation in progress.