bug(process): Kill all managed Argyll subprocesses on app / window exit #149

Closed
opened 2026-08-30 12:03:59 +01:00 by gronod · 0 comments
Owner

Description

When the user closes the ICCery window or quits the application while any ArgyllCMS sidecar is still running, the child process is not terminated.

Issue #147 documents the Stage 3 / chartread case specifically. The same gap exists for every tool managed by ProcessManager (targen, printtarg, chartread, average, colprof, profcheck, iccgamut, instlist).

Only the explicit Cancel path in Stage 3 calls invoke("kill_process", { id }). There is no Tauri lifecycle hook that drains the process table on shutdown.

Steps to Reproduce

  1. Start any long-running Stage 3 measurement (chartread active; Cancel visible).
  2. Close the window via title-bar close, keyboard quit (Cmd/Ctrl+Q), or OS “Quit”.
  3. Inspect the process list (ps, Activity Monitor, Task Manager).

Expected Behaviour

  • Every process still registered in ProcessManager is killed before the host exits.
  • Instrument USB/serial handles are released.
  • No orphaned chartread / colprof / etc. processes remain.

Actual Behaviour

  • Child processes outlive the parent.
  • Spectrophotometers remain locked until the user kills the process manually or unplugs the device.

Technical Notes

Current kill path (works):

  • ProcessManager::kill(id) drops stdin, signals a oneshot, then child.start_kill()
    (src-tauri/src/process_manager.rs)
  • Frontend Cancel button → commands::kill_process → above path
    (src/js/chartread.js)

Missing path:

  • src-tauri/src/lib.rs uses a bare .run(tauri::generate_context!()) with no RunEvent / WindowEvent::CloseRequested handler.
  • ProcessManager has no kill_all() helper.

Suggested Fix

  1. Add ProcessManager::kill_all(&self) that iterates every registered killer and signals them (and drops all stdins).
  2. In lib.rs, handle shutdown, e.g.:
.run(|app_handle, event| {
    if let tauri::RunEvent::Exit | tauri::RunEvent::ExitRequested { .. } = event {
        // block_on or spawn a short runtime to call kill_all
    }
})

Alternatively intercept WindowEvent::CloseRequested on the main window and kill before allowing close.

  1. Prefer graceful order: close stdin → signal kill → short wait → force kill if still alive.
  2. Apply on all platforms (Windows, Linux, macOS).

Acceptance Criteria

  • Closing ICCery during an active chartread leaves no chartread process.
  • Same for other managed tools if they are still running at quit time.
  • Cancel button behaviour is unchanged.
  • Instrument can be reopened by a new ICCery session without manual cleanup.
  • Unit or integration coverage for kill_all (at least that the map is emptied).

Dependencies

  • Related: #147 (Stage 3 specific). This ticket generalises it.
  • No feature dependencies
### Description When the user closes the ICCery window or quits the application while any ArgyllCMS sidecar is still running, the child process is **not** terminated. Issue #147 documents the Stage 3 / `chartread` case specifically. The same gap exists for every tool managed by `ProcessManager` (`targen`, `printtarg`, `chartread`, `average`, `colprof`, `profcheck`, `iccgamut`, `instlist`). Only the explicit **Cancel** path in Stage 3 calls `invoke("kill_process", { id })`. There is no Tauri lifecycle hook that drains the process table on shutdown. ### Steps to Reproduce 1. Start any long-running Stage 3 measurement (`chartread` active; Cancel visible). 2. Close the window via title-bar close, keyboard quit (Cmd/Ctrl+Q), or OS “Quit”. 3. Inspect the process list (`ps`, Activity Monitor, Task Manager). ### Expected Behaviour - Every process still registered in `ProcessManager` is killed before the host exits. - Instrument USB/serial handles are released. - No orphaned `chartread` / `colprof` / etc. processes remain. ### Actual Behaviour - Child processes outlive the parent. - Spectrophotometers remain locked until the user kills the process manually or unplugs the device. ### Technical Notes **Current kill path (works):** - `ProcessManager::kill(id)` drops stdin, signals a oneshot, then `child.start_kill()` (`src-tauri/src/process_manager.rs`) - Frontend Cancel button → `commands::kill_process` → above path (`src/js/chartread.js`) **Missing path:** - `src-tauri/src/lib.rs` uses a bare `.run(tauri::generate_context!())` with **no** `RunEvent` / `WindowEvent::CloseRequested` handler. - `ProcessManager` has no `kill_all()` helper. ### Suggested Fix 1. Add `ProcessManager::kill_all(&self)` that iterates every registered killer and signals them (and drops all stdins). 2. In `lib.rs`, handle shutdown, e.g.: ```rust .run(|app_handle, event| { if let tauri::RunEvent::Exit | tauri::RunEvent::ExitRequested { .. } = event { // block_on or spawn a short runtime to call kill_all } }) ``` Alternatively intercept `WindowEvent::CloseRequested` on the main window and kill before allowing close. 3. Prefer graceful order: close stdin → signal kill → short wait → force kill if still alive. 4. Apply on **all** platforms (Windows, Linux, macOS). ### Acceptance Criteria - [ ] Closing ICCery during an active `chartread` leaves no `chartread` process. - [ ] Same for other managed tools if they are still running at quit time. - [ ] Cancel button behaviour is unchanged. - [ ] Instrument can be reopened by a new ICCery session without manual cleanup. - [ ] Unit or integration coverage for `kill_all` (at least that the map is emptied). ### Dependencies - Related: #147 (Stage 3 specific). This ticket generalises it. - No feature dependencies
gronod added the Kind/Bug
Reviewed
Confirmed
1
Priority
High
2
labels 2026-08-30 12:03:59 +01:00
gronod added this to the Milestone 10: Production Ready Features (v0.6.0) milestone 2026-08-30 15:16:00 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/ICCery#149