util: Always use posix_spawn on macOS even with pre_exec hooks (#49090)

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 +
https://github.com/RemiKalbe/zed/commit/6e8f2d2ebe5a93753625a3026aeb996de8cb436b)
* 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>
This commit is contained in:
Jakub KonkaandCole Miller authored and GitHub committed 2026-02-13 20:16:11 +01:00
1 parent 4f54c3e07c
commit 16dfc60ad2
38 files changed
+1140 -297

No files matched your search

+5 -5
View File
@@ -427,7 +427,7 @@ impl ManagedNodeRuntime {
let node_ca_certs = env::var(NODE_CA_CERTS_ENV_VAR).unwrap_or_else(|_| String::new());
let valid = if fs::metadata(&node_binary).await.is_ok() {
let result = util::command::new_smol_command(&node_binary)
let result = util::command::new_command(&node_binary)
.env(NODE_CA_CERTS_ENV_VAR, node_ca_certs)
.arg(npm_file)
.arg("--version")
@@ -555,7 +555,7 @@ impl NodeRuntimeTrait for ManagedNodeRuntime {
) -> Result<Output> {
let attempt = || async {
let npm_command = self.npm_command(proxy, subcommand, args).await?;
let mut command = util::command::new_smol_command(npm_command.path);
let mut command = util::command::new_command(npm_command.path);
command.args(npm_command.args);
command.envs(npm_command.env);
configure_npm_command(&mut command, directory);
@@ -640,7 +640,7 @@ pub struct SystemNodeRuntime {
impl SystemNodeRuntime {
const MIN_VERSION: semver::Version = Version::new(22, 0, 0);
async fn new(node: PathBuf, npm: PathBuf) -> Result<Self> {
let output = util::command::new_smol_command(&node)
let output = util::command::new_command(&node)
.arg("--version")
.output()
.await
@@ -723,7 +723,7 @@ impl NodeRuntimeTrait for SystemNodeRuntime {
args: &[&str],
) -> anyhow::Result<Output> {
let npm_command = self.npm_command(proxy, subcommand, args).await?;
let mut command = util::command::new_smol_command(npm_command.path);
let mut command = util::command::new_command(npm_command.path);
command.args(npm_command.args);
command.envs(npm_command.env);
configure_npm_command(&mut command, directory);
@@ -841,7 +841,7 @@ impl NodeRuntimeTrait for UnavailableNodeRuntime {
}
}
fn configure_npm_command(command: &mut smol::process::Command, directory: Option<&Path>) {
fn configure_npm_command(command: &mut util::command::Command, directory: Option<&Path>) {
if let Some(directory) = directory {
command.current_dir(directory);
command.args(["--prefix".into(), directory.to_path_buf()]);