Skip to main content

bootstrap/utils/
job.rs

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