So instead of disabling notify while requests are active we might want
to only disable it while we are inside virtio_blk_handle_output.
Something like the following minimally tested patch:

I'd suggest that we get even more aggressive and install an idle
bottom half that checks the queue for newly submitted requests. If
we keep getting requests submitted before a new one completes, we'll
never take an I/O exit.

That has the downside of bouncing a cache line on unrelated exits.

The read and write sides of the ring are widely separated in physical
memory specifically to avoid cache line bouncing.

It probably doesn't matter with qemu as it is now, since it will
bounce qemu_mutex, but it will hurt with large guests (especially if
they have many rings).

IMO we should get things to work well without riding on unrelated
exits, especially as we're trying to reduce those exits.

A block I/O request can potentially be very, very long lived. By
serializing requests like this, there's a high likelihood that it's
going to kill performance with anything capable of processing multiple
requests.

OTOH, if we aggressively poll the ring when we have an opportunity to,
there's very little down side to that and it addresses the serialization
problem.

The same approach is probably a good idea for virtio-net.

With vhost-net you don't see exits.

The point is, when we've disabled notification, we should poll on the
ring for additional requests instead of waiting for one to complete
before looking at another one.

Even with vhost-net, this logic is still applicable although potentially
harder to achieve.