The two sweeps do not race a printing fixture against a clock

dev-chatty.flan outlives the surveys' twenty seconds by design, and unlike
dev-repl it prints while it does -- so the two backends stop at different
lines and the diff reports on scheduling rather than on lowering. It joins
dev-loop and dev-watch in the excluded-by-name list in both sweeps, with the
distinction written down.

rt_flush_out is guarded on __wasm__: the pipe it is careful about belongs to a
merged flan dev, which is only ever a native host, and wasm32 need not answer
for a descriptor mode its runtime may model differently.

And three comments that went false with the _exit: the atexit registration in
the merged entry point is no longer there for rt_die, which unlinks the socket
for itself now, so both places that said so say what it is actually left
covering.
This commit is contained in:
Joseph Ferano 2026-09-18 08:00:57 +07:00
parent 95058502f5
commit 8e5fcf54cc
4 changed files with 47 additions and 18 deletions

View File

@ -3146,15 +3146,19 @@ int flan_merged_program_state(void) {
* message has already sent two investigations in this repository to the wrong
* place. Gone is the honest state, and the client already has words for it.
*
* [atexit] covers exit(3), which is the compiler thread's own way out and the
* way out of a program that somehow returns from main. It does NOT cover the
* two places a program dies where it stands: rt_die in flan_rt.c and die_now
* in flan_agent.c both take _exit, because the atexit chain and the ELF
* destructors want the loader lock a dlopening listener thread may be holding
* and in this build that chain also holds OCaml's shutdown. Both of them
* therefore unlink this path by hand, which is why this function's body is
* written out three times in the tree rather than shared: it is four lines, and
* the alternative is a runtime that has to link against the daemon. */
* [atexit] covers exit(3), and what is left taking exit(3) here is narrower
* than it was: the two places a program dies where it stands rt_die in
* flan_rt.c and die_now in flan_agent.c both take _exit, because the atexit
* chain and the ELF destructors want the loader lock a dlopening listener
* thread may be holding, and in this build that chain also holds OCaml's
* shutdown. Both therefore unlink this path by hand, so the same four lines
* exist in three places rather than one sharing them would mean a runtime
* that links against the daemon, which is a worse trade than the repetition.
*
* What is left for this one is every exit(3) nobody planned an OCaml fatal
* on the compiler thread most of all, since [Stdlib.exit] ends in the C one.
* Four lines on a path nobody means to take is the right price for a socket
* that never sits on disk refusing connects. */
static void flan_merged_unlink_sock(void) {
const char *s = getenv("FLAN_DEV_SOCK");
if (s != NULL && *s != '\0') unlink(s);
@ -3234,9 +3238,9 @@ int main(int argc, char **argv) {
* own shutdown to that chain. The hand-written [flan_merged_unlink_sock] that
* used to sit under this function's _exit went with it for the same reason
* there is no way out of here any more to unlink on. The [atexit]
* registration above stays, because it is not for this path: it is for
* exit(3), which is what flan_rt.c's rt_die takes, and a merged daemon that
* dies through a trap must not leave a socket refusing connects behind it. */
* registration above stays, and no longer because of the trap: rt_die takes
* _exit now and unlinks for itself, exactly as die_now does. What is left
* for it is written at [flan_merged_unlink_sock]. */
for (;;) {
if (setjmp(program_return) == 0) {
rc = flan_program_main(argc, argv);

View File

@ -394,7 +394,9 @@ void flan_escape_bytes(const uint8_t *p, int64_t n, flan_slice *out) {
* redirected stdout is not, so without this the error appears above the output
* that led to it. */
#if !defined(__wasm__)
#include <fcntl.h>
#endif
#include <unistd.h>
/* That flush, made incapable of waiting.
@ -416,12 +418,24 @@ void flan_escape_bytes(const uint8_t *p, int64_t n, flan_slice *out) {
* The return value is ignored deliberately, twice over: a failed fcntl leaves
* the old blocking behaviour, which is what this code did before, and a flush
* that reports EAGAIN has done as much as it is going to. There is nothing a
* dying process can do about either. */
* dying process can do about either.
*
* Guarded on __wasm__, which this file otherwise does only for the valgrind
* client request below. The hazard is a pipe whose reader is
* the compiler thread of a merged `flan dev', which is a native host and only
* ever a native host a wasm32 module has no compiler beside it and no such
* pipe. So the guard is not a portability apology: it says where the problem
* can exist, and keeps wasm32 from having to answer for a descriptor mode its
* runtime may model differently. */
#if defined(__wasm__)
static void rt_flush_out(void) { (void)fflush(stdout); }
#else
static void rt_flush_out(void) {
int flags = fcntl(1, F_GETFL, 0);
if (flags >= 0) (void)fcntl(1, F_SETFL, flags | O_NONBLOCK);
(void)fflush(stdout);
}
#endif
/* [_exit] and not [exit], for the reason die_now gives in
* vendor/agent/flan_agent.c and gives at length: this runs on the game thread,

View File

@ -59,9 +59,11 @@ fi
corpus=${SURVEY_CORPUS:-$root}
out=$(mktemp -d); trap 'rm -rf "$out"' EXIT
# The two that run until something stops them, excluded by name for the reason
# the x86 sweep excludes them: a timeout cannot tell them from a hang.
forever="dev-loop dev-watch"
# The ones that run until something stops them, excluded by name for the reason
# the x86 sweep excludes them: a timeout cannot tell them from a hang -- and in
# dev-chatty's case cannot even give the two sides the same truncation, since
# it prints 4K a frame for as long as it is allowed to.
forever="dev-loop dev-watch dev-chatty"
TIMEOUT=${TIMEOUT:-20}

View File

@ -61,10 +61,19 @@ corpus=${SURVEY_CORPUS:-$root}
out=$(mktemp -d); trap 'rm -rf "$out"' EXIT
# The two that run until something stops them. Not a failure and not a match;
# The ones that run until something stops them. Not a failure and not a match;
# they are excluded by name because a timeout cannot tell them apart from a
# backend that hung.
forever="dev-loop dev-watch"
#
# dev-chatty is the third and is here for a sharper reason than the other two.
# It also outlives the timeout -- it is sized to outlast test_dev's checks and
# is killed with the connection -- but what makes it unusable here is that it
# *prints* while it does, 4K a frame. Two backends stopped by a clock stop at
# different lines, so the diff is a report about scheduling rather than about
# lowering, and it fails the alias every run. dev-repl outlives the timeout in
# the same way and is not listed, because it prints nothing and the two
# truncations are both empty.
forever="dev-loop dev-watch dev-chatty"
TIMEOUT=${TIMEOUT:-20}