mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-26 15:44:20 +00:00
Here's some backstory:
* on macOS, @cole-miller and I noticed that since roughly Oct 2025, due
to some changes to latest macOS Tahoe, for any spawned child process we
needed to reset Mach exception ports
(https://github.com/zed-industries/zed/issues/36754 +
6e8f2d2ebe)
* the changes in that PR achieve that via `pre_exec` hook on
`std::process::Command` which then abandons `posix_spawn` syscall for
`fork` + `execve` dance on macOS (we tracked it down in Rust's std
implementation)
* as it turns out, `fork` + `execve` is pretty expensive on macOS
(apparently way more so than on other OSes like Linux) and `fork` takes
a process-wide lock on the allocator which is bad
* however, since we wanna reset exception ports on the child, the only
official way supported by Rust's std is to use `pre_exec` hook
* posix_spawn on macOS exposes this tho via a macOS specific extension
to that syscall `posix_spawnattr_setexceptionports_np` but there is no
way to use that via any standard interfaces in `std::process::Command`
* thus, it seemed like a good idea to instead create our own custom
Command wrapper that on non-macOS hosts is a zero-cost wrapper of
`smol::process::Command`, while on macOS we reimplement the minimum to
achieve `smol::process::Command` with `posix_spawn` under-the-hood
Notably, this changeset improves git-blame in very large repos
significantly.
Release Notes:
- Fixed performance spawning child processes on macOS by always forcing
`posix_spawn` no matter what.
---------
Co-authored-by: Cole Miller <cole@zed.dev>
132 lines
3.2 KiB
Rust
132 lines
3.2 KiB
Rust
use std::ffi::OsStr;
|
|
#[cfg(not(target_os = "macos"))]
|
|
use std::path::Path;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
mod darwin;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub use darwin::{Child, Command, Stdio};
|
|
|
|
#[cfg(target_os = "windows")]
|
|
const CREATE_NO_WINDOW: u32 = 0x0800_0000_u32;
|
|
|
|
pub fn new_command(program: impl AsRef<OsStr>) -> Command {
|
|
Command::new(program)
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn new_std_command(program: impl AsRef<OsStr>) -> std::process::Command {
|
|
use std::os::windows::process::CommandExt;
|
|
|
|
let mut command = std::process::Command::new(program);
|
|
command.creation_flags(CREATE_NO_WINDOW);
|
|
command
|
|
}
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub fn new_std_command(program: impl AsRef<OsStr>) -> std::process::Command {
|
|
std::process::Command::new(program)
|
|
}
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
pub type Child = smol::process::Child;
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
pub use std::process::Stdio;
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
#[derive(Debug)]
|
|
pub struct Command(smol::process::Command);
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
impl Command {
|
|
#[inline]
|
|
pub fn new(program: impl AsRef<OsStr>) -> Self {
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
use smol::process::windows::CommandExt;
|
|
let mut cmd = smol::process::Command::new(program);
|
|
cmd.creation_flags(CREATE_NO_WINDOW);
|
|
Self(cmd)
|
|
}
|
|
#[cfg(not(target_os = "windows"))]
|
|
Self(smol::process::Command::new(program))
|
|
}
|
|
|
|
pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
|
|
self.0.arg(arg);
|
|
self
|
|
}
|
|
|
|
pub fn args<I, S>(&mut self, args: I) -> &mut Self
|
|
where
|
|
I: IntoIterator<Item = S>,
|
|
S: AsRef<OsStr>,
|
|
{
|
|
self.0.args(args);
|
|
self
|
|
}
|
|
|
|
pub fn env(&mut self, key: impl AsRef<OsStr>, val: impl AsRef<OsStr>) -> &mut Self {
|
|
self.0.env(key, val);
|
|
self
|
|
}
|
|
|
|
pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
|
|
where
|
|
I: IntoIterator<Item = (K, V)>,
|
|
K: AsRef<OsStr>,
|
|
V: AsRef<OsStr>,
|
|
{
|
|
self.0.envs(vars);
|
|
self
|
|
}
|
|
|
|
pub fn env_remove(&mut self, key: impl AsRef<OsStr>) -> &mut Self {
|
|
self.0.env_remove(key);
|
|
self
|
|
}
|
|
|
|
pub fn env_clear(&mut self) -> &mut Self {
|
|
self.0.env_clear();
|
|
self
|
|
}
|
|
|
|
pub fn current_dir(&mut self, dir: impl AsRef<Path>) -> &mut Self {
|
|
self.0.current_dir(dir);
|
|
self
|
|
}
|
|
|
|
pub fn stdin(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
|
|
self.0.stdin(cfg.into());
|
|
self
|
|
}
|
|
|
|
pub fn stdout(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
|
|
self.0.stdout(cfg.into());
|
|
self
|
|
}
|
|
|
|
pub fn stderr(&mut self, cfg: impl Into<Stdio>) -> &mut Self {
|
|
self.0.stderr(cfg.into());
|
|
self
|
|
}
|
|
|
|
pub fn kill_on_drop(&mut self, kill_on_drop: bool) -> &mut Self {
|
|
self.0.kill_on_drop(kill_on_drop);
|
|
self
|
|
}
|
|
|
|
pub fn spawn(&mut self) -> std::io::Result<Child> {
|
|
self.0.spawn()
|
|
}
|
|
|
|
pub async fn output(&mut self) -> std::io::Result<std::process::Output> {
|
|
self.0.output().await
|
|
}
|
|
|
|
pub async fn status(&mut self) -> std::io::Result<std::process::ExitStatus> {
|
|
self.0.status().await
|
|
}
|
|
}
|