When rerunning a task, our process id fetching seems to sometimes return the previous terminal's process id when respawning the task, causing us to kill the new terminal once the previous one drops as we spawn a new one, then drop the old one. This results in rerun sometimes spawning a blank task as the terminal immediately exits. The fix here is simple, we actually want to kill the process running inside the terminal process, not the terminal process itself when we exit in the terminal. No relnotes as this was introduced yesterday in https://github.com/zed-industries/zed/pull/41562 Release Notes: - N/A *or* Added/Fixed/Improved ...
171 lines
4.8 KiB
Rust
171 lines
4.8 KiB
Rust
use alacritty_terminal::tty::Pty;
|
|
#[cfg(target_os = "windows")]
|
|
use std::num::NonZeroU32;
|
|
#[cfg(unix)]
|
|
use std::os::fd::AsRawFd;
|
|
use std::path::PathBuf;
|
|
|
|
#[cfg(target_os = "windows")]
|
|
use windows::Win32::{Foundation::HANDLE, System::Threading::GetProcessId};
|
|
|
|
use sysinfo::{Pid, Process, ProcessRefreshKind, RefreshKind, System, UpdateKind};
|
|
|
|
pub struct ProcessIdGetter {
|
|
handle: i32,
|
|
fallback_pid: u32,
|
|
}
|
|
|
|
impl ProcessIdGetter {
|
|
pub fn fallback_pid(&self) -> Pid {
|
|
Pid::from_u32(self.fallback_pid)
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
impl ProcessIdGetter {
|
|
fn new(pty: &Pty) -> ProcessIdGetter {
|
|
ProcessIdGetter {
|
|
handle: pty.file().as_raw_fd(),
|
|
fallback_pid: pty.child().id(),
|
|
}
|
|
}
|
|
|
|
fn pid(&self) -> Option<Pid> {
|
|
let pid = unsafe { libc::tcgetpgrp(self.handle) };
|
|
if pid < 0 {
|
|
return Some(Pid::from_u32(self.fallback_pid));
|
|
}
|
|
Some(Pid::from_u32(pid as u32))
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
impl ProcessIdGetter {
|
|
fn new(pty: &Pty) -> ProcessIdGetter {
|
|
let child = pty.child_watcher();
|
|
let handle = child.raw_handle();
|
|
let fallback_pid = child.pid().unwrap_or_else(|| unsafe {
|
|
NonZeroU32::new_unchecked(GetProcessId(HANDLE(handle as _)))
|
|
});
|
|
|
|
ProcessIdGetter {
|
|
handle: handle as i32,
|
|
fallback_pid: u32::from(fallback_pid),
|
|
}
|
|
}
|
|
|
|
fn pid(&self) -> Option<Pid> {
|
|
let pid = unsafe { GetProcessId(HANDLE(self.handle as _)) };
|
|
// the GetProcessId may fail and returns zero, which will lead to a stack overflow issue
|
|
if pid == 0 {
|
|
// in the builder process, there is a small chance, almost negligible,
|
|
// that this value could be zero, which means child_watcher returns None,
|
|
// GetProcessId returns 0.
|
|
if self.fallback_pid == 0 {
|
|
return None;
|
|
}
|
|
return Some(Pid::from_u32(self.fallback_pid));
|
|
}
|
|
Some(Pid::from_u32(pid))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ProcessInfo {
|
|
pub name: String,
|
|
pub cwd: PathBuf,
|
|
pub argv: Vec<String>,
|
|
}
|
|
|
|
/// Fetches Zed-relevant Pseudo-Terminal (PTY) process information
|
|
pub struct PtyProcessInfo {
|
|
system: System,
|
|
refresh_kind: ProcessRefreshKind,
|
|
pid_getter: ProcessIdGetter,
|
|
pub current: Option<ProcessInfo>,
|
|
}
|
|
|
|
impl PtyProcessInfo {
|
|
pub fn new(pty: &Pty) -> PtyProcessInfo {
|
|
let process_refresh_kind = ProcessRefreshKind::nothing()
|
|
.with_cmd(UpdateKind::Always)
|
|
.with_cwd(UpdateKind::Always)
|
|
.with_exe(UpdateKind::Always);
|
|
let refresh_kind = RefreshKind::nothing().with_processes(process_refresh_kind);
|
|
let system = System::new_with_specifics(refresh_kind);
|
|
|
|
PtyProcessInfo {
|
|
system,
|
|
refresh_kind: process_refresh_kind,
|
|
pid_getter: ProcessIdGetter::new(pty),
|
|
current: None,
|
|
}
|
|
}
|
|
|
|
pub fn pid_getter(&self) -> &ProcessIdGetter {
|
|
&self.pid_getter
|
|
}
|
|
|
|
fn refresh(&mut self) -> Option<&Process> {
|
|
let pid = self.pid_getter.pid()?;
|
|
if self.system.refresh_processes_specifics(
|
|
sysinfo::ProcessesToUpdate::Some(&[pid]),
|
|
true,
|
|
self.refresh_kind,
|
|
) == 1
|
|
{
|
|
self.system.process(pid)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn get_child(&self) -> Option<&Process> {
|
|
let pid = self.pid_getter.fallback_pid();
|
|
self.system.process(pid)
|
|
}
|
|
|
|
pub(crate) fn kill_current_process(&mut self) -> bool {
|
|
self.refresh().is_some_and(|process| process.kill())
|
|
}
|
|
|
|
pub(crate) fn kill_child_process(&mut self) -> bool {
|
|
self.get_child().is_some_and(|process| process.kill())
|
|
}
|
|
|
|
fn load(&mut self) -> Option<ProcessInfo> {
|
|
let process = self.refresh()?;
|
|
let cwd = process.cwd().map_or(PathBuf::new(), |p| p.to_owned());
|
|
|
|
let info = ProcessInfo {
|
|
name: process.name().to_str()?.to_owned(),
|
|
cwd,
|
|
argv: process
|
|
.cmd()
|
|
.iter()
|
|
.filter_map(|s| s.to_str().map(ToOwned::to_owned))
|
|
.collect(),
|
|
};
|
|
self.current = Some(info.clone());
|
|
Some(info)
|
|
}
|
|
|
|
/// Updates the cached process info, returns whether the Zed-relevant info has changed
|
|
pub fn has_changed(&mut self) -> bool {
|
|
let current = self.load();
|
|
let has_changed = match (self.current.as_ref(), current.as_ref()) {
|
|
(None, None) => false,
|
|
(Some(prev), Some(now)) => prev.cwd != now.cwd || prev.name != now.name,
|
|
_ => true,
|
|
};
|
|
if has_changed {
|
|
self.current = current;
|
|
}
|
|
has_changed
|
|
}
|
|
|
|
pub fn pid(&self) -> Option<Pid> {
|
|
self.pid_getter.pid()
|
|
}
|
|
}
|