virtio-devices: block: Reduce notification latency when rate limited

When the rate limit was reached it was possible for the notification to
the guest to be lost since the logic to handle the notification was
tightly coupled with processing the queue. The notification would
eventually be triggered when the rate limit pool was refilled but this
could add significant latency.

Address this by refactoring the code to separate processing queue and
signalling - the processing of the queue is suspended when the rate
limit is reached but the signalling will still be attempted if needed
(i.e. VIRTIO_F_EVENT_IDX is still considered.)

Signed-off-by: wuxinyue <wuxinyue.wxy@antgroup.com>
Signed-off-by: Rob Bradford <rbradford@rivosinc.com>
This commit is contained in:
wuxinyue 2024-08-19 14:30:59 +08:00 committed by Rob Bradford
parent a79a3f6599
commit 6956306604

View File

@ -235,11 +235,7 @@ impl BlockEpollHandler {
Ok(()) Ok(())
} }
fn process_queue_submit_and_signal(&mut self) -> result::Result<(), EpollHelperError> { fn try_signal_used_queue(&mut self) -> result::Result<(), EpollHelperError> {
self.process_queue_submit().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!("Failed to process queue (submit): {:?}", e))
})?;
if self if self
.queue .queue
.needs_notification(self.mem.memory().deref()) .needs_notification(self.mem.memory().deref())
@ -258,6 +254,14 @@ impl BlockEpollHandler {
Ok(()) Ok(())
} }
fn process_queue_submit_and_signal(&mut self) -> result::Result<(), EpollHelperError> {
self.process_queue_submit().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!("Failed to process queue (submit): {:?}", e))
})?;
self.try_signal_used_queue()
}
#[inline] #[inline]
fn find_inflight_request(&mut self, completed_head: u16) -> Result<Request> { fn find_inflight_request(&mut self, completed_head: u16) -> Result<Request> {
// This loop neatly handles the fast path where the completions are // This loop neatly handles the fast path where the completions are
@ -514,8 +518,14 @@ impl EpollHelperHandler for BlockEpollHandler {
// Process the queue only when the rate limit is not reached // Process the queue only when the rate limit is not reached
if !rate_limit_reached { if !rate_limit_reached {
self.process_queue_submit_and_signal()? self.process_queue_submit().map_err(|e| {
EpollHelperError::HandleEvent(anyhow!(
"Failed to process queue (submit): {:?}",
e
))
})?;
} }
self.try_signal_used_queue()?;
} }
RATE_LIMITER_EVENT => { RATE_LIMITER_EVENT => {
if let Some(rate_limiter) = &mut self.rate_limiter { if let Some(rate_limiter) = &mut self.rate_limiter {