serve mux: fix Windows request dispatch — BINARY-mode stdin/stdout + PeekNamedPipe-only polling (fixes #195)
Two Windows-only bugs in run_serve_mux left the gateway hanging after READY: 1. No _setmode(_O_BINARY): the CRT collapsed CRLF inside fread() payloads (waits forever for missing bytes) and expanded LF in the READY/STAT sentinels. 2. WaitForSingleObject on an anonymous pipe is undefined (always-signaled or WAIT_FAILED) and PeekNamedPipe fails on file/console handles, so the dispatch gate never opened. New rule: idle -> block in getline (POSIX select(NULL) semantics); active -> PeekNamedPipe poll. Reproduced and verified on real Windows via MinGW cross-compile + WSL interop: old binary writes READY (with CRLF corruption) then hangs forever on a crafted SUBMIT frame; fixed binary answers DONE + STAT and exits cleanly. Linux path untouched (0 warnings, oracle-exact). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3418,6 +3418,14 @@ static void run_serve_mux(Model *m, const char *snap){
|
||||
KVState *initial=m->kv; free(initial->kv_start); free(initial);
|
||||
ServeCtx *ctx=calloc(nctx,sizeof(*ctx)); ServeReq *req=calloc(nctx,sizeof(*req));
|
||||
for(int i=0;i<nctx;i++) serve_ctx_init(m,&ctx[i],snap,i,maxctx);
|
||||
#ifdef _WIN32
|
||||
/* Same byte-exact protocol as run_serve: in TEXT mode the CRT collapses CRLF in
|
||||
* fread() payloads (waits forever for the missing bytes) and expands LF on the
|
||||
* way out (corrupting the READY/STAT sentinels). BINARY on both ends. (#195) */
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
#endif
|
||||
setvbuf(stdin,NULL,_IONBF,0);
|
||||
printf("\x01\x01READY\x01\x01\nSTAT 0 0.00 0.0 %.2f\n",rss_gb()); fflush(stdout);
|
||||
hwinfo_emit(m);
|
||||
@@ -3441,16 +3449,16 @@ static void run_serve_mux(Model *m, const char *snap){
|
||||
#elif defined(_WIN32)
|
||||
HANDLE ih=(HANDLE)_get_osfhandle(_fileno(stdin));
|
||||
DWORD avail=0;
|
||||
/* WaitForSingleObject(handle, 0) is non-blocking and works on both
|
||||
* pipe and console handles; for pipes PeekNamedPipe gives the byte
|
||||
* count. Either returns "data is there right now". When a decode
|
||||
* is active we poll (timeout 0); when idle we block until input. */
|
||||
if(active){
|
||||
ready=(WaitForSingleObject(ih,0)==WAIT_OBJECT_0)?1:0;
|
||||
} else {
|
||||
ready=(WaitForSingleObject(ih,INFINITE)==WAIT_OBJECT_0)?1:0;
|
||||
}
|
||||
if(ready && PeekNamedPipe(ih,NULL,0,NULL,&avail,NULL) && avail>0)
|
||||
/* Anonymous pipes are NOT waitable objects: WaitForSingleObject on them is
|
||||
* undefined (always-signaled or WAIT_FAILED), and PeekNamedPipe fails on
|
||||
* file/console handles — the old gate never dispatched (#195). New rule:
|
||||
* idle -> block in getline() inside mux_submit (same semantics as the
|
||||
* POSIX select(NULL)); active -> poll the pipe with PeekNamedPipe, and on
|
||||
* non-pipe stdin just defer submits until the batch finishes. */
|
||||
if(eof) ready=0;
|
||||
else if(!active) ready=1;
|
||||
else ready=(PeekNamedPipe(ih,NULL,0,NULL,&avail,NULL) && avail>0)?1:0;
|
||||
if(ready)
|
||||
#endif
|
||||
if(mux_submit(m,&T,ctx,req,nctx,maxctx,eos)<0) eof=1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user