Skip to content

Commit

Permalink
rt: cancel all in-flight ops on driver drop
Browse files Browse the repository at this point in the history
This fixes an immediate issue where ops like Accept can cause runtime shutdown to hang.

Unfortunately, it also stalls some work related to multi-shot ops.
  • Loading branch information
Noah-Kennedy committed Nov 2, 2022
1 parent b0e973a commit 558a1c3
Showing 1 changed file with 54 additions and 8 deletions.
62 changes: 54 additions & 8 deletions src/driver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ mod write;

mod writev;

use crate::driver::op::Lifecycle;
use io_uring::opcode::AsyncCancel;
use io_uring::IoUring;
use slab::Slab;
use std::io;
Expand Down Expand Up @@ -75,10 +77,6 @@ impl Driver {
self.uring.submit_and_wait(1)
}

fn num_operations(&self) -> usize {
self.ops.lifecycle.len()
}

pub(crate) fn tick(&mut self) {
let mut cq = self.uring.completion();
cq.sync();
Expand Down Expand Up @@ -107,9 +105,10 @@ impl Driver {
Err(ref e) if e.raw_os_error() == Some(libc::EBUSY) => {
self.tick();
}
Err(e) => {
Err(e) if e.raw_os_error() != Some(libc::EINTR) => {
return Err(e);
}
_ => continue,
}
}
}
Expand All @@ -121,9 +120,54 @@ impl AsRawFd for Driver {
}
}

/// Drop the driver, cancelling any in-progress ops and waiting for them to terminate.
///
/// This first cancels all ops and then waits for them to be moved to the completed lifecycle phase.
///
/// It is possible for this to be run without previously dropping the runtime, but this should only
/// be possible in the case of [`std::process::exit`].
///
/// This depends on us knowing when ops are completed and done firing.
/// When multishot ops are added (support exists but none are implemented), a way to know if such
/// an op is finished MUST be added, otherwise our shutdown process is unsound.
impl Drop for Driver {
fn drop(&mut self) {
while self.num_operations() > 0 {
// get all ops in flight for cancellation
while !self.uring.submission().is_empty() {
self.submit().expect("Internal error when dropping driver");
}

// pre-determine what to cancel
let mut cancellable_ops = Vec::new();
for (id, cycle) in self.ops.lifecycle.iter() {
// don't cancel completed items
if !matches!(cycle, Lifecycle::Completed(_)) {
cancellable_ops.push(id);
}
}

// cancel all ops
for id in cancellable_ops {
unsafe {
while self
.uring
.submission()
.push(&AsyncCancel::new(id as u64).build().user_data(u64::MAX))
.is_err()
{
self.submit().expect("Internal error when dropping driver");
}
}
}

// TODO: add a way to know if a multishot op is done sending completions
// SAFETY: this is currently unsound for multishot ops
while !self
.ops
.lifecycle
.iter()
.all(|(_, cycle)| matches!(cycle, Lifecycle::Completed(_)))
{
// If waiting fails, ignore the error. The wait will be attempted
// again on the next loop.
let _ = self.wait();
Expand Down Expand Up @@ -167,7 +211,9 @@ impl Ops {

impl Drop for Ops {
fn drop(&mut self) {
assert!(self.lifecycle.is_empty());
assert!(self.completions.is_empty());
assert!(self
.lifecycle
.iter()
.all(|(_, cycle)| matches!(cycle, Lifecycle::Completed(_))))
}
}

0 comments on commit 558a1c3

Please sign in to comment.