Skip to content

Commit 919ce8d

Browse files
fix(procmgr): defer Windows profile unload when job query fails
Retain profile and job handles for async drain when QueryInformationJobObject fails or reports remaining members, instead of dropping resources and unloading the user profile immediately.
1 parent 9c160fd commit 919ce8d

2 files changed

Lines changed: 56 additions & 40 deletions

File tree

pkg/procmgr/rust/src/platform/windows/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ impl JobObject {
188188
}
189189
}
190190

191+
/// Returns true when the job may still have running members, including when the
192+
/// active process count could not be queried.
193+
pub(crate) fn may_have_active_members(&self) -> bool {
194+
!matches!(self.active_process_count(), Ok(0))
195+
}
196+
191197
/// Block until every process in the job has exited or `timeout` elapses.
192198
pub fn wait_until_empty(&self, timeout: std::time::Duration) -> bool {
193199
const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(100);

pkg/procmgr/rust/src/process.rs

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -273,46 +273,56 @@ impl ManagedProcess {
273273
/// Returns `true` when profile unload was deferred and a background job drain is needed.
274274
#[cfg(windows)]
275275
fn release_windows_spawn_resources_after_exit(&mut self, exited_pid: Option<u32>) -> bool {
276-
if let Some(ref job) = self.job_object {
277-
match job.active_process_count() {
278-
Ok(0) => {
279-
self.clear_windows_spawn_resources();
280-
return false;
281-
}
282-
Ok(_) => {
283-
if let (Some(pid), Some(profile)) = (exited_pid, self.user_profile.take()) {
284-
if let Err(e) = job.terminate() {
285-
warn!(
286-
"[{}] failed to terminate residual job members before deferred drain (pid {}): {e:#}",
287-
self.name, pid
288-
);
289-
}
290-
let job = self.job_object.take();
291-
info!(
292-
"[{}] deferring profile unload until job members exit (pid {})",
293-
self.name, pid
294-
);
295-
self.deferred_exit_cleanups.push(DeferredExitCleanup {
296-
pid,
297-
user_profile: profile,
298-
job_object: job,
299-
});
300-
return true;
301-
}
302-
warn!(
303-
"[{}] job still has active processes but no profile to defer",
304-
self.name
305-
);
306-
}
307-
Err(e) => {
308-
warn!(
309-
"[{}] failed to query job active processes: {e:#}",
310-
self.name
311-
);
312-
}
276+
let Some(job) = self.job_object.as_ref() else {
277+
self.clear_windows_spawn_resources();
278+
return false;
279+
};
280+
let count = job.active_process_count();
281+
if matches!(count, Ok(0)) {
282+
self.clear_windows_spawn_resources();
283+
return false;
284+
}
285+
if let Err(e) = count {
286+
warn!(
287+
"[{}] failed to query job active processes: {e:#}",
288+
self.name
289+
);
290+
}
291+
let needs_drain = self.defer_windows_job_drain_after_exit(exited_pid);
292+
if !needs_drain {
293+
self.clear_windows_spawn_resources();
294+
}
295+
needs_drain
296+
}
297+
298+
/// Retain profile and job handles for async drain when members may still be running.
299+
#[cfg(windows)]
300+
fn defer_windows_job_drain_after_exit(&mut self, exited_pid: Option<u32>) -> bool {
301+
if let (Some(pid), Some(profile)) = (exited_pid, self.user_profile.take()) {
302+
if let Some(ref job) = self.job_object
303+
&& let Err(e) = job.terminate()
304+
{
305+
warn!(
306+
"[{}] failed to terminate residual job members before deferred drain (pid {}): {e:#}",
307+
self.name, pid
308+
);
313309
}
310+
let job = self.job_object.take();
311+
info!(
312+
"[{}] deferring profile unload until job members exit (pid {pid})",
313+
self.name
314+
);
315+
self.deferred_exit_cleanups.push(DeferredExitCleanup {
316+
pid,
317+
user_profile: profile,
318+
job_object: job,
319+
});
320+
return true;
314321
}
315-
self.clear_windows_spawn_resources();
322+
warn!(
323+
"[{}] job may still have active members but no profile to defer",
324+
self.name
325+
);
316326
false
317327
}
318328

@@ -329,7 +339,7 @@ impl ManagedProcess {
329339
.job_object
330340
.as_ref()
331341
.expect("deferred job drain entry must have a job");
332-
if !matches!(job.active_process_count(), Ok(0)) {
342+
if job.may_have_active_members() {
333343
return false;
334344
}
335345
let entry = self.deferred_exit_cleanups.remove(idx);
@@ -359,7 +369,7 @@ impl ManagedProcess {
359369
if self.deferred_exit_cleanups[idx]
360370
.job_object
361371
.as_ref()
362-
.is_some_and(|job| job.active_process_count().is_ok_and(|count| count > 0))
372+
.is_some_and(platform::JobObject::may_have_active_members)
363373
{
364374
return true;
365375
}

0 commit comments

Comments
 (0)