blk-mq: quiesce a queue that joins a quiesced tag set

blk_mq_quiesce_tagset() only quiesces the queues on the list at that
moment. A queue added before blk_mq_unquiesce_tagset() runs unquiesced,
and then gets unquiesced without ever having been quiesced, which trips
the warning in blk_mq_unquiesce_queue(). NVMe gets there when a
controller fails while its namespaces are being scanned.

Count the quiesce calls in the tag set and quiesce a joining queue that
many times, under a spinlock that also serializes the two functions. It
can't be tag_list_lock, see commit 59e25ef2b4.

Fixes: 414dd48e88 ("blk-mq: add tagset quiesce interface")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20261028164415.1262013-37-axboe@kernel.dk
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Josef Bacik 2026-10-28 10:41:39 -06:00 committed by Jens Axboe
parent 035236c9e1
commit 71ec3c7d6d
2 changed files with 28 additions and 0 deletions

View File

@ -336,12 +336,15 @@ void blk_mq_quiesce_tagset(struct blk_mq_tag_set *set)
{
struct request_queue *q;
spin_lock(&set->quiesce_lock);
set->quiesce_depth++;
rcu_read_lock();
list_for_each_entry_rcu(q, &set->tag_list, tag_set_list) {
if (!blk_queue_skip_tagset_quiesce(q))
blk_mq_quiesce_queue_nowait(q);
}
rcu_read_unlock();
spin_unlock(&set->quiesce_lock);
blk_mq_wait_quiesce_done(set);
}
@ -351,12 +354,16 @@ void blk_mq_unquiesce_tagset(struct blk_mq_tag_set *set)
{
struct request_queue *q;
spin_lock(&set->quiesce_lock);
if (set->quiesce_depth)
set->quiesce_depth--;
rcu_read_lock();
list_for_each_entry_rcu(q, &set->tag_list, tag_set_list) {
if (!blk_queue_skip_tagset_quiesce(q))
blk_mq_unquiesce_queue(q);
}
rcu_read_unlock();
spin_unlock(&set->quiesce_lock);
}
EXPORT_SYMBOL_GPL(blk_mq_unquiesce_tagset);
@ -4345,6 +4352,8 @@ static void blk_mq_del_queue_tag_set(struct request_queue *q)
static void blk_mq_add_queue_tag_set(struct blk_mq_tag_set *set,
struct request_queue *q)
{
unsigned int i;
mutex_lock(&set->tag_list_lock);
/*
@ -4358,7 +4367,18 @@ static void blk_mq_add_queue_tag_set(struct blk_mq_tag_set *set,
}
if (set->flags & BLK_MQ_F_TAG_QUEUE_SHARED)
queue_set_hctx_shared(q, true);
/*
* A queue that joins a quiesced tag set is quiesced like the others,
* and blk_mq_unquiesce_tagset() finds what it is going to undo.
*/
spin_lock(&set->quiesce_lock);
list_add_tail_rcu(&q->tag_set_list, &set->tag_list);
if (!blk_queue_skip_tagset_quiesce(q)) {
for (i = 0; i < set->quiesce_depth; i++)
blk_mq_quiesce_queue_nowait(q);
}
spin_unlock(&set->quiesce_lock);
mutex_unlock(&set->tag_list_lock);
}
@ -4927,6 +4947,8 @@ int blk_mq_alloc_tag_set(struct blk_mq_tag_set *set)
mutex_init(&set->tag_list_lock);
INIT_LIST_HEAD(&set->tag_list);
spin_lock_init(&set->quiesce_lock);
set->quiesce_depth = 0;
return 0;

View File

@ -523,6 +523,10 @@ enum hctx_type {
* @tag_list_lock: Serializes tag_list accesses.
* @tag_list: List of the request queues that use this tag set. See also
* request_queue.tag_set_list.
* @quiesce_lock: Serializes blk_mq_[un]quiesce_tagset() against each other
* and against a request queue being added to @tag_list.
* @quiesce_depth: How often blk_mq_quiesce_tagset() has been called without
* blk_mq_unquiesce_tagset().
* @srcu: Use as lock when type of the request queue is blocking
* (BLK_MQ_F_BLOCKING).
* @update_nr_hwq_lock:
@ -548,6 +552,8 @@ struct blk_mq_tag_set {
struct mutex tag_list_lock;
struct list_head tag_list;
spinlock_t quiesce_lock;
unsigned int quiesce_depth;
struct srcu_struct *srcu;
struct rw_semaphore update_nr_hwq_lock;