[PATCH v2 0/2] cancel all throttled bios in del_gendisk()

STALE1720d

Revision v2 of 7 in this series.

7 messages, 3 authors, 2021-12-01 · open the first message on its own page

[PATCH v2 0/2] cancel all throttled bios in del_gendisk()

From: Yu Kuai <hidden>
Date: 2021-11-30 01:05:26

If del_gendisk() is done when some io are still throttled, such io
will not be handled until the throttle is done, which is not
necessary.

Changes in v2:
 - move WARN_ON_ONCE() from throtl_rb_first() to it's caller
 - merge some patches into one.

Yu Kuai (2):
  blk-throtl: move WARN_ON_ONCE() from throtl_rb_first() to it's caller
  block: cancel all throttled bios in del_gendisk()

 block/blk-throttle.c | 68 ++++++++++++++++++++++++++++++++++++++++++--
 block/blk-throttle.h |  2 ++
 block/genhd.c        |  2 ++
 3 files changed, 69 insertions(+), 3 deletions(-)

-- 
2.31.1

[PATCH v2 1/2] blk-throtl: move WARN_ON_ONCE() from throtl_rb_first() to it's caller

From: Yu Kuai <hidden>
Date: 2021-11-30 01:05:28

Prepare to reintroduce tg_drain_bios(), which will iterate until
throtl_rb_first() return NULL.

Signed-off-by: Yu Kuai <redacted>
---
 block/blk-throttle.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/block/blk-throttle.c b/block/blk-throttle.c
index 39bb6e68a9a2..f7244190cb2f 100644
--- a/block/blk-throttle.c
+++ b/block/blk-throttle.c
@@ -502,7 +502,6 @@ throtl_rb_first(struct throtl_service_queue *parent_sq)
 	struct rb_node *n;
 
 	n = rb_first_cached(&parent_sq->pending_tree);
-	WARN_ON_ONCE(!n);
 	if (!n)
 		return NULL;
 	return rb_entry_tg(n);
@@ -521,8 +520,10 @@ static void update_min_dispatch_time(struct throtl_service_queue *parent_sq)
 	struct throtl_grp *tg;
 
 	tg = throtl_rb_first(parent_sq);
-	if (!tg)
+	if (!tg) {
+		WARN_ON_ONCE(1);
 		return;
+	}
 
 	parent_sq->first_pending_disptime = tg->disptime;
 }
@@ -1090,8 +1091,10 @@ static int throtl_select_dispatch(struct throtl_service_queue *parent_sq)
 			break;
 
 		tg = throtl_rb_first(parent_sq);
-		if (!tg)
+		if (!tg) {
+			WARN_ON_ONCE(1);
 			break;
+		}
 
 		if (time_before(jiffies, tg->disptime))
 			break;
-- 
2.31.1

[PATCH v2 2/2] block: cancel all throttled bios in del_gendisk()

From: Yu Kuai <hidden>
Date: 2021-11-30 01:05:30

Throttled bios can't be issued after del_gendisk() is done, thus
it's better to cancel them immediately rather than waiting for
throttle is done.

For example, if user thread is throttled with low bps while it's
issuing large io, and the device is deleted. The user thread will
wait for a long time for io to return.

Noted this patch is mainly from revertion of commit 32e3374304c7
("blk-throttle: remove tg_drain_bios") and commit b77412372b68
("blk-throttle: remove blk_throtl_drain").

Signed-off-by: Yu Kuai <redacted>
---
 block/blk-throttle.c | 59 ++++++++++++++++++++++++++++++++++++++++++++
 block/blk-throttle.h |  2 ++
 block/genhd.c        |  2 ++
 3 files changed, 63 insertions(+)
diff --git a/block/blk-throttle.c b/block/blk-throttle.c
index f7244190cb2f..64fd4d61cbfe 100644
--- a/block/blk-throttle.c
+++ b/block/blk-throttle.c
@@ -2260,6 +2260,65 @@ void blk_throtl_bio_endio(struct bio *bio)
 }
 #endif
 
