1515//! child will be associated with the job object as well. This means if we add
1616//! ourselves to the job object we create then everything will get torn down!
1717
18- pub fn setup ( ) {
18+ pub use self :: imp:: Setup ;
19+
20+ pub fn setup ( ) -> Option < Setup > {
1921 unsafe { imp:: setup ( ) }
2022}
2123
@@ -24,25 +26,44 @@ mod imp {
2426 use std:: env;
2527 use libc;
2628
27- pub unsafe fn setup ( ) {
29+ pub type Setup = ( ) ;
30+
31+ pub unsafe fn setup ( ) -> Option < ( ) > {
2832 // There's a test case for the behavior of
2933 // when-cargo-is-killed-subprocesses-are-also-killed, but that requires
3034 // one cargo spawned to become its own session leader, so we do that
3135 // here.
3236 if env:: var ( "__CARGO_TEST_SETSID_PLEASE_DONT_USE_ELSEWHERE" ) . is_ok ( ) {
3337 libc:: setsid ( ) ;
3438 }
39+ Some ( ( ) )
3540 }
3641}
3742
3843#[ cfg( windows) ]
3944mod imp {
4045 extern crate kernel32;
4146 extern crate winapi;
47+ extern crate psapi;
4248
49+ use std:: ffi:: OsString ;
50+ use std:: io;
4351 use std:: mem;
52+ use std:: os:: windows:: prelude:: * ;
53+
54+ pub struct Setup {
55+ job : Handle ,
56+ }
4457
45- pub unsafe fn setup ( ) {
58+ pub struct Handle {
59+ inner : winapi:: HANDLE ,
60+ }
61+
62+ fn last_err ( ) -> io:: Error {
63+ io:: Error :: last_os_error ( )
64+ }
65+
66+ pub unsafe fn setup ( ) -> Option < Setup > {
4667 // Creates a new job object for us to use and then adds ourselves to it.
4768 // Note that all errors are basically ignored in this function,
4869 // intentionally. Job objects are "relatively new" in Windows,
@@ -54,8 +75,9 @@ mod imp {
5475
5576 let job = kernel32:: CreateJobObjectW ( 0 as * mut _ , 0 as * const _ ) ;
5677 if job. is_null ( ) {
57- return
78+ return None
5879 }
80+ let job = Handle { inner : job } ;
5981
6082 // Indicate that when all handles to the job object are gone that all
6183 // process in the object should be killed. Note that this includes our
@@ -65,27 +87,174 @@ mod imp {
6587 info = mem:: zeroed ( ) ;
6688 info. BasicLimitInformation . LimitFlags =
6789 winapi:: JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE ;
68- let r = kernel32:: SetInformationJobObject ( job,
90+ let r = kernel32:: SetInformationJobObject ( job. inner ,
6991 winapi:: JobObjectExtendedLimitInformation ,
7092 & mut info as * mut _ as winapi:: LPVOID ,
7193 mem:: size_of_val ( & info) as winapi:: DWORD ) ;
7294 if r == 0 {
73- kernel32:: CloseHandle ( job) ;
74- return
95+ return None
7596 }
7697
7798 // Assign our process to this job object, meaning that our children will
7899 // now live or die based on our existence.
79100 let me = kernel32:: GetCurrentProcess ( ) ;
80- let r = kernel32:: AssignProcessToJobObject ( job, me) ;
101+ let r = kernel32:: AssignProcessToJobObject ( job. inner , me) ;
81102 if r == 0 {
82- kernel32:: CloseHandle ( job) ;
83- return
103+ return None
104+ }
105+
106+ Some ( Setup { job : job } )
107+ }
108+
109+ impl Drop for Setup {
110+ fn drop ( & mut self ) {
111+ // This is a litte subtle. By default if we are terminated then all
112+ // processes in our job object are terminated as well, but we
113+ // intentionally want to whitelist some processes to outlive our job
114+ // object (see below).
115+ //
116+ // To allow for this, we manually kill processes instead of letting
117+ // the job object kill them for us. We do this in a loop to handle
118+ // processes spawning other processes.
119+ //
120+ // Finally once this is all done we know that the only remaining
121+ // ones are ourselves and the whitelisted processes. The destructor
122+ // here then configures our job object to *not* kill everything on
123+ // close, then closes the job object.
124+ unsafe {
125+ while self . kill_remaining ( ) {
126+ info ! ( "killed some, going for more" ) ;
127+ }
128+
129+ let mut info: winapi:: JOBOBJECT_EXTENDED_LIMIT_INFORMATION ;
130+ info = mem:: zeroed ( ) ;
131+ let r = kernel32:: SetInformationJobObject (
132+ self . job . inner ,
133+ winapi:: JobObjectExtendedLimitInformation ,
134+ & mut info as * mut _ as winapi:: LPVOID ,
135+ mem:: size_of_val ( & info) as winapi:: DWORD ) ;
136+ if r == 0 {
137+ info ! ( "failed to configure job object to defaults: {}" ,
138+ last_err( ) ) ;
139+ }
140+ }
84141 }
142+ }
143+
144+ impl Setup {
145+ unsafe fn kill_remaining ( & mut self ) -> bool {
146+ #[ repr( C ) ]
147+ struct Jobs {
148+ header : winapi:: JOBOBJECT_BASIC_PROCESS_ID_LIST ,
149+ list : [ winapi:: ULONG_PTR ; 1024 ] ,
150+ }
151+
152+ let mut jobs: Jobs = mem:: zeroed ( ) ;
153+ let r = kernel32:: QueryInformationJobObject (
154+ self . job . inner ,
155+ winapi:: JobObjectBasicProcessIdList ,
156+ & mut jobs as * mut _ as winapi:: LPVOID ,
157+ mem:: size_of_val ( & jobs) as winapi:: DWORD ,
158+ 0 as * mut _ ) ;
159+ if r == 0 {
160+ info ! ( "failed to query job object: {}" , last_err( ) ) ;
161+ return false
162+ }
163+
164+ let mut killed = false ;
165+ let list = & jobs. list [ ..jobs. header . NumberOfProcessIdsInList as usize ] ;
166+ assert ! ( list. len( ) > 0 ) ;
167+ info ! ( "found {} remaining processes" , list. len( ) - 1 ) ;
168+
169+ let list = list. iter ( ) . filter ( |& & id| {
170+ // let's not kill ourselves
171+ id as winapi:: DWORD != kernel32:: GetCurrentProcessId ( )
172+ } ) . filter_map ( |& id| {
173+ // Open the process with the necessary rights, and if this
174+ // fails then we probably raced with the process exiting so we
175+ // ignore the problem.
176+ let flags = winapi:: PROCESS_QUERY_INFORMATION |
177+ winapi:: PROCESS_TERMINATE |
178+ winapi:: SYNCHRONIZE ;
179+ let p = kernel32:: OpenProcess ( flags,
180+ winapi:: FALSE ,
181+ id as winapi:: DWORD ) ;
182+ if p. is_null ( ) {
183+ None
184+ } else {
185+ Some ( Handle { inner : p } )
186+ }
187+ } ) . filter ( |p| {
188+ // Test if this process was actually in the job object or not.
189+ // If it's not then we likely raced with something else
190+ // recycling this PID, so we just skip this step.
191+ let mut res = 0 ;
192+ let r = kernel32:: IsProcessInJob ( p. inner , self . job . inner , & mut res) ;
193+ if r == 0 {
194+ info ! ( "failed to test is process in job: {}" , last_err( ) ) ;
195+ return false
196+ }
197+ res == winapi:: TRUE
198+ } ) ;
199+
200+
201+ for p in list {
202+ // Load the file which this process was spawned from. We then
203+ // later use this for identification purposes.
204+ let mut buf = [ 0 ; 1024 ] ;
205+ let r = psapi:: GetProcessImageFileNameW ( p. inner ,
206+ buf. as_mut_ptr ( ) ,
207+ buf. len ( ) as winapi:: DWORD ) ;
208+ if r == 0 {
209+ info ! ( "failed to get image name: {}" , last_err( ) ) ;
210+ continue
211+ }
212+ let s = OsString :: from_wide ( & buf[ ..r as usize ] ) ;
213+ info ! ( "found remaining: {:?}" , s) ;
85214
86- // Intentionally leak the `job` handle here. We've got the only
87- // reference to this job, so once it's gone we and all our children will
88- // be killed. This typically won't happen unless Cargo itself is
89- // ctrl-c'd.
215+ // And here's where we find the whole purpose for this
216+ // function! Currently, our only whitelisted process is
217+ // `mspdbsrv.exe`, and more details about that can be found
218+ // here:
219+ //
220+ // https://github.com/rust-lang/rust/issues/33145
221+ //
222+ // The gist of it is that all builds on one machine use the
223+ // same `mspdbsrv.exe` instance. If we were to kill this
224+ // instance then we could erroneously cause other builds to
225+ // fail.
226+ if let Some ( s) = s. to_str ( ) {
227+ if s. contains ( "mspdbsrv" ) {
228+ info ! ( "\t oops, this is mspdbsrv" ) ;
229+ continue
230+ }
231+ }
232+
233+ // Ok, this isn't mspdbsrv, let's kill the process. After we
234+ // kill it we wait on it to ensure that the next time around in
235+ // this function we're not going to see it again.
236+ let r = kernel32:: TerminateProcess ( p. inner , 1 ) ;
237+ if r == 0 {
238+ info ! ( "\t failed to kill subprocess: {}" , last_err( ) ) ;
239+ info ! ( "\t assuming subprocess is dead..." ) ;
240+ } else {
241+ info ! ( "\t terminated subprocess" ) ;
242+ }
243+ let r = kernel32:: WaitForSingleObject ( p. inner , winapi:: INFINITE ) ;
244+ if r != 0 {
245+ info ! ( "failed to wait for process to die: {}" , last_err( ) ) ;
246+ return false
247+ }
248+ killed = true ;
249+ }
250+
251+ return killed
252+ }
253+ }
254+
255+ impl Drop for Handle {
256+ fn drop ( & mut self ) {
257+ unsafe { kernel32:: CloseHandle ( self . inner ) ; }
258+ }
90259 }
91260}
0 commit comments