bootstrap/utils/
job.rs

1#[cfg(windows)]
2pub use for_windows::*;
3
4#[cfg(any(target_os = "haiku", target_os = "hermit", not(any(unix, windows))))]
5pub unsafe fn setup(_build: &mut crate::Build) {}
6
7#[cfg(all(unix, not(target_os = "haiku")))]
8pub unsafe fn setup(build: &mut crate::Build) {
9    if build.config.low_priority {
10        unsafe {
11            libc::setpriority(libc::PRIO_PGRP as _, 0, 10);
12        }
13    }
14}
15
16/// Job management on Windows for bootstrapping
17///
18/// Most of the time when you're running a build system (e.g., make) you expect
19/// Ctrl-C or abnormal termination to actually terminate the entire tree of
20/// processes in play. This currently works "by
21/// default" on Unix platforms because Ctrl-C actually sends a signal to the
22/// *process group* so everything will get torn
23/// down. On Windows, however, Ctrl-C is only sent to processes in the same console.
24/// If a process is detached or attached to another console, it won't receive the
25/// signal.
26///
27/// To achieve the same semantics on Windows we use Job Objects to ensure that
28/// all processes die at the same time. Job objects have a mode of operation
29/// where when all handles to the object are closed it causes all child
30/// processes associated with the object to be terminated immediately.
31/// Conveniently whenever a process in the job object spawns a new process the
32/// child will be associated with the job object as well. This means if we add
33/// ourselves to the job object we create then everything will get torn down!
34///
35/// Unfortunately most of the time the build system is actually called from a
36/// python wrapper (which manages things like building the build system) so this
37/// all doesn't quite cut it so far. To go the last mile we duplicate the job
38/// object handle into our parent process (a python process probably) and then
39/// close our own handle. This means that the only handle to the job object
40/// resides in the parent python process, so when python dies the whole build
41/// system dies (as one would probably expect!).
42///
43/// Note that this is a Windows specific module as none of this logic is required on Unix.
44#[cfg(windows)]
45mod for_windows {
46    use std::ffi::c_void;
47    use std::io;
48
49    use windows::Win32::Foundation::CloseHandle;
50    use windows::Win32::System::Diagnostics::Debug::{
51        SEM_NOGPFAULTERRORBOX, SetErrorMode, THREAD_ERROR_MODE,
52    };
53    use windows::Win32::System::JobObjects::{
54        AssignProcessToJobObject, CreateJobObjectW, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
55        JOB_OBJECT_LIMIT_PRIORITY_CLASS, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
56        JobObjectExtendedLimitInformation, SetInformationJobObject,
57    };
58    use windows::Win32::System::Threading::{BELOW_NORMAL_PRIORITY_CLASS, GetCurrentProcess};
59    use windows::core::PCWSTR;
60
61    use crate::Build;
62
63    pub unsafe fn setup(build: &mut Build) {
64        // SAFETY: pretty much everything below is unsafe
65        unsafe {
66            // Enable the Windows Error Reporting dialog which msys disables,
67            // so we can JIT debug rustc
68            let mode = SetErrorMode(THREAD_ERROR_MODE::default());
69            let mode = THREAD_ERROR_MODE(mode);
70            SetErrorMode(mode & !SEM_NOGPFAULTERRORBOX);
71
72            // Create a new job object for us to use
73            let job = CreateJobObjectW(None, PCWSTR::null()).unwrap();
74
75            // Indicate that when all handles to the job object are gone that all
76            // process in the object should be killed. Note that this includes our
77            // entire process tree by default because we've added ourselves and our
78            // children will reside in the job by default.
79            let mut info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
80            info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
81            if build.config.low_priority {
82                info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_PRIORITY_CLASS;
83                info.BasicLimitInformation.PriorityClass = BELOW_NORMAL_PRIORITY_CLASS.0;
84            }
85            let r = SetInformationJobObject(
86                job,
87                JobObjectExtendedLimitInformation,
88                &info as *const _ as *const c_void,
89                size_of_val(&info) as u32,
90            );
91            assert!(r.is_ok(), "{}", io::Error::last_os_error());
92
93            // Assign our process to this job object.
94            let r = AssignProcessToJobObject(job, GetCurrentProcess());
95            if r.is_err() {
96                CloseHandle(job).ok();
97                return;
98            }
99        }
100
101        // Note: we intentionally leak the job object handle. When our process exits
102        // (normally or abnormally) it will close the handle implicitly, causing all
103        // processes in the job to be cleaned up.
104    }
105}