blk-cgroup: don't free a blkg with its punt work queued

blkg_async_bio_workfn() relies on the bios on the list pinning the blkg.
But blkcg_punt_bio_submit() can call queue_work() after a running
instance already took and completed the bio, and queue_work() succeeds
on an empty list for the same reason. Nothing holds the blkg then, and
free_work shares its memory with async_bio_work on top of that.

Hold a reference across blkcg_punt_bio_submit(), give the two works
memory of their own, and cancel async_bio_work in blkg_free_workfn().

Fixes: d3f77dfdc7 ("blkcg: implement REQ_CGROUP_PUNT")
Cc: Tejun Heo <tj@kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Acked-by: Tejun Heo <tj@kernel.org>
Link: https://patch.msgid.link/20261028164415.1262013-20-axboe@kernel.dk
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Josef Bacik 2026-10-28 10:41:22 -06:00 committed by Jens Axboe
parent e335b853d9
commit 4def096314
2 changed files with 14 additions and 5 deletions

View File

@ -121,6 +121,11 @@ static void blkg_free_workfn(struct work_struct *work)
struct request_queue *q = blkg->q;
int i;
#ifdef CONFIG_BLK_CGROUP_PUNT_BIO
/* It may have been queued for bios which an earlier run has taken */
cancel_work_sync(&blkg->async_bio_work);
#endif
/*
* pd_free_fn() can also be called from blkcg_deactivate_policy(),
* in order to make sure pd_free_fn() is called in order, the deletion
@ -218,7 +223,7 @@ static void blkg_async_bio_workfn(struct work_struct *work)
struct blk_plug plug;
bool need_plug = false;
/* as long as there are pending bios, @blkg can't go away */
/* blkg_free_workfn() waits for us */
spin_lock(&blkg->async_bio_lock);
bio_list_merge_init(&bios, &blkg->async_bios);
spin_unlock(&blkg->async_bio_lock);
@ -245,10 +250,16 @@ void blkcg_punt_bio_submit(struct bio *bio)
struct blkcg_gq *blkg = bio->bi_blkg;
if (blkg->parent) {
/*
* Once it is on the list, a run which is under way can submit
* @bio, and its reference goes when it completes.
*/
blkg_get(blkg);
spin_lock(&blkg->async_bio_lock);
bio_list_add(&blkg->async_bios, bio);
spin_unlock(&blkg->async_bio_lock);
queue_work(blkcg_punt_bio_wq, &blkg->async_bio_work);
blkg_put(blkg);
} else {
/* never bounce for the root cgroup */
submit_bio(bio);

View File

@ -76,11 +76,9 @@ struct blkcg_gq {
#ifdef CONFIG_BLK_CGROUP_PUNT_BIO
spinlock_t async_bio_lock;
struct bio_list async_bios;
struct work_struct async_bio_work;
#endif
union {
struct work_struct async_bio_work;
struct work_struct free_work;
};
struct work_struct free_work;
atomic_t use_delay;
atomic64_t delay_nsec;