blk-mq: don't bring back flags of a completed request

The timeout handler sets and clears RQF_TIMED_OUT in a request it only
holds a reference to, with a plain read-modify-write, while the
completion clears flags in rq_flags the same way. If the two interleave,
blk_mq_finish_request() runs twice with the flags back: the zone write
plug reference is dropped twice and the scheduler's ->finish_request()
is called again.

Change RQF_TIMED_OUT with cmpxchg, so the handler never writes back a
flag that changed under it.

Fixes: e5c0ca1365 ("blk-mq: release scheduler resource when request completes")
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20261028164415.1262013-31-axboe@kernel.dk
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Josef Bacik 2026-10-28 10:41:33 -06:00 committed by Jens Axboe
parent 620cfc7a66
commit 9531230ee1
3 changed files with 17 additions and 2 deletions

View File

@ -1639,7 +1639,7 @@ EXPORT_SYMBOL_GPL(blk_mq_queue_inflight);
static void blk_mq_rq_timed_out(struct request *req)
{
req->rq_flags |= RQF_TIMED_OUT;
blk_rq_change_timed_out(req, true);
if (req->q->mq_ops->timeout) {
enum blk_eh_timer_return ret;

View File

@ -137,7 +137,8 @@ void blk_add_timer(struct request *req)
if (!req->timeout)
req->timeout = q->rq_timeout;
req->rq_flags &= ~RQF_TIMED_OUT;
if (req->rq_flags & RQF_TIMED_OUT)
blk_rq_change_timed_out(req, false);
expiry = jiffies + req->timeout;
WRITE_ONCE(req->deadline, expiry);

View File

@ -332,6 +332,20 @@ static inline void bio_integrity_free(struct bio *bio)
unsigned long blk_rq_timeout(unsigned long timeout);
void blk_add_timer(struct request *req);
/*
* The timeout handler changes RQF_TIMED_OUT in a request which it does not
* own: whoever completes it changes rq_flags at the same time, with plain
* loads and stores. Never write back a flag that has changed under us.
*/
static inline void blk_rq_change_timed_out(struct request *rq, bool set)
{
req_flags_t old = READ_ONCE(rq->rq_flags), new;
do {
new = set ? old | RQF_TIMED_OUT : old & ~RQF_TIMED_OUT;
} while (!try_cmpxchg(&rq->rq_flags, &old, new));
}
enum bio_merge_status {
BIO_MERGE_OK,
BIO_MERGE_NONE,