The stdout reader matched 'EngineFS server started at' against
string_data (the bytes from the most recent read only). If server.js
flushed in such a way that the readiness line straddled two reads,
neither chunk's .lines() yielded a full match, the endpoint channel
never received, recv() timed out, and the WebUI loaded against the
fallback URL.
Search the accumulated *lines buffer instead so a line split across
reads matches once the second chunk lands. Track endpoint_sent so we
do not resend on every subsequent chunk after a match.
Also preserve trailing newlines when trimming the retained buffer to
SRV_LOG_SIZE lines so a later chunk cannot be concatenated onto the
last unterminated line and corrupt a parser. Same trim treatment for
the stderr reader for consistency.
Closes#53
After both stdout and stderr readers exited, the Child handle was just
dropped at the end of the match arm. On Windows std::process::Child's
Drop neither kills nor waits, so the OS process kept its kernel object
(and its bound ports) alive until either the JobObject KILL_ON_JOB_CLOSE
fired on shell exit or init adopted it.
In the crash-restart path this is worse: a fresh start() is launched
while the previous runtime is still being reaped, and both processes
race for port 11470.
Explicitly kill().ok() + wait().ok() once the readers see EOF so the
previous runtime is gone before we report 'Server terminated.' and fire
the crash notice. Errors from killing an already-exited child are
intentionally swallowed.
Closes#50
CreateJobObjectA / SetInformationJobObject / AssignProcessToJobObject
were called inside the per-start thread and their return values were
ignored. Two consequences:
1. Each server crash-restart created a fresh kernel JobObject HANDLE
that was never CloseHandle'd. The HANDLE went out of scope when the
spawned thread exited, leaking a kernel object every crash.
2. On Win 7/8 (single-job systems) and inside parent jobs that disallow
breakaway, AssignProcessToJobObject silently failed, so
stremio-runtime could survive the shell's death and hold port 11470.
Hoist the setup into ensure_parent_job_object() guarded by sync::Once
so it runs exactly once per shell process, and check each return value
explicitly with a clear log message when the OS-level safety net is
degraded. The HANDLE is intentionally not closed: closing it while
KILL_ON_JOB_CLOSE is set would terminate the shell itself.
Closes#47Closes#48
stdout.read(...).unwrap_or(!0) substituted usize::MAX on Err, then
`if on > buffer.len() { continue; }` swallowed it and looped back into
read(). On a sticky IO error (broken pipe, EBADF) the thread spun a
CPU core forever and the accumulated logs were never flushed.
Match Ok(0)/Ok(n)/Err(e) explicitly: break on EOF, break on error after
logging, and treat any other Ok(n) as bytes read. Same change for
stderr.
Closes#51