Skip to main content

agentmux_launcher/
job_object.rs

1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4/// Owns a Windows Job Object handle. CloseHandle on drop. The job's
5/// `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` flag means closing the last handle
6/// terminates every assigned process — which is what we want as a backstop
7/// if this launcher dies abruptly.
8#[cfg(target_os = "windows")]
9pub(crate) struct JobHandle(pub(crate) windows_sys::Win32::Foundation::HANDLE);
10
11#[cfg(target_os = "windows")]
12unsafe impl Send for JobHandle {}
13
14#[cfg(target_os = "windows")]
15impl Drop for JobHandle {
16    fn drop(&mut self) {
17        if !self.0.is_null() {
18            unsafe {
19                windows_sys::Win32::Foundation::CloseHandle(self.0);
20            }
21        }
22    }
23}
24
25/// Create a Job Object J0 with `KILL_ON_JOB_CLOSE`. Caller assigns
26/// processes to it via `srv_spawner::assign_pid_to_job(pid, job)`.
27#[cfg(target_os = "windows")]
28pub(crate) fn create_job_object() -> Result<windows_sys::Win32::Foundation::HANDLE, String> {
29    use windows_sys::Win32::Foundation::CloseHandle;
30    use windows_sys::Win32::System::JobObjects::*;
31
32    unsafe {
33        let job = CreateJobObjectW(std::ptr::null(), std::ptr::null());
34        if job.is_null() {
35            return Err("CreateJobObjectW returned null".into());
36        }
37        let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
38        info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
39        let ok = SetInformationJobObject(
40            job,
41            JobObjectExtendedLimitInformation,
42            &info as *const _ as *const std::ffi::c_void,
43            std::mem::size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
44        );
45        if ok == 0 {
46            CloseHandle(job);
47            return Err("SetInformationJobObject failed".into());
48        }
49        Ok(job)
50    }
51}