Skip to content

Commit dfef52d

Browse files
committed
The execve waiter lets go of every stream it holds
⚠️⚠️ A replacement leaves ONE image; this composition leaves two, and the one that remains still held every file description the caller had --- including the write end of a pipe it had just placed at the started program's standard output. A pipe reports the end of input when the LAST writer lets go, so the reader on the other side saw a stream that was still open, from a program that had ended. ⭐ Measured through a consumer: an MCP server that exits mid-request should be reported as "Connection closed" and was reported as "Timed out after 1000ms". The server was gone, nobody was writing, and the pipe stayed open because of a waiter neither side knew existed. ⚠️ THIS IS THE 0.10 DEFECT'S THIRD FACE. `kal_process_spawn_bound' was added because a SIGNAL reached the middle image; this is the middle image holding a RESOURCE. Same fact, same fix: make that image as invisible as it claims to be.
1 parent 199d4d9 commit dfef52d

3 files changed

Lines changed: 58 additions & 0 deletions

File tree

port/src/okm.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ void okm_unlock(void);
161161
/* The table. */
162162
int okm_fd_alloc(int from); /* the lowest free descriptor */
163163
void okm_fd_release(int fd);
164+
165+
/* Every stream this image holds, released. The one caller is the `execve'
166+
* composition, whose waiting image must not keep a pipe open on behalf of a
167+
* program that has already ended --- see the definition. */
168+
void __okm_close_all_for_exec(void);
164169
struct okm_desc* okm_desc_of(int fd);
165170
int okm_fd_bind(int fd, int kind, kal_uintptr stream,
166171
struct kal_file file, struct kal_dir dir, int flags);

port/src/okm_fd.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,25 @@ int okm_absolute(int dirfd, const char* path, char* out, size_t cap)
521521
if (d->path_slot < 0 || !g_dirpath[d->path_slot][0]) return -ENOENT;
522522
return join(g_dirpath[d->path_slot], path, out, cap);
523523
}
524+
525+
/* Every stream this image holds, released --- used by the one place that has an
526+
* image with nothing left to do.
527+
*
528+
* ⚠️⚠️ IT EXISTS BECAUSE `execve' HERE LEAVES AN IMAGE BEHIND. A replacement
529+
* leaves one image; this library composes it as start, wait, end, which leaves
530+
* two --- and the second still holds every file description the caller had. A pipe
531+
* reports the end of input when the LAST writer lets go, so a waiter holding the
532+
* write end kept a reader on the other side waiting for a program that had
533+
* already ended. Measured through a consumer: a server that exits mid-request was
534+
* reported as a timeout rather than as a closed connection.
535+
*
536+
* ⚠️ NOT A GENERAL `close everything'. The standard streams are released too,
537+
* which is right HERE and wrong anywhere else: this image writes nothing more.
538+
* The one caller is the `execve' composition, immediately before it waits. */
539+
void __okm_close_all_for_exec(void)
540+
{
541+
okm_lock();
542+
for (int fd = 0; fd < OKM_MAX_FD; fd++)
543+
if (g_fd[fd].desc >= 0) okm_fd_release(fd);
544+
okm_unlock();
545+
}

port/src/okm_syscall.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,37 @@ syscall_arg_t __okm_syscall(syscall_arg_t n, syscall_arg_t a1, syscall_arg_t a2,
18691869
const int e = __okm_spawn_common(&child, (const char*)a1, 0, 0,
18701870
(char* const*)a2, (char* const*)a3, 1);
18711871
if (e) return -e;
1872+
1873+
/* ⚠️⚠️ THE WAITER LETS GO OF EVERY STREAM IT HOLDS, AND WITHOUT THIS THE
1874+
* FAR END OF A PIPE NEVER SAW THE END OF INPUT.
1875+
*
1876+
* A replacement leaves ONE image. This composition leaves two, and the
1877+
* one that remains still holds every file description the caller had ---
1878+
* including the write end of a pipe it had just placed at the started
1879+
* program's standard output. A pipe reports the end of input when the
1880+
* LAST writer lets go, so as long as this waiter sat there, the reader on
1881+
* the other side saw a stream that was still open, from a program that
1882+
* had already ended.
1883+
*
1884+
* ⭐ Measured through a consumer: an MCP server that exits while a
1885+
* request is in flight should be reported as "Connection closed", and was
1886+
* reported as "Timed out after 1000ms" --- the client waited its full
1887+
* deadline for an end of input that this image was holding shut. The
1888+
* server was long gone; nobody was writing; the pipe stayed open because
1889+
* of a waiter neither side knew existed.
1890+
*
1891+
* ⚠️ THIS IS THE 0.10 DEFECT'S THIRD FACE. `kal_process_spawn_bound' was
1892+
* added because a SIGNAL reached the middle image; this is the middle
1893+
* image holding a RESOURCE. Both come from the same fact --- the
1894+
* composition has an image the interface never told anyone about --- and
1895+
* both are fixed by making that image as invisible as it claims to be.
1896+
*
1897+
* ⇒ Safe because this image does exactly two things afterwards: wait, and
1898+
* end with a status. The started program received what it needed at the
1899+
* spawn; the descriptors here are this image's own references and nothing
1900+
* reads them again. */
1901+
__okm_close_all_for_exec();
1902+
18721903
int st = 0;
18731904
if (do_wait4((int)child, &st, 0, 0) < 0) kal_exit(127);
18741905
/* The status the started program ended with, in the form this library

0 commit comments

Comments
 (0)