Skip to content

Commit f99d237

Browse files
authored
nvme: deadlock enabling doorbell buffer while doing I/O (#1080)
9a3a93a introduced a bug in the previously-okay SubQueue::pop; before, acc_mem.access() would go through MemAccessor, acquire a reference on the underlying MemCtx (an Arc refcount bump), drop the lock, and continue. immediately after, SubQueue::pop would lock the SubQueue's state and actually perform the pop. this ordering is backwards from set_db_buf, which locks the SubQueue's state *then* conditionally performs acc_mem.access() if the buffer is set up as part of an import. after 9a3a93a, SubQueue::pop uses access_borrowed() to avoid contention on the refcount for MemCtx, the cost of retaining the lock on the SubQueue's MemAccessor node while taking the lock on the same queue's SubQueueState. at this point a concurrent SubQueue::pop and SubQueue::set_db_buf could deadlock; one holds the queue state lock, the other holds the accessor node lock, and both will try to take the other. resolve this tension by picking a consistent ordering for the queue state and acc_mem locks: SubQueueState first, then acc_mem.{access,_borrow}(). this was already the ordering in CompQueue::push and both set_db_buf, so pop() gets the trivial change.
1 parent a54b7de commit f99d237

1 file changed

Lines changed: 11 additions & 2 deletions

File tree

lib/propolis/src/hw/nvme/queue.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,13 @@ struct QueueState<QS> {
113113
/// a `SubQueueState` for a Submission Queue.
114114
inner: Mutex<QueueInner<QS>>,
115115

116-
pub acc_mem: MemAccessor,
116+
/// This queue's memory accessor node.
117+
///
118+
/// Be careful about lock ordering when using this accessor; access_borrow()
119+
/// holds this node's lock. If a user of this queue state requires both
120+
/// `access_borrow()` and `QueueInner`, the protocol is to lock queue
121+
/// state first and this accessor second.
122+
acc_mem: MemAccessor,
117123
}
118124
impl<QS> QueueState<QS> {
119125
fn new(size: u32, acc_mem: MemAccessor, inner: QS) -> Self {
@@ -649,12 +655,15 @@ impl SubQueue {
649655
pub fn pop(
650656
self: &Arc<SubQueue>,
651657
) -> Option<(GuestData<SubmissionQueueEntry>, Permit, u16)> {
658+
// Lock the SubQueueState early to conform to lock ordering requirement;
659+
// see docs on QueueState::acc_mem.
660+
let mut state = self.state.lock();
661+
652662
let Some(mem) = self.state.acc_mem.access_borrow() else { return None };
653663
let mem = mem.view();
654664

655665
// Attempt to reserve an entry on the Completion Queue
656666
let permit = self.cq.reserve_entry(&self, &mem)?;
657-
let mut state = self.state.lock();
658667

659668
// Check for last-minute updates to the tail via any configured doorbell
660669
// page, prior to attempting the pop itself.

0 commit comments

Comments
 (0)