Alex Crichton | 046e687 | 2015-11-19 23:20:12 | [diff] [blame] | 1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
| 2 | // file at the top-level directory of this distribution and at |
| 3 | // http://rust-lang.org/COPYRIGHT. |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 8 | // option. This file may not be copied, modified, or distributed |
| 9 | // except according to those terms. |
| 10 | |
| 11 | //! Job management on Windows for bootstrapping |
| 12 | //! |
| 13 | //! Most of the time when you're running a build system (e.g. make) you expect |
| 14 | //! Ctrl-C or abnormal termination to actually terminate the entire tree of |
| 15 | //! process in play, not just the one at the top. This currently works "by |
| 16 | //! default" on Unix platforms because Ctrl-C actually sends a signal to the |
| 17 | //! *process group* rather than the parent process, so everything will get torn |
| 18 | //! down. On Windows, however, this does not happen and Ctrl-C just kills the |
| 19 | //! parent process. |
| 20 | //! |
| 21 | //! To achieve the same semantics on Windows we use Job Objects to ensure that |
| 22 | //! all processes die at the same time. Job objects have a mode of operation |
| 23 | //! where when all handles to the object are closed it causes all child |
| 24 | //! processes associated with the object to be terminated immediately. |
| 25 | //! Conveniently whenever a process in the job object spawns a new process the |
| 26 | //! child will be associated with the job object as well. This means if we add |
| 27 | //! ourselves to the job object we create then everything will get torn down! |
| 28 | //! |
| 29 | //! Unfortunately most of the time the build system is actually called from a |
| 30 | //! python wrapper (which manages things like building the build system) so this |
| 31 | //! all doesn't quite cut it so far. To go the last mile we duplicate the job |
| 32 | //! object handle into our parent process (a python process probably) and then |
| 33 | //! close our own handle. This means that the only handle to the job object |
| 34 | //! resides in the parent python process, so when python dies the whole build |
| 35 | //! system dies (as one would probably expect!). |
| 36 | //! |
| 37 | //! Note that this module has a #[cfg(windows)] above it as none of this logic |
| 38 | //! is required on Unix. |
| 39 | |
| 40 | extern crate kernel32; |
| 41 | extern crate winapi; |
| 42 | |
| 43 | use std::env; |
| 44 | use std::io; |
| 45 | use std::mem; |
| 46 | |
| 47 | use self::winapi::*; |
| 48 | use self::kernel32::*; |
| 49 | |
| 50 | pub unsafe fn setup() { |
| 51 | // Create a new job object for us to use |
| 52 | let job = CreateJobObjectW(0 as *mut _, 0 as *const _); |
| 53 | assert!(job != 0 as *mut _, "{}", io::Error::last_os_error()); |
| 54 | |
| 55 | // Indicate that when all handles to the job object are gone that all |
| 56 | // process in the object should be killed. Note that this includes our |
Georg Brandl | 26eb2be | 2016-05-05 19:11:41 | [diff] [blame] | 57 | // entire process tree by default because we've added ourselves and our |
Alex Crichton | 046e687 | 2015-11-19 23:20:12 | [diff] [blame] | 58 | // children will reside in the job by default. |
| 59 | let mut info = mem::zeroed::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>(); |
| 60 | info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; |
| 61 | let r = SetInformationJobObject(job, |
| 62 | JobObjectExtendedLimitInformation, |
| 63 | &mut info as *mut _ as LPVOID, |
| 64 | mem::size_of_val(&info) as DWORD); |
| 65 | assert!(r != 0, "{}", io::Error::last_os_error()); |
| 66 | |
Alex Crichton | 07638b9 | 2016-02-12 04:46:47 | [diff] [blame] | 67 | // Assign our process to this job object. Note that if this fails, one very |
| 68 | // likely reason is that we are ourselves already in a job object! This can |
| 69 | // happen on the build bots that we've got for Windows, or if just anyone |
| 70 | // else is instrumenting the build. In this case we just bail out |
| 71 | // immediately and assume that they take care of it. |
| 72 | // |
| 73 | // Also note that nested jobs (why this might fail) are supported in recent |
| 74 | // versions of Windows, but the version of Windows that our bots are running |
| 75 | // at least don't support nested job objects. |
Alex Crichton | 046e687 | 2015-11-19 23:20:12 | [diff] [blame] | 76 | let r = AssignProcessToJobObject(job, GetCurrentProcess()); |
Alex Crichton | 07638b9 | 2016-02-12 04:46:47 | [diff] [blame] | 77 | if r == 0 { |
| 78 | CloseHandle(job); |
| 79 | return |
| 80 | } |
Alex Crichton | 046e687 | 2015-11-19 23:20:12 | [diff] [blame] | 81 | |
| 82 | // If we've got a parent process (e.g. the python script that called us) |
| 83 | // then move ownership of this job object up to them. That way if the python |
| 84 | // script is killed (e.g. via ctrl-c) then we'll all be torn down. |
| 85 | // |
| 86 | // If we don't have a parent (e.g. this was run directly) then we |
| 87 | // intentionally leak the job object handle. When our process exits |
| 88 | // (normally or abnormally) it will close the handle implicitly, causing all |
| 89 | // processes in the job to be cleaned up. |
| 90 | let pid = match env::var("BOOTSTRAP_PARENT_ID") { |
| 91 | Ok(s) => s, |
| 92 | Err(..) => return, |
| 93 | }; |
| 94 | |
| 95 | let parent = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid.parse().unwrap()); |
| 96 | assert!(parent != 0 as *mut _, "{}", io::Error::last_os_error()); |
| 97 | let mut parent_handle = 0 as *mut _; |
| 98 | let r = DuplicateHandle(GetCurrentProcess(), job, |
| 99 | parent, &mut parent_handle, |
| 100 | 0, FALSE, DUPLICATE_SAME_ACCESS); |
| 101 | |
| 102 | // If this failed, well at least we tried! An example of DuplicateHandle |
| 103 | // failing in the past has been when the wrong python2 package spawed this |
| 104 | // build system (e.g. the `python2` package in MSYS instead of |
| 105 | // `mingw-w64-x86_64-python2`. Not sure why it failed, but the "failure |
| 106 | // mode" here is that we only clean everything up when the build system |
| 107 | // dies, not when the python parent does, so not too bad. |
| 108 | if r != 0 { |
| 109 | CloseHandle(job); |
| 110 | } |
| 111 | } |