use core::task::{Context, Poll};
use std::io;
use std::process::ExitStatus;
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::sync::atomic_waker::AtomicWaker;
use std::sync::Arc;
pub(crate) struct Exit {
pid: u32,
#[cfg(any(target_os = "linux", target_os = "android"))]
pidfd: Option<std::os::fd::OwnedFd>,
#[cfg(windows)]
process: std::os::windows::io::OwnedHandle,
#[cfg(unix)]
state: core::sync::atomic::AtomicU8,
result: OnceLock<Result<ExitStatus, i32>>,
settled: AtomicBool,
waker: AtomicWaker,
watched: AtomicBool,
gone: AtomicBool,
}
#[cfg(unix)]
const ALIVE: u8 = 0;
#[cfg(unix)]
const BUSY: u8 = 1;
#[cfg(unix)]
const REAPED: u8 = 2;
impl Exit {
pub(crate) fn new(child: &std::process::Child) -> io::Result<Arc<Self>> {
Ok(Arc::new(Self {
pid: child.id(),
#[cfg(any(target_os = "linux", target_os = "android"))]
pidfd: if no_pidfd() {
None
} else {
pidfd_open(child.id())
},
#[cfg(windows)]
process: {
use std::os::windows::io::AsHandle;
child.as_handle().try_clone_to_owned()?
},
#[cfg(unix)]
state: core::sync::atomic::AtomicU8::new(ALIVE),
result: OnceLock::new(),
settled: AtomicBool::new(false),
waker: AtomicWaker::new(),
watched: AtomicBool::new(false),
gone: AtomicBool::new(false),
}))
}
pub(crate) fn pid(&self) -> u32 {
self.pid
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn pidfd(&self) -> Option<std::os::fd::RawFd> {
use std::os::fd::AsRawFd;
self.pidfd.as_ref().map(AsRawFd::as_raw_fd)
}
#[cfg(windows)]
pub(crate) fn process(&self) -> crate::syscore::windows::HANDLE {
use std::os::windows::io::AsRawHandle;
self.process.as_raw_handle()
}
fn outcome(&self) -> Option<io::Result<ExitStatus>> {
self.result
.get()
.map(|r| r.map_err(io::Error::from_raw_os_error))
}
pub(crate) fn finish(&self, result: Result<ExitStatus, i32>) {
let _ = self.result.set(result);
self.settled.store(true, Ordering::SeqCst);
self.waker.wake();
}
fn finished(&self) -> bool {
self.result.get().is_some()
}
fn settled(&self) -> bool {
self.settled.load(Ordering::SeqCst)
}
pub(crate) fn poll_wait(
self: &Arc<Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<ExitStatus>> {
if let Some(outcome) = self.outcome() {
self.ensure_watched();
return Poll::Ready(outcome);
}
self.waker.register(cx.waker());
self.ensure_watched();
match self.outcome() {
Some(outcome) => Poll::Ready(outcome),
None => Poll::Pending,
}
}
pub(crate) fn ensure_watched(self: &Arc<Self>) {
if self.settled() || self.watched.swap(true, Ordering::SeqCst) {
return;
}
#[cfg(any(target_os = "linux", target_os = "android"))]
if self.pidfd.is_none() {
super::fallback::watch(self);
return;
}
crate::rt::shard::watch_exit(self);
}
pub(crate) fn unwatched(self: &Arc<Self>) {
self.watched.store(false, Ordering::SeqCst);
#[cfg(test)]
at_let_go();
if !self.gone.load(Ordering::SeqCst) {
self.waker.wake();
return;
}
if self.settled() || self.watched.swap(true, Ordering::SeqCst) {
return;
}
if !crate::rt::shard::watch_exit_elsewhere(self) {
self.watched.store(false, Ordering::SeqCst);
}
}
pub(crate) fn went(self: &Arc<Self>) {
self.gone.store(true, Ordering::SeqCst);
#[cfg(unix)]
self.watch_orphan();
}
#[cfg(unix)]
fn watch_orphan(self: &Arc<Self>) {
if self.settled() || self.watched.swap(true, Ordering::SeqCst) {
return;
}
#[cfg(any(target_os = "linux", target_os = "android"))]
if self.pidfd.is_none() {
super::fallback::watch(self);
return;
}
let here = crate::rt::context::current(|current| {
current.map(|shard| shard.watch(Arc::clone(self))).is_some()
});
if !here && !crate::rt::shard::watch_exit_elsewhere(self) {
self.watched.store(false, Ordering::SeqCst);
}
}
pub(crate) fn try_wait(self: &Arc<Self>) -> io::Result<Option<ExitStatus>> {
if let Some(outcome) = self.outcome() {
self.ensure_watched();
return outcome.map(Some);
}
self.ensure_watched();
let peeked = self.peek()?;
if let Some(status) = peeked {
let _ = self.result.set(Ok(status));
}
Ok(peeked)
}
pub(crate) fn kill(&self) -> io::Result<()> {
if self.finished() {
return Ok(());
}
#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(fd) = self.pidfd() {
let r = unsafe {
libc::syscall(
libc::SYS_pidfd_send_signal,
fd,
libc::SIGKILL,
core::ptr::null::<libc::siginfo_t>(),
0,
)
};
return match r {
0 => Ok(()),
_ => match io::Error::last_os_error() {
e if e.raw_os_error() == Some(libc::ESRCH) => Ok(()),
e => Err(e),
},
};
}
#[cfg(unix)]
{
if !self.busy() {
return Ok(());
}
let r = unsafe { libc::kill(self.pid as libc::pid_t, libc::SIGKILL) };
let err = io::Error::last_os_error();
self.state.store(ALIVE, Ordering::SeqCst);
match r {
0 => Ok(()),
_ if err.raw_os_error() == Some(libc::ESRCH) => Ok(()),
_ => Err(err),
}
}
#[cfg(windows)]
{
if unsafe { crate::syscore::windows::TerminateProcess(self.process(), 1) } != 0 {
return Ok(());
}
match io::Error::last_os_error() {
_ if self.peek().ok().flatten().is_some() => Ok(()),
e => Err(e),
}
}
}
#[cfg(unix)]
fn busy(&self) -> bool {
loop {
match self
.state
.compare_exchange(ALIVE, BUSY, Ordering::SeqCst, Ordering::SeqCst)
{
Ok(_) => return true,
Err(REAPED) => return false,
Err(_) => std::thread::yield_now(),
}
}
}
#[cfg(unix)]
pub(crate) fn reap_by_pid(&self) -> Option<Result<ExitStatus, i32>> {
use std::os::unix::process::ExitStatusExt;
if !self.busy() {
return None;
}
let mut raw = 0;
let r = unsafe { libc::waitpid(self.pid as libc::pid_t, &mut raw, libc::WNOHANG) };
let errno = io::Error::last_os_error()
.raw_os_error()
.unwrap_or(libc::EIO);
let (state, outcome) = match r {
0 => (ALIVE, None),
r if r > 0 => (REAPED, Some(Ok(ExitStatus::from_raw(raw)))),
_ => (REAPED, Some(Err(errno))),
};
self.state.store(state, Ordering::SeqCst);
outcome
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub(crate) fn reap_pidfd(&self) -> Option<Result<ExitStatus, i32>> {
let fd = self.pidfd()?;
waitid_pidfd(fd, libc::WEXITED | libc::WNOHANG)
}
fn peek(&self) -> io::Result<Option<ExitStatus>> {
#[cfg(any(target_os = "linux", target_os = "android"))]
if let Some(fd) = self.pidfd() {
return match waitid_pidfd(fd, libc::WEXITED | libc::WNOHANG | libc::WNOWAIT) {
None => Ok(None),
Some(r) => r.map(Some).map_err(io::Error::from_raw_os_error),
};
}
#[cfg(unix)]
{
let mut info: libc::siginfo_t = unsafe { core::mem::zeroed() };
let r = unsafe {
libc::waitid(
libc::P_PID,
self.pid as libc::id_t,
&mut info,
libc::WEXITED | libc::WNOHANG | libc::WNOWAIT,
)
};
if r != 0 {
return Err(io::Error::last_os_error());
}
Ok(status_of(&info))
}
#[cfg(windows)]
{
windows_status(self.process())
}
}
}
#[cfg(unix)]
pub(crate) fn status_of(info: &libc::siginfo_t) -> Option<ExitStatus> {
use std::os::unix::process::ExitStatusExt;
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "netbsd",
target_os = "openbsd"
))]
let (pid, code, status) = unsafe { (info.si_pid(), info.si_code, info.si_status()) };
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "netbsd",
target_os = "openbsd"
)))]
let (pid, code, status) = (info.si_pid, info.si_code, info.si_status);
if pid == 0 {
return None;
}
let raw = match code {
libc::CLD_EXITED => (status & 0xff) << 8,
libc::CLD_DUMPED => (status & 0x7f) | 0x80,
_ => status & 0x7f,
};
Some(ExitStatus::from_raw(raw))
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn waitid_pidfd(fd: std::os::fd::RawFd, options: libc::c_int) -> Option<Result<ExitStatus, i32>> {
let mut info: libc::siginfo_t = unsafe { core::mem::zeroed() };
let r = unsafe { libc::waitid(libc::P_PIDFD, fd as libc::id_t, &mut info, options) };
if r != 0 {
return Some(Err(io::Error::last_os_error()
.raw_os_error()
.unwrap_or(libc::EIO)));
}
status_of(&info).map(Ok)
}
#[cfg(test)]
thread_local! {
pub(crate) static AT_LET_GO: core::cell::RefCell<Option<Box<dyn FnOnce()>>> =
const { core::cell::RefCell::new(None) };
}
#[cfg(test)]
fn at_let_go() {
if let Some(hook) = AT_LET_GO.with(|hook| hook.borrow_mut().take()) {
hook();
}
}
#[cfg(all(test, any(target_os = "linux", target_os = "android")))]
thread_local! {
pub(crate) static NO_PIDFD: core::cell::Cell<bool> = const { core::cell::Cell::new(false) };
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn no_pidfd() -> bool {
#[cfg(test)]
return NO_PIDFD.with(core::cell::Cell::get);
#[cfg(not(test))]
false
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn pidfd_open(pid: u32) -> Option<std::os::fd::OwnedFd> {
use std::os::fd::FromRawFd;
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0) };
if fd < 0 {
return None;
}
Some(unsafe { std::os::fd::OwnedFd::from_raw_fd(fd as std::os::fd::RawFd) })
}
#[cfg(windows)]
pub(crate) fn windows_status(
process: crate::syscore::windows::HANDLE,
) -> io::Result<Option<ExitStatus>> {
use std::os::windows::process::ExitStatusExt;
use crate::syscore::windows::{GetExitCodeProcess, WAIT_OBJECT_0, WaitForSingleObject};
if unsafe { WaitForSingleObject(process, 0) } != WAIT_OBJECT_0 {
return Ok(None);
}
let mut code = 0u32;
if unsafe { GetExitCodeProcess(process, &mut code) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(Some(ExitStatus::from_raw(code)))
}