Skip to content

Commit f13e813

Browse files
committed
fix(block): only notify guest once
Currently block device has a guest notification logic inside it's request processing loop. This can create a situation when guest can continuously add more requests to the queue, making the whole request processing loop arbitrary long. This is an issue, since it block any other IO from being processed. The solution is to simply notify guest one time, after all current requests are processed. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent a4f38e7 commit f13e813

File tree

1 file changed

+47
-43
lines changed
  • src/vmm/src/devices/virtio/block/virtio

1 file changed

+47
-43
lines changed

src/vmm/src/devices/virtio/block/virtio/device.rs

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -384,24 +384,6 @@ impl VirtioBlock {
384384
}
385385
}
386386

387-
fn add_used_descriptor(
388-
queue: &mut Queue,
389-
index: u16,
390-
len: u32,
391-
irq_trigger: &IrqTrigger,
392-
block_metrics: &BlockDeviceMetrics,
393-
) {
394-
queue.add_used(index, len).unwrap_or_else(|err| {
395-
error!("Failed to add available descriptor head {}: {}", index, err)
396-
});
397-
398-
if queue.prepare_kick() {
399-
irq_trigger.trigger_irq(IrqType::Vring).unwrap_or_else(|_| {
400-
block_metrics.event_fails.inc();
401-
});
402-
}
403-
}
404-
405387
/// Device specific function for peaking inside a queue and processing descriptors.
406388
pub fn process_queue(&mut self, queue_index: usize) -> Result<(), InvalidAvailIdx> {
407389
// This is safe since we checked in the event handler that the device is activated.
@@ -443,17 +425,26 @@ impl VirtioBlock {
443425
break;
444426
}
445427
ProcessingResult::Executed(finished) => {
446-
Self::add_used_descriptor(
447-
queue,
448-
head.index,
449-
finished.num_bytes_to_mem,
450-
&self.irq_trigger,
451-
&self.metrics,
452-
);
428+
queue
429+
.add_used(head.index, finished.num_bytes_to_mem)
430+
.unwrap_or_else(|err| {
431+
error!(
432+
"Failed to add available descriptor head {}: {}",
433+
head.index, err
434+
)
435+
});
453436
}
454437
}
455438
}
456439

440+
if queue.prepare_kick() {
441+
self.irq_trigger
442+
.trigger_irq(IrqType::Vring)
443+
.unwrap_or_else(|_| {
444+
self.metrics.event_fails.inc();
445+
});
446+
}
447+
457448
if let FileEngine::Async(ref mut engine) = self.disk.file_engine {
458449
if let Err(err) = engine.kick_submission_queue() {
459450
error!("BlockError submitting pending block requests: {:?}", err);
@@ -495,17 +486,25 @@ impl VirtioBlock {
495486
),
496487
};
497488
let finished = pending.finish(mem, res, &self.metrics);
498-
499-
Self::add_used_descriptor(
500-
queue,
501-
finished.desc_idx,
502-
finished.num_bytes_to_mem,
503-
&self.irq_trigger,
504-
&self.metrics,
505-
);
489+
queue
490+
.add_used(finished.desc_idx, finished.num_bytes_to_mem)
491+
.unwrap_or_else(|err| {
492+
error!(
493+
"Failed to add available descriptor head {}: {}",
494+
finished.desc_idx, err
495+
)
496+
});
506497
}
507498
}
508499
}
500+
501+
if queue.prepare_kick() {
502+
self.irq_trigger
503+
.trigger_irq(IrqType::Vring)
504+
.unwrap_or_else(|_| {
505+
self.metrics.event_fails.inc();
506+
});
507+
}
509508
}
510509

