start() ended with rx.recv().unwrap(), so every caller blocked until
the server reported readiness. process_event runs on the NWG message
pump and is invoked when crash_notice fires; the modal_error_message
returns, start() is called, and the entire UI freezes for up to 60s
while the new stremio-runtime boots.
Return the readiness receiver from start() instead of consuming it.
build_partial keeps the synchronous wait for initial startup so the
WebView isn't attached against a not-yet-bound port. process_event
fires-and-forgets so the GUI keeps pumping; if the new runtime also
fails, the server thread will simply fire crash_notice again.
Closes#49
start() unconditionally spawned a fresh stremio-runtime every time it
was called. Today only the GUI thread calls it, but the crash-restart
path can re-fire start() while the previous teardown is still in
progress, racing two runtimes for port 11470.
Track a `running: Arc<AtomicBool>` on the struct: swap(true) on entry
and bail with a log if the previous server thread has not yet flipped
it back to false. Reset to false right before sender.notice() so that
the GUI's crash handler can legitimately spawn the next instance.
Closes#57
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
Commit 0b882f3 synthesized an Event::EndFile(Error) whenever
event_context.wait_event returned libmpv2::Error::Raw and forwarded it
to the web layer. MPV is fully able to continue playback after such
transient errors (its demuxer cache is intact), so fabricating an
end-of-file caused long-running HTTP streams (e.g. RealDebrid) to flip
to a blocking "Loading failed" overlay mid-playback. Revert that branch
to the prior log-and-continue behavior.
Also downgrade PlayerEnded errors from critical:true to critical:false
so genuine mpv_end_file_reason::Error surfaces as a 3s toast in
stremio-web (Player.js), letting the user retry, instead of an
unrecoverable full-screen overlay.