1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
//! Job management (mostly for windows) //! //! Most of the time when you're running cargo you expect Ctrl-C to actually //! terminate the entire tree of processes in play, not just the one at the top //! (cago). This currently works "by default" on Unix platforms because Ctrl-C //! actually sends a signal to the *process group* rather than the parent //! process, so everything will get torn down. On Windows, however, this does //! not happen and Ctrl-C just kills cargo. //! //! To achieve the same semantics on Windows we use Job Objects to ensure that //! all processes die at the same time. Job objects have a mode of operation //! where when all handles to the object are closed it causes all child //! processes associated with the object to be terminated immediately. //! Conveniently whenever a process in the job object spawns a new process the //! child will be associated with the job object as well. This means if we add //! ourselves to the job object we create then everything will get torn down! pub fn setup() { unsafe { imp::setup() } } #[cfg(unix)] mod imp { use std::env; use libc; pub unsafe fn setup() { // There's a test case for the behavior of // when-cargo-is-killed-subprocesses-are-also-killed, but that requires // one cargo spawned to become its own session leader, so we do that // here. if env::var("__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE").is_ok() { libc::setsid(); } } } #[cfg(windows)] mod imp { extern crate kernel32; extern crate winapi; use std::mem; pub unsafe fn setup() { // Creates a new job object for us to use and then adds ourselves to it. // Note that all errors are basically ignored in this function, // intentionally. Job objects are "relatively new" in Windows, // particularly the ability to support nested job objects. Older // Windows installs don't support this ability. We probably don't want // to force Cargo to abort in this situation or force others to *not* // use job objects, so we instead just ignore errors and assume that // we're otherwise part of someone else's job object in this case. let job = kernel32::CreateJobObjectW(0 as *mut _, 0 as *const _); if job.is_null() { return } // Indicate that when all handles to the job object are gone that all // process in the object should be killed. Note that this includes our // entire process tree by default because we've added ourselves and and // our children will reside in the job once we spawn a process. let mut info: winapi::JOBOBJECT_EXTENDED_LIMIT_INFORMATION; info = mem::zeroed(); info.BasicLimitInformation.LimitFlags = winapi::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; let r = kernel32::SetInformationJobObject(job, winapi::JobObjectExtendedLimitInformation, &mut info as *mut _ as winapi::LPVOID, mem::size_of_val(&info) as winapi::DWORD); if r == 0 { kernel32::CloseHandle(job); return } // Assign our process to this job object, meaning that our children will // now live or die based on our existence. let me = kernel32::GetCurrentProcess(); let r = kernel32::AssignProcessToJobObject(job, me); if r == 0 { kernel32::CloseHandle(job); return } // Intentionally leak the `job` handle here. We've got the only // reference to this job, so once it's gone we and all our children will // be killed. This typically won't happen unless Cargo itself is // ctrl-c'd. } }