511510
pub fn process_async_completion_event(&mut self) {
@@ -1572,14 +1571,14 @@ mod tests {
15721571

15731572
// Run scenario that doesn't trigger FullSq BlockError: Add sq_size flush requests.
15741573
add_flush_requests_batch(&mut block, &vq, IO_URING_NUM_ENTRIES);
1575-
simulate_queue_event(&mut block, Some(false));
1574+
simulate_queue_event(&mut block, Some(true));
15761575
assert!(!block.is_io_engine_throttled);
15771576
simulate_async_completion_event(&mut block, true);
15781577
check_flush_requests_batch(IO_URING_NUM_ENTRIES, &vq);
15791578

15801579
// Run scenario that triggers FullSqError : Add sq_size + 10 flush requests.
15811580
add_flush_requests_batch(&mut block, &vq, IO_URING_NUM_ENTRIES + 10);
1582-
simulate_queue_event(&mut block, Some(false));
1581+
simulate_queue_event(&mut block, Some(true));
15831582
assert!(block.is_io_engine_throttled);
15841583
// When the async_completion_event is triggered:
15851584
// 1. sq_size requests should be processed processed.
@@ -1606,16 +1605,16 @@ mod tests {
16061605
// Run scenario that triggers FullCqError. Push 2 * IO_URING_NUM_ENTRIES and wait for
16071606
// completion. Then try to push another entry.
16081607
add_flush_requests_batch(&mut block, &vq, IO_URING_NUM_ENTRIES);
1609-
simulate_queue_event(&mut block, Some(false));
1608+
simulate_queue_event(&mut block, Some(true));
16101609
assert!(!block.is_io_engine_throttled);
16111610
thread::sleep(Duration::from_millis(150));
16121611
add_flush_requests_batch(&mut block, &vq, IO_URING_NUM_ENTRIES);
1613-
simulate_queue_event(&mut block, Some(false));
1612+
simulate_queue_event(&mut block, Some(true));
16141613
assert!(!block.is_io_engine_throttled);
16151614
thread::sleep(Duration::from_millis(150));
16161615

16171616
add_flush_requests_batch(&mut block, &vq, 1);
1618-
simulate_queue_event(&mut block, Some(false));
1617+
simulate_queue_event(&mut block, Some(true));
16191618
assert!(block.is_io_engine_throttled);
16201619
simulate_async_completion_event(&mut block, true);
16211620
assert!(!block.is_io_engine_throttled);
@@ -1671,13 +1670,15 @@ mod tests {
16711670
vq.dtable[1].len.set(512);
16721671
mem.write_obj::<u64>(123_456_789, data_addr).unwrap();
16731672

1674-
// Following write procedure should fail because of bandwidth rate limiting.
1673+
// This will fail because of bandwidth rate limiting.
1674+
// The irq is still triggered because notification suppression
1675+
// is not enabled.
16751676
{
16761677
// Trigger the attempt to write.
16771678
check_metric_after_block!(
16781679
&block.metrics.rate_limiter_throttled_events,
16791680
1,
1680-
simulate_queue_event(&mut block, Some(false))
1681+
simulate_queue_event(&mut block, Some(true))
16811682
);
16821683

16831684
// Assert that limiter is blocked.
@@ -1739,13 +1740,15 @@ mod tests {
17391740
vq.dtable[1].len.set(512);
17401741
mem.write_obj::<u64>(123_456_789, data_addr).unwrap();
17411742

1742-
// Following write procedure should fail because of ops rate limiting.
1743+
// This will fail because of ops rate limiting.
1744+
// The irq is still triggered because notification suppression
1745+
// is not enabled.
17431746
{
17441747
// Trigger the attempt to write.
17451748
check_metric_after_block!(
17461749
&block.metrics.rate_limiter_throttled_events,
17471750
1,
1748-
simulate_queue_event(&mut block, Some(false))
1751+
simulate_queue_event(&mut block, Some(true))
17491752
);
17501753

17511754
// Assert that limiter is blocked.
@@ -1754,7 +1757,8 @@ mod tests {
17541757
assert_eq!(vq.used.idx.get(), 0);
17551758
}
17561759

1757-
// Do a second write that still fails but this time on the fast path.
1760+
// Do a second write that still fails but this time on the fast path
1761+
// which does not call `process_queue`, so no irq notifications.
17581762
{
17591763
// Trigger the attempt to write.
17601764
check_metric_after_block!(

0 commit comments

Comments
 (0)