It read the generation, then a non-atomic length, then returned the buffer itself — and the agent sent those bytes down a socket some time later, while the game thread was free to be a hundred bytes into the next value. A seqlock cannot validate a read that finishes after it returns, so the pointer was the bug and not the ordering. Made into a real one rather than documented down to what it guaranteed, because what it guaranteed was nothing: the generation told the daemon a new value had arrived and said nothing about whether the bytes it then read were that value. Writing the honest comment would have left the daemon's only way of reading a result unsound with a note beside it. The counter is odd for exactly as long as a value is being written. flan_dev_result_read copies into the caller's buffer and checks the counter either side of the copy, retrying if it moved; a reader that loses the race reports the last complete generation and no bytes, so a daemon polling for a new value keeps polling rather than being shown half of one. The count handed out is the number of complete values, so "has it moved" still means what lib/dev.ml takes it to mean. The agent's buffer is RESULT_MAX, so the copy is never truncated. The race itself has no regression test. Arranging it means landing a socket read inside a render thunk from outside the process, which is the same hook the snapshot generation wants. What is tested is that eval still reads back the value it rendered, through test_dev's existing cases.
Description
Languages
OCaml
69.7%
Emacs Lisp
13.1%
C
10.6%
Standard ML
3.1%
HTML
2.3%
Other
1.2%