+/*
+ * Dispatch all bios from all children tg's queued on @parent_sq.  On
+ * return, @parent_sq is guaranteed to not have any active children tg's
+ * and all bios from previously active tg's are on @parent_sq->bio_lists[].
+ */
+static void tg_drain_bios(struct throtl_service_queue *parent_sq)
+{
+	struct throtl_grp *tg;
+
+	while ((tg = throtl_rb_first(parent_sq))) {
+		struct throtl_service_queue *sq = &tg->service_queue;
+		struct bio *bio;
+
+		throtl_dequeue_tg(tg);
+
+		while ((bio = throtl_peek_queued(&sq->queued[READ])))
+			tg_dispatch_one_bio(tg, bio_data_dir(bio));
+		while ((bio = throtl_peek_queued(&sq->queued[WRITE])))
+			tg_dispatch_one_bio(tg, bio_data_dir(bio));
+	}
+}
+
+/**
+ * blk_throtl_cancel_bios - cancel throttled bios
+ * @q: request_queue to cancel throttled bios for
+ *
+ * This function is called to error all currently throttled bios on @q.
+ */
+void blk_throtl_cancel_bios(struct request_queue *q)
+{
+	struct throtl_data *td = q->td;
+	struct blkcg_gq *blkg;
+	struct cgroup_subsys_state *pos_css;
+	struct bio *bio;
+	int rw;
+
+	rcu_read_lock();
+
+	/*
+	 * Drain each tg while doing post-order walk on the blkg tree, so
+	 * that all bios are propagated to td->service_queue.  It'd be
+	 * better to walk service_queue tree directly but blkg walk is
+	 * easier.
+	 */
+	blkg_for_each_descendant_post(blkg, pos_css, td->queue->root_blkg)
+		tg_drain_bios(&blkg_to_tg(blkg)->service_queue);
+
+	/* finally, transfer bios from top-level tg's into the td */
+	tg_drain_bios(&td->service_queue);
+
+	rcu_read_unlock();
+
+	/* all bios now should be in td->service_queue, cancel them */
+	for (rw = READ; rw <= WRITE; rw++)
+		while ((bio = throtl_pop_queued(&td->service_queue.queued[rw],
+						NULL)))
+			bio_io_error(bio);
+}
+
 int blk_throtl_init(struct request_queue *q)
 {
 	struct throtl_data *td;
diff --git a/block/blk-throttle.h b/block/blk-throttle.h
index 175f03abd9e4..9d67d5139954 100644
--- a/block/blk-throttle.h
+++ b/block/blk-throttle.h
@@ -160,12 +160,14 @@ static inline void blk_throtl_exit(struct request_queue *q) { }
 static inline void blk_throtl_register_queue(struct request_queue *q) { }
 static inline void blk_throtl_charge_bio_split(struct bio *bio) { }
 static inline bool blk_throtl_bio(struct bio *bio) { return false; }
+#define blk_throtl_cancel_bios(q)  do { } while (0)
 #else /* CONFIG_BLK_DEV_THROTTLING */
 int blk_throtl_init(struct request_queue *q);
 void blk_throtl_exit(struct request_queue *q);
 void blk_throtl_register_queue(struct request_queue *q);
 void blk_throtl_charge_bio_split(struct bio *bio);
 bool __blk_throtl_bio(struct bio *bio);
+void blk_throtl_cancel_bios(struct request_queue *q);
 static inline bool blk_throtl_bio(struct bio *bio)
 {
 	struct throtl_grp *tg = blkg_to_tg(bio->bi_blkg);
diff --git a/block/genhd.c b/block/genhd.c
index 8e9cbf23c510..24fa3356d164 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -28,6 +28,7 @@
 
 #include "blk.h"
 #include "blk-rq-qos.h"
+#include "blk-throttle.h"
 
 static struct kobject *block_depr;
 
@@ -619,6 +620,7 @@ void del_gendisk(struct gendisk *disk)
 
 	blk_mq_freeze_queue_wait(q);
 
+	blk_throtl_cancel_bios(q);
 	rq_qos_exit(q);
 	blk_sync_queue(q);
 	blk_flush_integrity();
-- 
2.31.1

Re: [PATCH v2 1/2] blk-throtl: move WARN_ON_ONCE() from throtl_rb_first() to it's caller

From: Christoph Hellwig <hch@infradead.org>
Date: 2021-11-30 06:43:46

On Tue, Nov 30, 2021 at 09:17:29AM +0800, Yu Kuai wrote:
quoted hunk
Prepare to reintroduce tg_drain_bios(), which will iterate until
throtl_rb_first() return NULL.

Signed-off-by: Yu Kuai <redacted>
---
 block/blk-throttle.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/block/blk-throttle.c b/block/blk-throttle.c
index 39bb6e68a9a2..f7244190cb2f 100644
--- a/block/blk-throttle.c
+++ b/block/blk-throttle.c
@@ -502,7 +502,6 @@ throtl_rb_first(struct throtl_service_queue *parent_sq)
 	struct rb_node *n;
 
 	n = rb_first_cached(&parent_sq->pending_tree);
-	WARN_ON_ONCE(!n);
 	if (!n)
 		return NULL;
 	return rb_entry_tg(n);
@@ -521,8 +520,10 @@ static void update_min_dispatch_time(struct throtl_service_queue *parent_sq)
 	struct throtl_grp *tg;
 
 	tg = throtl_rb_first(parent_sq);
-	if (!tg)
+	if (!tg) {
+		WARN_ON_ONCE(1);
 		return;
	if (WARN_ON_ONCE(!tg))
		return;
 		tg = throtl_rb_first(parent_sq);
-		if (!tg)
+		if (!tg) {
+			WARN_ON_ONCE(1);
 			break;
+		}
Same here.

Re: [PATCH v2 2/2] block: cancel all throttled bios in del_gendisk()

From: Christoph Hellwig <hch@infradead.org>
Date: 2021-11-30 06:44:03

This look fine to me, but I think someone who actually knows the
throttle code also needs to look over it.

Re: [PATCH v2 2/2] block: cancel all throttled bios in del_gendisk()

From: Tejun Heo <tj@kernel.org>
Date: 2021-11-30 16:26:22

Hello,

On Tue, Nov 30, 2021 at 09:17:30AM +0800, Yu Kuai wrote:
+void blk_throtl_cancel_bios(struct request_queue *q)
+{
+	struct throtl_data *td = q->td;
+	struct blkcg_gq *blkg;
+	struct cgroup_subsys_state *pos_css;
+	struct bio *bio;
+	int rw;
+
+	rcu_read_lock();
So, all of the draining is being performed without holding the q lock, which
*might* be okay given that we're in the del_gendisk path but is likely risky
- ie. there can still be timers or whatever racing against it.

Thanks.

-- 
tejun

Re: [PATCH v2 2/2] block: cancel all throttled bios in del_gendisk()

From: yukuai (C) <hidden>
Date: 2021-12-01 09:26:27

ÔÚ 2021/12/01 0:26, Tejun Heo дµÀ:
Hello,

On Tue, Nov 30, 2021 at 09:17:30AM +0800, Yu Kuai wrote:
quoted
+void blk_throtl_cancel_bios(struct request_queue *q)
+{
+	struct throtl_data *td = q->td;
+	struct blkcg_gq *blkg;
+	struct cgroup_subsys_state *pos_css;
+	struct bio *bio;
+	int rw;
+
+	rcu_read_lock();
So, all of the draining is being performed without holding the q lock, which
*might* be okay given that we're in the del_gendisk path but is likely risky
- ie. there can still be timers or whatever racing against it.
I'll hold queue_lock to draining bios in next iteration,

Thanks,
Kuai
Thanks.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help