Repository navigation
fix(swarm): keep a Windows lane alive on the stdin pipe it watches - #151
Conversation
Every lane of an evo-swarm on Windows died about a second after it started listening, and the coordinator reported each one as [lane 1] init-failed — ERROR 12029: Cannot connect 12029 is ERROR_WINHTTP_CANNOT_CONNECT, so this read as a networking fault, but the lane's ready file and its own lane.log both showed a listener that had come up and then gone: the lane's process was gone before the coordinator's first request could be answered. A lane is launched with --watch-stdin and stdin a pipe the coordinator holds, so the lane can never outlive the swarm: end of file on that pipe is its shutdown. watch-stdin-eof parks a thread on the stream make-stdin-stream returns and reads it with read-byte. On Windows make-stdin-stream returned a *character* stream whenever stdin was not a console — which is exactly a lane's case, a pipe. read-byte on a character stream signals a type error rather than returning a byte, and the handler around that read treated any error as end of file, so every lane shut itself down at once. Measured: with --watch-stdin the lane exits within a second of writing its ready file; without it, it stays up. Two changes, one cause each: - make-stdin-stream's Windows pipe branch is now a byte stream (:element-type '(unsigned-byte 8)), which is the only kind of stream its one reader can read. The console branch is untouched: on a console it still returns the console input handle, which drain-console-input reads as key events. The character branch had no remaining caller — read-available-input's Windows character path only ever saw a console handle — so untranslated-newlines and that branch are gone, and read-available-input reads bytes everywhere but the console. - watch-stdin-eof calls its thunk only on a genuine end of file. A read that fails for any other reason leaves the thread with nothing to watch and returns; this signal is a lane's whole life, and a stream that cannot be read is not a parent that went away. Verified against real lanes on Windows: with --watch-stdin the lane now stays up and answers, both lanes of a two-lane swarm reach idle with restarts 0 and a ready model, a delegated task runs in the lane with a real model call, and — the contract the flag exists for — closing the coordinator's end of the pipe still shuts the lane down cleanly (exit 0, ready file deleted). The regression check is test-stdin-stream-for-watching, which fails on the old element type. Co-authored-by: EvoAgent <evo@ruiqilei.com>
|
The Unmodified Reproduced by dispatching CI on
Co-authored-by: EvoAgent evo@ruiqilei.com |
Every lane of an evo-swarm on Windows died about a second after it started listening, and the coordinator reported each one as:
12029 is
ERROR_WINHTTP_CANNOT_CONNECT, so this read as a networking fault — but the lane'sready.jsonand its ownlane.logboth showed a listener that had come up and then gone. The lane's process was gone before the coordinator's first request could be answered.Cause
A lane is launched with
--watch-stdinand stdin a pipe the coordinator holds, so a lane can never outlive the swarm: end of file on that pipe is its shutdown.watch-stdin-eofparks a thread on the streammake-stdin-streamreturns and reads it withread-byte.On Windows
make-stdin-streamreturned a character stream whenever stdin was not a console — which is exactly a lane's case, a pipe.read-byteon a character stream signals a type error rather than returning a byte, and the handler around that read treated any error as end of file, so every lane shut itself down at once.Measured, with the lane's stdin a pipe held open by the parent:
--watch-stdinChanges
make-stdin-stream's Windows pipe branch is now a byte stream (:element-type '(unsigned-byte 8)), the only kind its one reader can read. The console branch is untouched: on a console it still returns the console input handle, whichdrain-console-inputreads as key events. The character branch had no remaining caller —read-available-input's Windows character path only ever saw a console handle — sountranslated-newlinesand that branch are gone, andread-available-inputreads bytes everywhere but the console.watch-stdin-eofcalls its thunk only on a genuine end of file. A read that fails for any other reason leaves the thread with nothing to watch and returns; this signal is a lane's whole life, and a stream that cannot be read is not a parent that went away.Verification (real Windows lanes, not the harness)
--watch-stdin, the lane stays up and answers.idle,restarts: 0, with a ready model.test-stdin-stream-for-watchingfails on the old element type and passes on the new one.Co-authored-by: EvoAgent evo@ruiqilei.com