fix: terminate unrar sidecars when the launcher closes mid-install
Bug report: unrar.exe kept running after closing the launcher during a game install. The orphaned process kept extracting in the background and held file handles on the staging directory. Root cause (regular install path): run_unrar_sidecar ran the unrar sidecar via tauri-plugin-shell's Command::output(). That helper spawns the process on a detached SharedChild OS thread and immediately drops the CommandChild; there is no Drop impl that kills the process. On app exit only shutdown_peer_runtime ran, and Windows does not cascade-kill child processes, so closing the launcher left unrar running. Fix: - run_unrar_sidecar now uses .spawn() instead of .output(), keeping a killable CommandChild. It registers the child in a new LanSpreadState.active_unrar_children registry and an RAII UnrarChildGuard deregisters it on every return path. The CommandEvent stream is drained to reproduce the exact stdout/stderr (NEWLINE_BYTE is b'\n'), status code, success flag, and UnpackLogEntry the old .output() produced, so logging behavior is unchanged. - kill_active_unrar_children() runs in the RunEvent::Exit handler before shutdown_peer_runtime, killing every in-progress unrar. Killing first also lets the install task unwind so the runtime stops promptly. Two concurrency hazards were closed in the registry design: - Children are keyed by a monotonic id, not pid. A pid key let a finishing install's guard deregister a different install's child after the OS recycled the pid, which could re-orphan a live child. - A shutting_down latch lives in the registry under the same mutex as the kill sweep. The sweep is a one-shot drain, so a child registered after it (a task caught between spawn() and registration, or a later archive in a multi-archive install -- unpack_archives does not observe the shutdown token) would be missed. Registration now checks the latch under that mutex and kills the child immediately instead of inserting it. Since registration and the sweep serialize on one mutex, every interleaving kills the child. Also hardened the streamed-install sender path (ExternalUnrarStream Provider) with kill_on_drop(true) on its tokio unrar spawns, so a dropped or aborted producer task cannot orphan unrar there either. Known limitation: a hard force-kill or crash of the launcher (e.g. Task Manager -> End Task) bypasses RunEvent::Exit and is not covered. Making that bulletproof would require a Windows Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; the reported "close the launcher" case is fully fixed. Test Plan: - just clippy (pedantic, -D warnings): clean. - just fmt: no changes. - just test: all suites pass (incl. the 20 Tauri-lib unit tests). - Manual (Windows): start a game install, close the launcher mid-extract, confirm no unrar.exe remains in Task Manager. Repeat with two concurrent installs and with a multi-archive game.
This commit is contained in:
@@ -225,6 +225,7 @@ async fn unrar_listing(program: &Path, archive: &Path) -> eyre::Result<RarListin
|
||||
.arg("lt")
|
||||
.arg("-cfg-")
|
||||
.arg(archive)
|
||||
.kill_on_drop(true)
|
||||
.output()
|
||||
.await?;
|
||||
if !output.status.success() {
|
||||
@@ -329,6 +330,9 @@ async fn stream_unrar_entries(
|
||||
.arg(archive)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
// Safety net: if this task is dropped before its cancel/error path runs
|
||||
// (e.g. on shutdown), tokio still kills unrar instead of orphaning it.
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
let result = async {
|
||||
|
||||
Reference in New Issue
Block a user