use std::rc::Rc;
use anyhow::Result;
use tokio::process::Child;
#[derive(Clone)]
pub struct ChildProcessTracker(Rc<dyn Tracker>);
impl ChildProcessTracker {
#[cfg(windows)]
pub fn new() -> Self {
match windows::JobObject::new() {
Ok(tracker) => Self(Rc::new(tracker)),
Err(err) => {
if cfg!(debug_assertions) {
panic!("Could not start tracking processes. {:#}", err);
} else {
Self(Rc::new(NullChildProcessTracker))
}
}
}
}
#[cfg(not(windows))]
pub fn new() -> Self {
Self(Rc::new(NullChildProcessTracker))
}
pub fn track(&self, child: &Child) {
if let Err(err) = self.0.track(child)
&& cfg!(debug_assertions)
&& child.id().is_some()
{
panic!("Could not track process: {:#}", err);
}
}
}
trait Tracker: Send + Sync {
fn track(&self, child: &Child) -> Result<()>;
}
struct NullChildProcessTracker;
impl Tracker for NullChildProcessTracker {
fn track(&self, _: &Child) -> Result<()> {
Ok(())
}
}
#[cfg(target_os = "windows")]
mod windows {
use std::ptr;
use anyhow::Result;
use anyhow::bail;
use tokio::process::Child;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
use windows_sys::Win32::Foundation::TRUE;
use windows_sys::Win32::System::JobObjects::AssignProcessToJobObject;
use windows_sys::Win32::System::JobObjects::CreateJobObjectW;
use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_BREAKAWAY_OK;
use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
use windows_sys::Win32::System::JobObjects::JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK;
use windows_sys::Win32::System::JobObjects::JOBOBJECT_EXTENDED_LIMIT_INFORMATION;
use windows_sys::Win32::System::JobObjects::JobObjectExtendedLimitInformation;
use windows_sys::Win32::System::JobObjects::SetInformationJobObject;
use super::Tracker;
pub struct JobObject(WinHandle);
impl JobObject {
pub fn new() -> Result<Self> {
unsafe {
let handle =
WinHandle::new(CreateJobObjectW(ptr::null_mut(), ptr::null()));
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
info.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
| JOB_OBJECT_LIMIT_BREAKAWAY_OK
| JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK
| JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION;
let result = SetInformationJobObject(
handle.as_raw(),
JobObjectExtendedLimitInformation,
&mut info as *mut _ as *mut core::ffi::c_void,
std::mem::size_of_val(&info) as u32,
);
if result != TRUE {
bail!(
"Could not set job information object. {:#}",
std::io::Error::last_os_error()
);
}
Ok(Self(handle))
}
}
fn add_process_handle(&self, process_handle: HANDLE) -> Result<()> {
unsafe {
let result = AssignProcessToJobObject(self.0.as_raw(), process_handle);
if result != TRUE {
bail!(
"Could not assign process to job object. {:#}",
std::io::Error::last_os_error()
);
} else {
Ok(())
}
}
}
}
impl Tracker for JobObject {
fn track(&self, child: &Child) -> Result<()> {
if let Some(handle) = child.raw_handle() {
self.add_process_handle(handle)
} else {
Ok(())
}
}
}
struct WinHandle(HANDLE);
impl WinHandle {
pub fn new(handle: HANDLE) -> Self {
WinHandle(handle)
}
pub fn as_raw(&self) -> HANDLE {
self.0
}
}
unsafe impl Send for WinHandle {}
unsafe impl Sync for WinHandle {}
impl Drop for WinHandle {
fn drop(&mut self) {
unsafe {
if !self.0.is_null() && self.0 != INVALID_HANDLE_VALUE {
windows_sys::Win32::Foundation::CloseHandle(self.0);
}
}
}
}
}