Skip to content

Commit 7a42ca9

Browse files
committed
Auto merge of #100786 - sunshowers:macos-posix-chdir, r=sunshowers
Use posix_spawn for absolute paths on macOS Currently, on macOS, Rust never uses the fast posix_spawn path if a directory change is requested, due to a bug in Apple's libc. However, the bug is only triggered if the program is a relative path. This PR makes it so that the fast path continues to work if the program is an absolute path or a lone filename. This was an alternative proposed in #80537 (comment), and it makes a measurable performance difference in some of my code that spawns thousands of processes.
2 parents 94b2b15 + bd8b4b9 commit 7a42ca9

File tree

3 files changed

+60
-1
lines changed

3 files changed

+60
-1
lines changed

Diff for: library/std/src/sys/unix/process/process_common.rs

+33
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ pub struct Command {
9292
argv: Argv,
9393
env: CommandEnv,
9494

95+
program_kind: ProgramKind,
9596
cwd: Option<CString>,
9697
uid: Option<uid_t>,
9798
gid: Option<gid_t>,
@@ -148,15 +149,40 @@ pub enum Stdio {
148149
Fd(FileDesc),
149150
}
150151

152+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
153+
pub enum ProgramKind {
154+
/// A program that would be looked up on the PATH (e.g. `ls`)
155+
PathLookup,
156+
/// A relative path (e.g. `my-dir/foo`, `../foo`, `./foo`)
157+
Relative,
158+
/// An absolute path.
159+
Absolute,
160+
}
161+
162+
impl ProgramKind {
163+
fn new(program: &OsStr) -> Self {
164+
if program.bytes().starts_with(b"/") {
165+
Self::Absolute
166+
} else if program.bytes().contains(&b'/') {
167+
// If the program has more than one component in it, it is a relative path.
168+
Self::Relative
169+
} else {
170+
Self::PathLookup
171+
}
172+
}
173+
}
174+
151175
impl Command {
152176
#[cfg(not(target_os = "linux"))]
153177
pub fn new(program: &OsStr) -> Command {
154178
let mut saw_nul = false;
179+
let program_kind = ProgramKind::new(program.as_ref());
155180
let program = os2c(program, &mut saw_nul);
156181
Command {
157182
argv: Argv(vec![program.as_ptr(), ptr::null()]),
158183
args: vec![program.clone()],
159184
program,
185+
program_kind,
160186
env: Default::default(),
161187
cwd: None,
162188
uid: None,
@@ -174,11 +200,13 @@ impl Command {
174200
#[cfg(target_os = "linux")]
175201
pub fn new(program: &OsStr) -> Command {
176202
let mut saw_nul = false;
203+
let program_kind = ProgramKind::new(program.as_ref());
177204
let program = os2c(program, &mut saw_nul);
178205
Command {
179206
argv: Argv(vec![program.as_ptr(), ptr::null()]),
180207
args: vec![program.clone()],
181208
program,
209+
program_kind,
182210
env: Default::default(),
183211
cwd: None,
184212
uid: None,
@@ -254,6 +282,11 @@ impl Command {
254282
OsStr::from_bytes(self.program.as_bytes())
255283
}
256284

285+
#[allow(dead_code)]
286+
pub fn get_program_kind(&self) -> ProgramKind {
287+
self.program_kind
288+
}
289+
257290
pub fn get_args(&self) -> CommandArgs<'_> {
258291
let mut iter = self.args.iter();
259292
iter.next();

Diff for: library/std/src/sys/unix/process/process_common/tests.rs

+24
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,27 @@ fn test_process_group_no_posix_spawn() {
122122
t!(cat.wait());
123123
}
124124
}
125+
126+
#[test]
127+
fn test_program_kind() {
128+
let vectors = &[
129+
("foo", ProgramKind::PathLookup),
130+
("foo.out", ProgramKind::PathLookup),
131+
("./foo", ProgramKind::Relative),
132+
("../foo", ProgramKind::Relative),
133+
("dir/foo", ProgramKind::Relative),
134+
// Note that paths on Unix can't contain / in them, so this is actually the directory "fo\\"
135+
// followed by the file "o".
136+
("fo\\/o", ProgramKind::Relative),
137+
("/foo", ProgramKind::Absolute),
138+
("/dir/../foo", ProgramKind::Absolute),
139+
];
140+
141+
for (program, expected_kind) in vectors {
142+
assert_eq!(
143+
ProgramKind::new(program.as_ref()),
144+
*expected_kind,
145+
"actual != expected program kind for input {program}",
146+
);
147+
}
148+
}

Diff for: library/std/src/sys/unix/process/process_unix.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,9 @@ impl Command {
453453
// successfully launch the program, but erroneously return
454454
// ENOENT when used with posix_spawn_file_actions_addchdir_np
455455
// which was introduced in macOS 10.15.
456-
return Ok(None);
456+
if self.get_program_kind() == ProgramKind::Relative {
457+
return Ok(None);
458+
}
457459
}
458460
match posix_spawn_file_actions_addchdir_np.get() {
459461
Some(f) => Some((f, cwd)),

0 commit comments

Comments
 (0)