rust-lang/rust · error · io::Error
pidfd_spawnp succeeded but the child's PID could not be obta
Error message
pidfd_spawnp succeeded but the child's PID could not be obtained
What it means
Returned by Rust std's Linux pidfd-spawn path after pidfd_spawnp succeeds but the subsequent pidfd.pid() call fails. The child has already been spawned and std holds its pidfd, yet it cannot read the PID - typically because the PID-fetching ioctl is unsupported, glibc's procfs fallback is blocked, or the process is out of file descriptors. The original error kind is preserved and wrapped with this explanatory message.
Source
Thrown at library/std/src/sys/process/unix/unix.rs:810
&& e.raw_os_error() == Some(libc::ENOSYS)
{
PIDFD_SUPPORTED.store(FORK_EXEC, Ordering::Relaxed);
return Ok(None);
}
spawn_res?;
use crate::os::fd::{FromRawFd, IntoRawFd};
let pidfd = PidFd::from_raw_fd(pidfd);
let pid = match pidfd.pid() {
Ok(pid) => pid,
Err(e) => {
// The child has been spawned and we are holding its pidfd.
// But we cannot obtain its pid even though pidfd_spawnp and getpid support
// was verified earlier.
// This is quite unlikely, but might happen if the ioctl is not supported,
// glibc tries to use procfs and we're out of file descriptors.
return Err(Error::new(
e.kind(),
"pidfd_spawnp succeeded but the child's PID could not be obtained",
));
}
};
return Ok(Some(Process::new(pid as i32, pidfd.into_raw_fd())));
}
// Safety: -1 indicates we don't have a pidfd.
let mut p = Process::new(0, -1);
let spawn_res = spawn_fn(
&mut p.pid,
self.get_program_cstr().as_ptr(),
file_actions.0.as_ptr(),
attrs.0.as_ptr(),
self.get_argv().as_ptr() as *const _,View on GitHub (pinned to 7088e4b63a)
Solutions
- Disable create_pidfd on the Command to fall back to fork+exec, which returns the PID directly.
- Ensure procfs is mounted and readable at /proc in the sandbox/container.
- Loosen the seccomp filter to allow the pidfd-related ioctls, or raise RLIMIT_NOFILE.
- Upgrade glibc to a version with a correct pidfd pid retrieval implementation.
Example fix
// before
let mut cmd = Command::new("./child");
cmd.create_pidfd(true);
let child = cmd.spawn()?;
// after
let mut cmd = Command::new("./child");
// do not request a pidfd; rely on fork+exec
let child = cmd.spawn()?; Defensive patterns
Strategy: fallback
Validate before calling
// Disable pidfd when running in restricted sandboxes.
fn wants_pidfd() -> bool {
std::env::var_os("SANDBOX").is_none() && std::fs::metadata("/proc/self").is_ok()
}
// if !wants_pidfd() { cmd.create_pidfd(false); } Try / catch
let mut cmd = Command::new(prog);
cmd.create_pidfd(true);
match cmd.spawn() {
Ok(child) => Ok(child),
Err(e) if e.to_string().contains("PID could not be obtained") => {
let mut cmd2 = Command::new(prog); // no pidfd
cmd2.spawn()
}
Err(e) => Err(e),
} Prevention
- Do not request pidfd in containers that mask /proc or restrict ioctls.
- Ensure procfs is mounted and RLIMIT_NOFILE is adequate.
- Fall back to fork+exec by disabling create_pidfd on spawn errors.
When it happens
Trigger: Spawning a child with Command and .create_pidfd(true) on a Linux kernel/glibc combination where pidfd_spawnp works but pidfd_getfd/pid retrieval does not - e.g. restricted seccomp filter blocking /proc, procfs unmounted, or EMFILE (out of fds). The fallback at PIDFD_SUPPORTED already ruled out ENOSYS, so this is a post-spawn inconsistency.
Common situations: Containerized runtime with procfs masked/read-only; seccomp profile missing the ioctl syscalls; very high fd usage hitting RLIMIT_NOFILE; older glibc advertising pidfd_spawnp but buggy pid query.
Related errors
- failed to open NUL device for child {stream}: {e}
- process groups are not supported on espidf
- Path already exists
- creating or truncating a file requires write or append acces
- must specify at least one of read, write, or append access
AI-assisted analysis of rust-lang/rust@7088e4b63a (2026-08-10).
Data as JSON: /api/errors/d1aada0d49fb82b6.
Report an issue: GitHub.