diff options
| author | akbash <akbash@herdr.dev> | 2026-07-13 17:25:52 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-13 17:25:52 +0300 |
| commit | 75ed6abc6258f09af5d4fc995a5004c1abf14fc2 (patch) | |
| tree | 7dd561becfc189f2dbe6f153d0c870ac913351ae /src/app/input | |
| parent | 6e85ff07f188186edcec8ac0e8263f899c7f1f26 (diff) | |
fix: reap detached custom command children (#1384)
* fix: reap detached custom command children
refs #1360
* test: harden child cleanup and sidebar metrics
refs #1360
---------
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Diffstat (limited to 'src/app/input')
| -rw-r--r-- | src/app/input/navigate.rs | 49 |
1 files changed, 41 insertions, 8 deletions
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs index a1597669..7f98f530 100644 --- a/src/app/input/navigate.rs +++ b/src/app/input/navigate.rs @@ -843,7 +843,7 @@ impl App { } fn spawn_custom_command( - &self, + &mut self, binding: &crate::config::CustomCommandKeybind, ) -> std::io::Result<()> { let mut command = crate::platform::detached_custom_command_process(&binding.command); @@ -856,7 +856,8 @@ impl App { if let Some(cwd) = cwd { command.current_dir(cwd); } - command.spawn()?; + let child = command.spawn()?; + self.detached_custom_command_children.push(child); Ok(()) } @@ -2943,9 +2944,11 @@ navigate_pane_down = "ctrl+j" app.state.mode = Mode::Terminal; let output_path = unique_temp_path("custom-command-keybind"); + let release_path = unique_temp_path("custom-command-release"); let command = format!( - "printf '%s\\n%s\\n%s\\n' \"$HERDR_ACTIVE_WORKSPACE_ID\" \"$HERDR_ACTIVE_TAB_ID\" \"$HERDR_ACTIVE_PANE_ID\" > '{}'", - output_path.display() + "printf '%s\\n%s\\n%s\\n%s\\n' \"$$\" \"$HERDR_ACTIVE_WORKSPACE_ID\" \"$HERDR_ACTIVE_TAB_ID\" \"$HERDR_ACTIVE_PANE_ID\" > '{}'; i=0; while [ ! -e '{}' ] && [ \"$i\" -lt 250 ]; do sleep 0.02; i=$((i + 1)); done", + output_path.display(), + release_path.display(), ); app.state.keybinds.custom_commands = vec![crate::config::CustomCommandKeybind { bindings: crate::config::ActionKeybinds::prefix("m"), @@ -2962,18 +2965,48 @@ navigate_pane_down = "ctrl+j" .await; assert_eq!(app.state.mode, Mode::Prefix); + let launch_started = std::time::Instant::now(); app.handle_key(TerminalKey::new(KeyCode::Char('m'), KeyModifiers::empty())) .await; + assert!(launch_started.elapsed() < Duration::from_secs(2)); let content = wait_for_file(&output_path); let lines: Vec<&str> = content.lines().collect(); - assert_eq!(lines.len(), 3); - assert_eq!(lines[0], app.state.workspaces[0].id); - assert_eq!(lines[1], format!("{}:t1", app.state.workspaces[0].id)); - assert_eq!(lines[2], format!("{}:p1", app.state.workspaces[0].id)); + assert_eq!(lines.len(), 4); + let pid = lines[0] + .parse::<u32>() + .expect("command should report its pid"); + assert!(crate::platform::process_exists(pid)); + assert_eq!(lines[1], app.state.workspaces[0].id); + assert_eq!(lines[2], format!("{}:t1", app.state.workspaces[0].id)); + assert_eq!(lines[3], format!("{}:p1", app.state.workspaces[0].id)); assert_eq!(app.state.mode, Mode::Terminal); + std::fs::write(&release_path, b"release").expect("release command"); + let deadline = tokio::time::Instant::now() + Duration::from_secs(2); + while crate::platform::process_exists(pid) && tokio::time::Instant::now() < deadline { + app.reap_finished_custom_commands(); + tokio::time::sleep(Duration::from_millis(20)).await; + } + app.reap_finished_custom_commands(); + let reaped_by_runtime = !crate::platform::process_exists(pid); + if !reaped_by_runtime { + if let Some(child) = app + .detached_custom_command_children + .iter_mut() + .find(|child| child.id() == pid) + { + let _ = child.kill(); + let _ = child.wait(); + } + } + assert!( + reaped_by_runtime, + "detached command child {pid} was not reaped" + ); + let _ = std::fs::remove_file(output_path); + let _ = std::fs::remove_file(release_path); } #[cfg(unix)] |
