use crate::error::RunnerError;
pub(crate) struct JobObjectHandle {
handle: windows::Win32::Foundation::HANDLE,
}
unsafe impl Send for JobObjectHandle {}
impl Drop for JobObjectHandle {
fn drop(&mut self) {
use windows::Win32::Foundation::CloseHandle;
unsafe {
let _ = CloseHandle(self.handle);
}
}
}
pub(crate) fn create_job_object() -> Result<JobObjectHandle, RunnerError> {
use windows::Win32::System::JobObjects::{
CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
JobObjectExtendedLimitInformation, SetInformationJobObject,
};
unsafe {
let job = CreateJobObjectW(None, None).map_err(|e| RunnerError::NativeExecutionFailed {
reason: format!("Failed to create Job Object: {e}"),
})?;
let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(
job,
JobObjectExtendedLimitInformation,
(&raw const info).cast(),
std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
)
.map_err(|e| RunnerError::NativeExecutionFailed {
reason: format!("Failed to configure Job Object: {e}"),
})?;
Ok(JobObjectHandle { handle: job })
}
}
pub(crate) fn assign_to_job(
job: &JobObjectHandle,
child: &tokio::process::Child,
) -> Result<(), RunnerError> {
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::JobObjects::AssignProcessToJobObject;
use windows::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS};
if let Some(pid) = child.id() {
unsafe {
let process_handle = OpenProcess(PROCESS_ALL_ACCESS, false, pid).map_err(|e| {
RunnerError::NativeExecutionFailed {
reason: format!("Failed to open process for job assignment: {e}"),
}
})?;
AssignProcessToJobObject(job.handle, process_handle).map_err(|e| {
let _ = CloseHandle(process_handle);
RunnerError::NativeExecutionFailed {
reason: format!("Failed to assign process to Job Object: {e}"),
}
})?;
let _ = CloseHandle(process_handle);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::create_job_object;
#[test]
fn test_job_object_handle_creation() {
let result = create_job_object();
assert!(result.is_ok(), "Job Object creation should succeed");
println!("Job Object handle creation test passed");
}
#[test]
fn test_job_object_handle_drop() {
{
let _job = create_job_object().unwrap();
}
println!("Job Object handle drop test passed");
}
#[test]
fn test_multiple_job_object_handles() {
let job1 = create_job_object();
let job2 = create_job_object();
assert!(job1.is_ok(), "First Job Object creation should succeed");
assert!(job2.is_ok(), "Second Job Object creation should succeed");
println!("Multiple Job Object handles test passed");
}
}