61 lines
2.8 KiB
Markdown
61 lines
2.8 KiB
Markdown
# 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.
|