process: Unix: preregister for signal notifications before polling child

This commit is contained in:
Ivan Petkov 2018-05-20 20:37:21 -07:00
parent 827e77e71e
commit 9290602815
No known key found for this signature in database
GPG Key ID: 0B431E9837056942

View File

@ -98,10 +98,10 @@ impl Child {
pub fn poll_exit(&mut self) -> Poll<ExitStatus, io::Error> { pub fn poll_exit(&mut self) -> Poll<ExitStatus, io::Error> {
loop { loop {
// Ensure that once we've successfully waited we won't try to // Ensure we don't register for additional notifications
// `kill` above. // if the child has already finished.
if let Some(e) = try!(self.try_wait(false)) { if self.reaped {
return Ok(e.into()) return Ok(Async::NotReady);
} }
// If the child hasn't exited yet, then it's our responsibility to // If the child hasn't exited yet, then it's our responsibility to
@ -110,8 +110,34 @@ impl Child {
// //
// As described in `spawn` above, we just indicate that we can // As described in `spawn` above, we just indicate that we can
// next make progress once a SIGCHLD is received. // next make progress once a SIGCHLD is received.
if try!(self.sigchld.poll()).is_not_ready() { //
return Ok(Async::NotReady) // However, we will register for a notification on the next signal
// BEFORE we poll the child. Otherwise it is possible that the child
// can exit and the signal can arrive after we last polled the child,
// but before we've registered for a notification on the next signal
// (this can cause a deadlock if there are no more spawned children
// which can generate a different signal for us). A side effect of
// pre-registering for signal notifications is that when the child
// exits, we will have already registered for an additional
// notification we don't need to consume. If another signal arrives,
// this future's task will be notified/woken up again. Since the
// futures model allows for spurious wake ups this extra wakeup
// should not cause significant issues with parent futures.
let registered_interest = try!(self.sigchld.poll()).is_not_ready();
if let Some(e) = try!(self.try_wait(false)) {
return Ok(e.into());
}
// If our attempt to poll for the next signal was not ready, then
// we've arranged for our task to get notified and we can bail out.
if registered_interest {
return Ok(Async::NotReady);
} else {
// Otherwise, if the signal stream delivered a signal to us, we
// won't get notified at the next signal, so we'll loop and try
// again.
continue;
} }
} }
} }