From 2294f5240734af416e73bf0c19aa8e9be41ec4e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 12:34:09 +0000 Subject: [PATCH] fix: guard JobObject setup with Once and check return values 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 #47 Closes #48 --- src/stremio_app/stremio_server/server.rs | 74 +++++++++++++++--------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/src/stremio_app/stremio_server/server.rs b/src/stremio_app/stremio_server/server.rs index 15ae361..c59b273 100644 --- a/src/stremio_app/stremio_server/server.rs +++ b/src/stremio_app/stremio_server/server.rs @@ -2,13 +2,13 @@ use crate::stremio_app::constants::{SRV_BUFFER_SIZE, SRV_LOG_SIZE, STREMIO_SERVE use native_windows_gui::{self as nwg, PartialUi}; use std::io::Write; use std::{ - env, fs, + env, fs, io, io::Read, ops::Deref, os::windows::process::CommandExt, path, process::{Command, Stdio}, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, Once}, thread, }; use winapi::um::{ @@ -21,6 +21,50 @@ use winapi::um::{ }, }; +// Guarded by Once: avoids HANDLE leak per crash and re-assignment failure on Win 7/8. +fn ensure_parent_job_object() { + static ONCE: Once = Once::new(); + ONCE.call_once(|| unsafe { + let job = CreateJobObjectA(std::ptr::null_mut(), std::ptr::null_mut()); + if job.is_null() { + eprintln!( + "CreateJobObjectA failed: {}; child stremio-runtime may outlive the shell on crash", + io::Error::last_os_error() + ); + return; + } + let jeli = JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION { + LimitFlags: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE + | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION + | JOB_OBJECT_LIMIT_BREAKAWAY_OK, + ..std::mem::zeroed() + }, + ..std::mem::zeroed() + }; + if winapi::um::jobapi2::SetInformationJobObject( + job, + JobObjectExtendedLimitInformation, + &jeli as *const _ as *mut _, + std::mem::size_of::() as u32, + ) == 0 + { + eprintln!( + "SetInformationJobObject failed: {}", + io::Error::last_os_error() + ); + return; + } + if winapi::um::jobapi2::AssignProcessToJobObject(job, GetCurrentProcess()) == 0 { + eprintln!( + "AssignProcessToJobObject failed: {}; child stremio-runtime may outlive the shell", + io::Error::last_os_error() + ); + } + // Don't CloseHandle: KILL_ON_JOB_CLOSE would terminate the shell itself. + }); +} + #[derive(Default)] pub struct StremioServer { development: bool, @@ -38,31 +82,9 @@ impl StremioServer { let logs = self.logs.clone(); let sender = self.crash_notice.sender(); + ensure_parent_job_object(); + thread::spawn(move || { - // Use Win32JobObject to kill the child process when the parent process is killed - // With the JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK and JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flags - unsafe { - let job_main_process = CreateJobObjectA(std::ptr::null_mut(), std::ptr::null_mut()); - let jeli = JOBOBJECT_EXTENDED_LIMIT_INFORMATION { - BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION { - LimitFlags: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE - | JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION - | JOB_OBJECT_LIMIT_BREAKAWAY_OK, - ..std::mem::zeroed() - }, - ..std::mem::zeroed() - }; - winapi::um::jobapi2::SetInformationJobObject( - job_main_process, - JobObjectExtendedLimitInformation, - &jeli as *const _ as *mut _, - std::mem::size_of::() as u32, - ); - winapi::um::jobapi2::AssignProcessToJobObject( - job_main_process, - GetCurrentProcess(), - ); - } let mut path = env::current_exe() .and_then(fs::canonicalize) .expect("Cannot get the current executable path");