use std::io;
use std::time::Duration;
use tokio::process::{Child, Command};
use windows_sys::Win32::Foundation::{CloseHandle, FILETIME, HANDLE};
use windows_sys::Win32::System::JobObjects::{
AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JobObjectBasicAccountingInformation, JobObjectExtendedLimitInformation,
QueryInformationJobObject, SetInformationJobObject, TerminateJobObject,
};
use windows_sys::Win32::System::ProcessStatus::{K32GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS};
use windows_sys::Win32::System::Threading::{
GetProcessTimes, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION,
};
use crate::Mechanism;
use crate::stats::ProcessGroupStats;
use crate::sys::ProcMetrics;
pub(crate) struct Job {
handle: HANDLE,
}
unsafe impl Send for Job {}
unsafe impl Sync for Job {}
impl Job {
pub(crate) fn new() -> io::Result<Self> {
let handle = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
if handle.is_null() {
return Err(io::Error::last_os_error());
}
let job = Job { handle };
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { std::mem::zeroed() };
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let ok = unsafe {
SetInformationJobObject(
job.handle,
JobObjectExtendedLimitInformation,
std::ptr::from_ref(&info).cast(),
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(job)
}
pub(crate) fn spawn(&self, cmd: &mut Command) -> io::Result<Child> {
let mut child = cmd.spawn()?;
let handle = child.raw_handle().ok_or_else(|| {
io::Error::other("child exited before it could be assigned to the job")
})?;
let ok = unsafe { AssignProcessToJobObject(self.handle, handle as HANDLE) };
if ok == 0 {
let err = io::Error::last_os_error();
let _ = child.start_kill();
return Err(err);
}
Ok(child)
}
pub(crate) fn adopt(&self, child: &Child) -> io::Result<()> {
let handle = child
.raw_handle()
.ok_or_else(|| io::Error::other("child has no handle (already exited?)"))?;
let ok = unsafe { AssignProcessToJobObject(self.handle, handle as HANDLE) };
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) fn kill_all(&self) -> io::Result<()> {
let ok = unsafe { TerminateJobObject(self.handle, 1) };
if ok == 0 {
return Err(io::Error::last_os_error());
}
Ok(())
}
pub(crate) async fn graceful_shutdown(
&self,
_timeout: Duration,
_escalate: bool,
) -> io::Result<()> {
self.kill_all()
}
pub(crate) fn stats(&self) -> io::Result<ProcessGroupStats> {
let mut acct: JOBOBJECT_BASIC_ACCOUNTING_INFORMATION = unsafe { std::mem::zeroed() };
let ok = unsafe {
QueryInformationJobObject(
self.handle,
JobObjectBasicAccountingInformation,
std::ptr::from_mut(&mut acct).cast(),
std::mem::size_of::<JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>() as u32,
std::ptr::null_mut(),
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
let mut ext: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = unsafe { std::mem::zeroed() };
let ok = unsafe {
QueryInformationJobObject(
self.handle,
JobObjectExtendedLimitInformation,
std::ptr::from_mut(&mut ext).cast(),
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
std::ptr::null_mut(),
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
let cpu_100ns = (acct.TotalUserTime as u64).saturating_add(acct.TotalKernelTime as u64);
Ok(ProcessGroupStats {
active_process_count: acct.ActiveProcesses as usize,
total_cpu_time: Some(Duration::from_nanos(cpu_100ns.saturating_mul(100))),
peak_memory_bytes: Some(ext.PeakJobMemoryUsed as u64),
})
}
pub(crate) fn mechanism(&self) -> Mechanism {
Mechanism::JobObject
}
}
fn filetime_nanos(ft: FILETIME) -> u64 {
let units = ((ft.dwHighDateTime as u64) << 32) | ft.dwLowDateTime as u64;
units.saturating_mul(100)
}
pub(crate) fn process_metrics(pid: u32) -> ProcMetrics {
let mut metrics = ProcMetrics::default();
let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
if handle.is_null() {
return metrics;
}
let mut creation = FILETIME {
dwLowDateTime: 0,
dwHighDateTime: 0,
};
let mut exit = creation;
let mut kernel = creation;
let mut user = creation;
let ok = unsafe { GetProcessTimes(handle, &mut creation, &mut exit, &mut kernel, &mut user) };
if ok != 0 {
metrics.cpu_time = Some(Duration::from_nanos(
filetime_nanos(kernel) + filetime_nanos(user),
));
}
let mut counters: PROCESS_MEMORY_COUNTERS = unsafe { std::mem::zeroed() };
counters.cb = std::mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
let ok = unsafe { K32GetProcessMemoryInfo(handle, &mut counters, counters.cb) };
if ok != 0 {
metrics.peak_memory_bytes = Some(counters.PeakWorkingSetSize as u64);
}
unsafe { CloseHandle(handle) };
metrics
}
impl Drop for Job {
fn drop(&mut self) {
unsafe { CloseHandle(self.handle) };
}
}