publish() wrote queue[head % QUEUE] without consulting tail, so the 65th module queued between two agent/poll calls landed on the slot the game thread was reading — twenty-four bytes of function pointers copied field by field with no atomic near them, so the consumer could take half of one job and half of another and call it. The comment claimed the overflow dropped the oldest request; nothing did that. A full ring is refused now, at the sender, before the dlopen. Dropping loses a reload the sender was told was ok, which is the same lie more quietly; blocking stalls the accept loop, which serves connections inline, so a program that had stopped polling would also stop answering status and abort — the dev loop would have no way to reach a program that had stopped listening to it. The check is separate from the store because there is one producer: room, once seen, cannot be taken away. Two smaller defects in the same file: A module with no flan_reload_install was refused and its handle dropped on the floor. Not an exception to "nothing is ever dlclosed" — that rule is about a module something points into, and this one installed nothing, so no cell names it. What leaked was the handle value rather than the mapping: dlopen refcounts by path, so re-sending the same bad file raised a count nothing could lower. exit(134) from the break loop runs the atexit chain and the ELF destructors, which want the loader lock the listener thread may be holding inside dlopen. A program asked to abort would hang instead of dying. _exit, with the streams flushed by hand at each call site. The deadlock itself is read rather than tested; what the tests pin is that the exit status is still 134. programs/agent-queue.flan blocks on stdin so the window is held open by the test rather than by a timer: it takes 64 modules, refuses the 65th with a reason, and installs 64 when it finally polls. noinstall.c's destructor prints while the program is still running, which is the only way to see the close — at exit the loader runs every destructor whether anything was closed or not. Both halves fail on the old code.
17 lines
793 B
C
17 lines
793 B
C
/* A shared object that is not a redefinition module: it loads, and it has no
|
|
* flan_reload_install for the agent to find.
|
|
*
|
|
* The destructor is the observation. The agent used to drop the handle on the
|
|
* floor when it refused a module like this one — the mapping stayed, the
|
|
* reference count went up, and the one handle that could have brought it down
|
|
* was gone. Now the module is closed, because nothing was installed from it
|
|
* and so nothing can point into it, and this line appears while the program is
|
|
* still running. Waiting for the program to exit would prove nothing: the
|
|
* loader runs every destructor at exit whether anything was closed or not. */
|
|
#include <stdio.h>
|
|
|
|
__attribute__((destructor)) static void unloaded(void) {
|
|
printf("unloaded\n");
|
|
fflush(stdout);
|
|
}
|