Hi,
A few fixes related to the batched allocations:
- Have the requests hold a queue reference, flush them on schedule
unplug as well.
- Make sure the queue matches, could be a mismatch if we're driving
multiple devices.
--
Jens Axboe
Requests that were stored in the cache deliberately didn't hold an enter
reference to the queue, instead we grabbed one every time we pulled a
request out of there. That made for awkward logic on freeing the remainder
of the cached list, if needed, where we had to artificially raise the
queue usage count before each free.
Grab references up front for cached plug requests. That's safer, and also
more efficient.
Fixes: 47c122e35d7e ("block: pre-allocate requests if plug is started and is a batch")
Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
block/blk-core.c | 2 +-
block/blk-mq.c | 7 ++++---
2 files changed, 5 insertions(+), 4 deletions(-)
Retain the old logic for the fops based submit, but for our internal
blk_mq_submit_bio(), move the queue entering logic into the core
function itself.
We need to be a bit careful if going into the scheduler, as a scheduler
or queue mappings can arbitrarily change before we have entered the queue.
Have the bio scheduler mapping do that separately, it's a very cheap
operation compared to actually doing merging locking and lookups.
Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
block/blk-core.c | 14 ++++++--------
block/blk-mq-sched.c | 13 ++++++++++---
block/blk-mq.c | 28 ++++++++++++++++++----------
3 files changed, 34 insertions(+), 21 deletions(-)
We need to improve the logic here a bit, most importantly ensuring that
the request matches the current queue. If it doesn't, we cannot use it
and must fallback to normal request alloc.
Fixes: 47c122e35d7e ("block: pre-allocate requests if plug is started and is a batch")
Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
block/blk-mq.c | 25 ++++++++++++++++++++-----
1 file changed, 20 insertions(+), 5 deletions(-)
From: Christoph Hellwig <hch@infradead.org> Date: 2021-11-04 09:10:12
On Wed, Nov 03, 2021 at 12:32:21PM -0600, Jens Axboe wrote:
Retain the old logic for the fops based submit, but for our internal
blk_mq_submit_bio(), move the queue entering logic into the core
function itself.
Can you explain the why? I guess you want to skip the extra reference
for the cached requests now that they already have one. But please
state that, and explain why it is a fix, as to me it just seems like
another little optimization.
We need to be a bit careful if going into the scheduler, as a scheduler
or queue mappings can arbitrarily change before we have entered the queue.
Have the bio scheduler mapping do that separately, it's a very cheap
operation compared to actually doing merging locking and lookups.
So just don't do the merges for cache requets and side step this
extra bio_queue_enter for that case?
- if (unlikely(bio_queue_enter(bio) != 0))
- return;
-
if (!submit_bio_checks(bio) || !blk_crypto_bio_prep(&bio))
- goto queue_exit;
+ return;
This is broken, we really ant the submit checks under freeze
protection to make sure the parameters can't be changed underneath
us.
From: Christoph Hellwig <hch@infradead.org> Date: 2021-11-04 09:17:24
On Wed, Nov 03, 2021 at 12:32:22PM -0600, Jens Axboe wrote:
+ if (plug && !rq_list_empty(plug->cached_rq)) {
+ rq = rq_list_peek(&plug->cached_rq);
No need for the empty check plus peek. This could be simplified
down to:
if (!plug)
return NULL;
rq = rq_list_peek(&plug->cached_rq);
if (!rq || rq->q != q)
return NULL;
rq_qos_throttle(q, bio);
plug->cached_rq = rq_list_next(rq);
INIT_LIST_HEAD(&rq->queuelist);
return rq;
@@ -1643,7 +1643,7 @@ void blk_flush_plug(struct blk_plug *plug, bool from_schedule) flush_plug_callbacks(plug, from_schedule); if (!rq_list_empty(plug->mq_list)) blk_mq_flush_plug_list(plug, from_schedule);- if (unlikely(!from_schedule && plug->cached_rq))+ if (unlikely(!rq_list_empty(plug->cached_rq)))
How is this related to the rest of the patch?
With references to the requests, flushing them even from a schedule
unplug condition is a lot saner in case someone is waiting on the
queue to quiesce.
--
Jens Axboe
On Wed, Nov 03, 2021 at 12:32:22PM -0600, Jens Axboe wrote:
quoted
+ if (plug && !rq_list_empty(plug->cached_rq)) {
+ rq = rq_list_peek(&plug->cached_rq);
No need for the empty check plus peek. This could be simplified
down to:
if (!plug)
return NULL;
rq = rq_list_peek(&plug->cached_rq);
if (!rq || rq->q != q)
return NULL;
rq_qos_throttle(q, bio);
plug->cached_rq = rq_list_next(rq);
INIT_LIST_HEAD(&rq->queuelist);
return rq;
I tend to prefer having logic flow from the expected conditions. And
since we need to check if the plug is valid anyway, I prefer the current
logic.
--
Jens Axboe
On Wed, Nov 03, 2021 at 12:32:21PM -0600, Jens Axboe wrote:
quoted
Retain the old logic for the fops based submit, but for our internal
blk_mq_submit_bio(), move the queue entering logic into the core
function itself.
Can you explain the why? I guess you want to skip the extra reference
for the cached requests now that they already have one. But please
state that, and explain why it is a fix, as to me it just seems like
another little optimization.
It's just pointless to grab double references, and counter productive
too.
quoted
We need to be a bit careful if going into the scheduler, as a scheduler
or queue mappings can arbitrarily change before we have entered the queue.
Have the bio scheduler mapping do that separately, it's a very cheap
operation compared to actually doing merging locking and lookups.
So just don't do the merges for cache requets and side step this
extra bio_queue_enter for that case?
I'd be fine with that, but it's a bit of a chicken and egg situation as
we don't know. I guess we could move the plugged request check earlier,
and just bypass merging there. Though that makes it a special case
thing, and it's generally useful now. Not sure that would be a good
idea.
quoted
- if (unlikely(bio_queue_enter(bio) != 0))
- return;
-
if (!submit_bio_checks(bio) || !blk_crypto_bio_prep(&bio))
- goto queue_exit;
+ return;
This is broken, we really ant the submit checks under freeze
protection to make sure the parameters can't be changed underneath
us.
Which parameters are you worried about in submit_bio_checks()? I don't
immediately see anything that would make me worry about it.
This looks weird, as blk_try_enter_queue is already called by
bio_queue_enter.
It's just for avoiding a pointless call into bio_queue_enter(), which
isn't needed it blk_try_enter_queue() is successful. The latter is short
and small and can be inlined, while bio_queue_enter() is a lot bigger.
quoted
} else {
struct blk_mq_alloc_data data = {
.q = q,
@@ -2528,6 +2534,11 @@ void blk_mq_submit_bio(struct bio *bio) .cmd_flags = bio->bi_opf, };+ if (unlikely(!blk_mq_queue_enter(q, bio)))+ return;++ rq_qos_throttle(q, bio);+
At some point the code in this !cached branch really needs to move
into a helper..
From: Christoph Hellwig <hch@infradead.org> Date: 2021-11-04 17:30:50
On Thu, Nov 04, 2021 at 05:41:35AM -0600, Jens Axboe wrote:
quoted
quoted
if (!submit_bio_checks(bio) || !blk_crypto_bio_prep(&bio))
- goto queue_exit;
+ return;
This is broken, we really ant the submit checks under freeze
protection to make sure the parameters can't be changed underneath
us.
Which parameters are you worried about in submit_bio_checks()? I don't
immediately see anything that would make me worry about it.
Mostly checks if certain operations are supported or not, as
revalidation could clear those.
quoted
This looks weird, as blk_try_enter_queue is already called by
bio_queue_enter.
It's just for avoiding a pointless call into bio_queue_enter(), which
isn't needed it blk_try_enter_queue() is successful. The latter is short
and small and can be inlined, while bio_queue_enter() is a lot bigger.
If this is so impotant let's operated with an inlined bio_queue_enter
that calls out of line into slow path instead of open coding it
like this.
quoted
quoted
} else {
struct blk_mq_alloc_data data = {
.q = q,
@@ -2528,6 +2534,11 @@ void blk_mq_submit_bio(struct bio *bio) .cmd_flags = bio->bi_opf, };+ if (unlikely(!blk_mq_queue_enter(q, bio)))+ return;++ rq_qos_throttle(q, bio);+
At some point the code in this !cached branch really needs to move
into a helper..
Like in the next patch?
No, I mean the !cached case which is a lot more convoluted.
On Thu, Nov 04, 2021 at 05:41:35AM -0600, Jens Axboe wrote:
quoted
quoted
quoted
if (!submit_bio_checks(bio) || !blk_crypto_bio_prep(&bio))
- goto queue_exit;
+ return;
This is broken, we really ant the submit checks under freeze
protection to make sure the parameters can't be changed underneath
us.
Which parameters are you worried about in submit_bio_checks()? I don't
immediately see anything that would make me worry about it.
Mostly checks if certain operations are supported or not, as
revalidation could clear those.
quoted
quoted
This looks weird, as blk_try_enter_queue is already called by
bio_queue_enter.
It's just for avoiding a pointless call into bio_queue_enter(), which
isn't needed it blk_try_enter_queue() is successful. The latter is short
and small and can be inlined, while bio_queue_enter() is a lot bigger.
If this is so impotant let's operated with an inlined bio_queue_enter
that calls out of line into slow path instead of open coding it
like this.
Sure, I can do that instead.
quoted
quoted
quoted
} else {
struct blk_mq_alloc_data data = {
.q = q,
@@ -2528,6 +2534,11 @@ void blk_mq_submit_bio(struct bio *bio) .cmd_flags = bio->bi_opf, };+ if (unlikely(!blk_mq_queue_enter(q, bio)))+ return;++ rq_qos_throttle(q, bio);+
At some point the code in this !cached branch really needs to move
into a helper..
Like in the next patch?
No, I mean the !cached case which is a lot more convoluted.
Yeah, a helper there might be appropriate. I'll write it up.
--
Jens Axboe
On Thu, Nov 04, 2021 at 05:35:10AM -0600, Jens Axboe wrote:
quoted
I tend to prefer having logic flow from the expected conditions. And
since we need to check if the plug is valid anyway, I prefer the current
logic.
Well, for 99% of the I/O not using a cached request is the expected
condition.
That may very well be the case right now, but as more things plug into
propagating expected number of requests, that's likely to change.
That said, I did reflow it for you in the updated version.
--
Jens Axboe