Actually check process when querying for completion

With --on-update=do-nothing, we need to know when the process is done
before we can spawn a new one, but we never actually used to truly check
the process, only the presence or absence of a spawned process. That
process may have already completed, but because we don't wait on it when
in do-nothing mode, there is no opportunity to notice this.

So now we either actually check the completion status of the process (on
Windows), or we expose the `done` mutex value on demand (Unix).
Essentially this adds a way to check the completion status of the
process without blocking (modulo a mutex lock on unix).

Fixes #200
This commit is contained in:
Félix Saparelli 2021-07-01 01:20:50 +12:00
parent 960bbbabbc
commit 71a178d4c2
2 changed files with 48 additions and 16 deletions

View File

@ -241,6 +241,13 @@ mod imp {
.expect("poisoned cvar in process::wait");
}
}
pub fn is_complete(&self) -> bool {
*self
.lock
.lock()
.expect("poisoned lock in process::is_complete")
}
}
}
@ -393,22 +400,37 @@ mod imp {
}
}
// Some(true) if complete, Some(false) if incomplete, None if timed out
fn wait_for_completion(&self, timeout: DWORD) -> Option<bool> {
let mut code: DWORD = 0;
let mut key: ULONG_PTR = 0;
let mut overlapped = mem::MaybeUninit::<LPOVERLAPPED>::uninit();
if 0 == unsafe {
GetQueuedCompletionStatus(
self.completion_port,
&mut code,
&mut key,
overlapped.as_mut_ptr(),
timeout,
)
} {
// TODO: handle errors? how does that manifest for timeouts?
None
} else {
Some(code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO && (key as HANDLE) == self.job)
}
}
pub fn is_complete(&self) -> bool {
self.wait_for_completion(0).unwrap_or(false)
}
pub fn wait(&self) {
loop {
let mut code: DWORD = 0;
let mut key: ULONG_PTR = 0;
let mut overlapped = mem::MaybeUninit::<LPOVERLAPPED>::uninit();
unsafe {
GetQueuedCompletionStatus(
self.completion_port,
&mut code,
&mut key,
overlapped.as_mut_ptr(),
INFINITE,
);
}
if code == JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO && (key as HANDLE) == self.job {
if self.wait_for_completion(INFINITE).expect(
"GetQueuedCompletionStatus passed INFINITE timeout but timed out anyway",
) {
break;
}
}

View File

@ -194,7 +194,10 @@ impl ExecHandler {
.read()
.expect("poisoned lock in has_running_process");
(*guard).is_some()
(*guard)
.as_ref()
.map(|process| !process.is_complete())
.unwrap_or(false)
}
}
@ -216,8 +219,15 @@ impl Handler for ExecHandler {
// Only returns Err() on lock poisoning.
fn on_update(&self, ops: &[PathOp]) -> Result<bool> {
let signal = self.signal.unwrap_or(Signal::SIGTERM);
let has_running_processes = self.has_running_process();
match (self.has_running_process(), self.args.on_busy_update) {
log::debug!(
"ON UPDATE: has_running_processes: {} --- on_busy_update: {:?}",
has_running_processes,
self.args.on_busy_update
);
match (has_running_processes, self.args.on_busy_update) {
// If nothing is running, start the command
(false, _) => {
self.spawn(ops)?;