Skip to content

Commit edf16ce

Browse files
committed
use pidfd_spawn for faster process creation when pidfds are requested
1 parent f0aceed commit edf16ce

File tree

2 files changed

+96
-6
lines changed

2 files changed

+96
-6
lines changed

library/std/src/sys/pal/unix/linux/pidfd/tests.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::assert_matches::assert_matches;
22
use crate::os::fd::{AsRawFd, RawFd};
3-
use crate::os::linux::process::{ChildExt, CommandExt};
4-
use crate::os::unix::process::ExitStatusExt;
3+
use crate::os::linux::process::{ChildExt, CommandExt as _};
4+
use crate::os::unix::process::{CommandExt as _, ExitStatusExt};
55
use crate::process::Command;
66

77
#[test]
@@ -42,6 +42,15 @@ fn test_command_pidfd() {
4242
.unwrap()
4343
.pidfd()
4444
.expect_err("pidfd should not have been created");
45+
46+
// exercise the fork/exec path since the earlier attempts may have used pidfd_spawnp()
47+
let mut child =
48+
unsafe { Command::new("false").pre_exec(|| Ok(())) }.create_pidfd(true).spawn().unwrap();
49+
50+
if pidfd_open_available {
51+
assert!(child.pidfd().is_ok())
52+
}
53+
child.wait().expect("error waiting on child");
4554
}
4655

4756
#[test]

library/std/src/sys/pal/unix/process/process_unix.rs

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,17 +449,61 @@ impl Command {
449449
use crate::mem::MaybeUninit;
450450
use crate::sys::weak::weak;
451451
use crate::sys::{self, cvt_nz, on_broken_pipe_flag_used};
452+
use core::sync::atomic::{AtomicU8, Ordering};
452453

453454
if self.get_gid().is_some()
454455
|| self.get_uid().is_some()
455456
|| (self.env_saw_path() && !self.program_is_path())
456457
|| !self.get_closures().is_empty()
457458
|| self.get_groups().is_some()
458-
|| self.get_create_pidfd()
459459
{
460460
return Ok(None);
461461
}
462462

463+
weak! {
464+
fn pidfd_spawnp(
465+
*mut libc::c_int,
466+
*const libc::c_char,
467+
*const libc::posix_spawn_file_actions_t,
468+
*const libc::posix_spawnattr_t,
469+
*const *mut libc::c_char,
470+
*const *mut libc::c_char
471+
) -> libc::c_int
472+
}
473+
474+
weak! { fn pidfd_getpid(libc::c_int) -> libc::c_int }
475+
476+
static PIDFD_SPAWN_SUPPORTED: AtomicU8 = AtomicU8::new(0);
477+
const UNKNOWN: u8 = 0;
478+
const YES: u8 = 1;
479+
// NO currently forces a fallback to fork/exec. We could be more nuanced here and keep using spawn
480+
// if we know pidfd's aren't supported at all and the fallback would be futile.
481+
const NO: u8 = 2;
482+
483+
if self.get_create_pidfd() {
484+
let flag = PIDFD_SPAWN_SUPPORTED.load(Ordering::Relaxed);
485+
if flag == NO || pidfd_spawnp.get().is_none() || pidfd_getpid.get().is_none() {
486+
return Ok(None);
487+
}
488+
if flag == UNKNOWN {
489+
let mut support = NO;
490+
let our_pid = crate::process::id();
491+
let pidfd =
492+
unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) } as libc::c_int;
493+
if pidfd >= 0 {
494+
let pid = unsafe { pidfd_getpid.get().unwrap()(pidfd) } as u32;
495+
unsafe { libc::close(pidfd) };
496+
if pid == our_pid {
497+
support = YES
498+
};
499+
}
500+
PIDFD_SPAWN_SUPPORTED.store(support, Ordering::Relaxed);
501+
if support != YES {
502+
return Ok(None);
503+
}
504+
}
505+
}
506+
463507
// Only glibc 2.24+ posix_spawn() supports returning ENOENT directly.
464508
#[cfg(all(target_os = "linux", target_env = "gnu"))]
465509
{
@@ -543,9 +587,6 @@ impl Command {
543587

544588
let pgroup = self.get_pgroup();
545589

546-
// Safety: -1 indicates we don't have a pidfd.
547-
let mut p = unsafe { Process::new(0, -1) };
548-
549590
struct PosixSpawnFileActions<'a>(&'a mut MaybeUninit<libc::posix_spawn_file_actions_t>);
550591

551592
impl Drop for PosixSpawnFileActions<'_> {
@@ -640,6 +681,46 @@ impl Command {
640681
#[cfg(target_os = "nto")]
641682
let spawn_fn = retrying_libc_posix_spawnp;
642683

684+
if self.get_create_pidfd() {
685+
let mut pidfd: libc::c_int = -1;
686+
let spawn_res = pidfd_spawnp.get().unwrap()(
687+
&mut pidfd,
688+
self.get_program_cstr().as_ptr(),
689+
file_actions.0.as_ptr(),
690+
attrs.0.as_ptr(),
691+
self.get_argv().as_ptr() as *const _,
692+
envp as *const _,
693+
);
694+
695+
let spawn_res = cvt_nz(spawn_res);
696+
if let Err(ref e) = spawn_res
697+
&& e.raw_os_error() == Some(libc::ENOSYS)
698+
{
699+
PIDFD_SPAWN_SUPPORTED.store(NO, Ordering::Relaxed);
700+
return Ok(None);
701+
}
702+
spawn_res?;
703+
704+
let pid = match cvt(pidfd_getpid.get().unwrap()(pidfd)) {
705+
Ok(pid) => pid,
706+
Err(e) => {
707+
// The child has been spawned and we are holding its pidfd.
708+
// But we cannot obtain its pid even though pidfd_getpid support was verified earlier.
709+
// This might happen if libc can't open procfs because the file descriptor limit has been reached.
710+
libc::close(pidfd);
711+
return Err(Error::new(
712+
e.kind(),
713+
"pidfd_spawnp succeeded but the child's PID could not be obtained",
714+
));
715+
}
716+
};
717+
718+
return Ok(Some(Process::new(pid, pidfd)));
719+
}
720+
721+
// Safety: -1 indicates we don't have a pidfd.
722+
let mut p = Process::new(0, -1);
723+
643724
let spawn_res = spawn_fn(
644725
&mut p.pid,
645726
self.get_program_cstr().as_ptr(),

0 commit comments

Comments
 (0)