[PATCH RFC v4] bcache: flush backing device before cleaning the writeback dirty keys

Zhou Jifeng <[email protected]> Thu, 9 Jul 2026 14:23:30 +0800
Newsgroups org.kernel.vger.linux-bcache
Message-ID <[email protected]>
From: Zhou Jifeng <[email protected]>

Currently, when writeback dirty data completes, write_dirty_finish()
immediately inserts the clean key into the btree.  If a power failure
occurs after the clean-key insert but before the backing device has
flushed its volatile write cache, the btree contains a clean key that
points at backing data that never reached stable media.  On the next
read, bcache serves stale data from the backing device.

Fix this by deferring the clean-key btree insert until after an
explicit backing-device flush (REQ_PREFLUSH).  Written keys are first
parked in a new rbtree (writeback_flush_pending) keyed by START_KEY.
After enough read_dirty() passes, a full btree scan, or a wrap-around
in refill_dirty(), writeback_finish_batch() issues a synchronous
backing-device flush, and only on success inserts the clean keys into
the btree and removes them from the pending tree.  On flush failure
the entries are re-inserted into the current tree for a later retry.

Key changes:

- Introduce struct writeback_pending (separate kmalloc'd allocation,
  decoupled from the dirty_io closure so the closure can be freed
  immediately and the pending entry lives until the flush).

- Add __writeback_pending_insert() — inserts into the START_KEY-
  ordered rbtree with overlap detection.  The rbtree is also used as
  an interval tree by bch_writeback_drop_pending() for front-end
  write overlap queries; that search is correct only because dirty
  btree keys are pairwise disjoint, an invariant enforced at insert
  time.

- writeback_add_to_pending() parks a key after successful writeback
  IO.  Uses GFP_NOIO because the caller runs on the WQ_MEM_RECLAIM
  writeback workqueue.  kmalloc failure silently drops the key (it
  stays dirty and is re-selected by the next scan).

- writeback_finish_batch() detaches the entire pending tree into a
  local list under spinlock, issues writeback_flush(), then either
  cleans the keys (on success) or re-inserts them (on failure).

- writeback_flush() open-codes blkdev_issue_flush() to call
  bch_count_backing_io_errors() on failure.

- bch_writeback_drop_pending() drops overlapping entries from the
  pending tree.  Called from cached_dev_write() alongside the
  existing bch_keybuf_check_overlapping(); overlapping front-end
  writes now force writeback instead of bypassing the cache.

- refill_dirty() now returns enum writeback_scan_result (PARTIAL,
  PARTIAL_WRAP, FULL_SEARCHED) instead of a boolean.  Before
  wrapping around for a second scan pass, it flushes the pending
  tree so the wrap scan won't re-select keys that are still
  KEY_DIRTY in the btree but already written to backing and
  awaiting flush.  writeback_lock may be transiently released
  mid-function during this pre-wrap flush.

- refill_full_stripes() no longer wraps internally; it returns
  at the end of the stripe range and delegates the wrap to
  refill_dirty().  buf->last_scanned is no longer rewound past
  the current position to avoid re-selecting keys already in
  writeback_flush_pending.

- read_dirty() gates deferred flush-and-clean on PASS_PER_FLUSH
  (default 5) passes, a full btree scan, or a PARTIAL_WRAP.  It
  accepts the scan_result from refill_dirty() as a parameter.

- write_dirty_finish() is simplified: it no longer inserts clean
  keys inline; it only calls writeback_add_to_pending().

- BDEV_STATE_CLEAN eligibility in the main loop now also requires
  writeback_pending_empty(), so a device is not marked clean while
  entries are still in the pending tree.

- Thread exit: the workqueue is destroyed first, then a final
  writeback_finish_batch() is attempted (skipped when the cache
  set is disabled or the device is detaching), then any remaining
  pending entries are drained.  The exit path intentionally does
  NOT set BDEV_STATE_CLEAN because it lacks the WB_SCAN_FULL_SEARCHED
  gate that the in-loop check requires; a kthread_stop mid-scan
  (e.g. from bch_cached_dev_attach's error path) must not mark a
  still-dirty device clean.

Signed-off-by: Coly Li <[email protected]>
Signed-off-by: Zhou Jifeng <[email protected]>
---
 drivers/md/bcache/bcache.h    |  11 +
 drivers/md/bcache/request.c   |   6 +-
 drivers/md/bcache/writeback.c | 497 ++++++++++++++++++++++++++++++----
 drivers/md/bcache/writeback.h |  13 +
 4 files changed, 470 insertions(+), 57 deletions(-)

diff --git a/drivers/md/bcache/bcache.h b/drivers/md/bcache/bcache.h
index ec9ff9715..620f2d4a5 100644
--- a/drivers/md/bcache/bcache.h
+++ b/drivers/md/bcache/bcache.h
@@ -347,6 +347,17 @@ struct cached_dev {
 	struct workqueue_struct	*writeback_write_wq;
 
 	struct keybuf		writeback_keys;
+	/*
+	 * writeback_flush_pending and writeback_flush_lock are accessed
+	 * by both the writeback thread (via writeback_add_to_pending and
+	 * writeback_finish_batch) and foreground writes (via
+	 * bch_writeback_drop_pending).  writeback_flush_passes is
+	 * accessed only by the writeback thread.
+	 */
+	struct rb_root		writeback_flush_pending;
+	spinlock_t		writeback_flush_lock;
+	unsigned int		writeback_flush_passes;
+	mempool_t		*writeback_pending_pool;
 
 	struct task_struct	*status_update_thread;
 	/*
diff --git a/drivers/md/bcache/request.c b/drivers/md/bcache/request.c
index 3fa3b13a4..b2c5acd88 100644
--- a/drivers/md/bcache/request.c
+++ b/drivers/md/bcache/request.c
@@ -990,13 +990,15 @@ static void cached_dev_write(struct cached_dev *dc, struct search *s)
 	down_read_non_owner(&dc->writeback_lock);
 	if (bch_keybuf_check_overlapping(&dc->writeback_keys, &start, &end)) {
 		/*
-		 * We overlap with some dirty data undergoing background
-		 * writeback, force this write to writeback
+		 * We overlap with dirty data undergoing background
+		 * writeback, force this write to writeback.
 		 */
 		s->iop.bypass = false;
 		s->iop.writeback = true;
 	}
 
+	bch_writeback_drop_pending(dc, &start, &end);
+
 	/*
 	 * Discards aren't _required_ to do anything, so skipping if
 	 * check_overlapping returned true is ok
diff --git a/drivers/md/bcache/writeback.c b/drivers/md/bcache/writeback.c
index 4b237074f..7a1ec8f03 100644
--- a/drivers/md/bcache/writeback.c
+++ b/drivers/md/bcache/writeback.c
@@ -319,6 +319,12 @@ static unsigned int writeback_delay(struct cached_dev *dc,
 	return bch_next_delay(&dc->writeback_rate, sectors);
 }
 
+struct writeback_pending {
+	struct rb_node		node;
+	struct list_head	list;
+	BKEY_PADDED(key);
+};
+
 struct dirty_io {
 	struct closure		cl;
 	struct cached_dev	*dc;
@@ -348,38 +354,83 @@ static CLOSURE_CALLBACK(dirty_io_destructor)
 	kfree(io);
 }
 
+/*
+ * Insert wp into writeback_flush_pending rbtree (ordered by START_KEY).
+ * Caller must hold dc->writeback_flush_lock.
+ *
+ * The rbtree is used as an interval tree by bch_writeback_drop_pending()
+ * via a standard BST interval search.  That search is only correct because
+ * the entries are pairwise disjoint (dirty btree keys never overlap).
+ *
+ * Returns false if an overlapping entry already exists.  This is the
+ * expected outcome when PASS_PER_FLUSH > 1 and a key is re-selected by
+ * a later refill pass while still sitting in the pending tree — the key
+ * is still KEY_DIRTY in the btree until writeback_clean_key() runs.
+ * The read-and-rewrite cycle is wasted IO, but the tree stays consistent
+ * and the key will be cleaned when the next flush finally fires.
+ */
+static bool __writeback_pending_insert(struct cached_dev *dc,
+				       struct writeback_pending *wp)
+{
+	struct writeback_pending *entry;
+	struct rb_node **p, *parent = NULL;
+
+	lockdep_assert_held(&dc->writeback_flush_lock);
+
+	p = &dc->writeback_flush_pending.rb_node;
+	while (*p) {
+		parent = *p;
+		entry = rb_entry(parent, struct writeback_pending, node);
+		if (bkey_cmp(&wp->key, &START_KEY(&entry->key)) <= 0)
+			p = &(*p)->rb_left;
+		else if (bkey_cmp(&START_KEY(&wp->key), &entry->key) >= 0)
+			p = &(*p)->rb_right;
+		else
+			return false; /* overlap */
+	}
+	rb_link_node(&wp->node, parent, p);
+	rb_insert_color(&wp->node, &dc->writeback_flush_pending);
+	return true;
+}
+
+/*
+ * Temporarily add this key to writeback_flush_pending. After enough
+ * read_dirty() passes finish, or the full btree scan completes,
+ * writeback_finish_batch() explicitly flushes the backing device before
+ * inserting the cleaned keys back into the btree. This guarantees that
+ * the backing data will be on stable media before the dirty btree keys
+ * are overwritten by the clean keys, avoiding stale clean bkeys after
+ * power failure.
+ */
+static void writeback_add_to_pending(struct cached_dev *dc,
+				     struct bkey *key)
+{
+	struct writeback_pending *wp;
+
+	wp = mempool_alloc(dc->writeback_pending_pool, GFP_NOIO);
+
+	INIT_LIST_HEAD(&wp->list);
+	bkey_copy(&wp->key, key);
+	spin_lock(&dc->writeback_flush_lock);
+	if (!__writeback_pending_insert(dc, wp)) {
+		spin_unlock(&dc->writeback_flush_lock);
+		mempool_free(wp, dc->writeback_pending_pool);
+		return;
+	}
+	spin_unlock(&dc->writeback_flush_lock);
+}
+
 static CLOSURE_CALLBACK(write_dirty_finish)
 {
 	closure_type(io, struct dirty_io, cl);
 	struct keybuf_key *w = io->bio.bi_private;
 	struct cached_dev *dc = io->dc;
+	bool written = KEY_DIRTY(&w->key);
 
 	bio_free_pages(&io->bio);
 
-	/* This is kind of a dumb way of signalling errors. */
-	if (KEY_DIRTY(&w->key)) {
-		int ret;
-		unsigned int i;
-		struct keylist keys;
-
-		bch_keylist_init(&keys);
-
-		bkey_copy(keys.top, &w->key);
-		SET_KEY_DIRTY(keys.top, false);
-		bch_keylist_push(&keys);
-
-		for (i = 0; i < KEY_PTRS(&w->key); i++)
-			atomic_inc(&PTR_BUCKET(dc->disk.c, &w->key, i)->pin);
-
-		ret = bch_btree_insert(dc->disk.c, &keys, NULL, &w->key);
-
-		if (ret)
-			trace_bcache_writeback_collision(&w->key);
-
-		atomic_long_inc(ret
-				? &dc->disk.c->writeback_keys_failed
-				: &dc->disk.c->writeback_keys_done);
-	}
+	if (written)
+		writeback_add_to_pending(dc, &w->key);
 
 	bch_keybuf_del(&dc->writeback_keys, w);
 	up(&dc->in_flight);
@@ -400,6 +451,213 @@ static void dirty_endio(struct bio *bio)
 	closure_put(&io->cl);
 }
 
+/*
+ * Issue a flush to the backing device.  Open-codes blkdev_issue_flush()
+ * so we can call bch_count_backing_io_errors() on failure.
+ */
+static int writeback_flush(struct cached_dev *dc)
+{
+	struct bio bio;
+	int ret;
+
+	bio_init(&bio, dc->bdev, NULL, 0, REQ_OP_WRITE | REQ_PREFLUSH);
+
+	ret = submit_bio_wait(&bio);
+	if (ret)
+		bch_count_backing_io_errors(dc, &bio);
+
+	bio_uninit(&bio);
+	return ret;
+}
+
+static void writeback_clean_key(struct cached_dev *dc, struct bkey *key)
+{
+	int ret;
+	unsigned int i;
+	struct keylist keys;
+
+	bch_keylist_init(&keys);
+
+	bkey_copy(keys.top, key);
+	SET_KEY_DIRTY(keys.top, false);
+	bch_keylist_push(&keys);
+
+	for (i = 0; i < KEY_PTRS(key); i++)
+		atomic_inc(&PTR_BUCKET(dc->disk.c, key, i)->pin);
+
+	/*
+	 * If the insert fails (e.g. GC moved the cached data and the key's
+	 * pointers went stale), just give up. The key stays dirty in btree
+	 * and will be re-selected on the next writeback pass. The backing
+	 * device already has the correct data, so no harm done.
+	 */
+	ret = bch_btree_insert(dc->disk.c, &keys, NULL, key);
+
+	if (ret)
+		trace_bcache_writeback_collision(key);
+
+	atomic_long_inc(ret
+			? &dc->disk.c->writeback_keys_failed
+			: &dc->disk.c->writeback_keys_done);
+}
+
+static void writeback_drain_pending(struct cached_dev *dc)
+{
+	struct writeback_pending *wp, *n;
+	LIST_HEAD(keys);
+
+	/*
+	 * Called only at writeback-thread exit, after the workqueue has
+	 * been destroyed.  New foreground writers can no longer reach
+	 * bch_writeback_drop_pending() at this point, but we hold the
+	 * lock anyway to keep the API uniform with the rest of the
+	 * pending-tree code.
+	 */
+	spin_lock(&dc->writeback_flush_lock);
+	while (!RB_EMPTY_ROOT(&dc->writeback_flush_pending)) {
+		struct rb_node *rb_node =
+			rb_first(&dc->writeback_flush_pending);
+
+		wp = rb_entry(rb_node, struct writeback_pending, node);
+		rb_erase(rb_node, &dc->writeback_flush_pending);
+		list_add(&wp->list, &keys);
+	}
+	spin_unlock(&dc->writeback_flush_lock);
+
+	list_for_each_entry_safe(wp, n, &keys, list)
+		mempool_free(wp, dc->writeback_pending_pool);
+}
+
+static bool writeback_pending_empty(struct cached_dev *dc)
+{
+	bool ret;
+
+	spin_lock(&dc->writeback_flush_lock);
+	ret = RB_EMPTY_ROOT(&dc->writeback_flush_pending);
+	spin_unlock(&dc->writeback_flush_lock);
+
+	return ret;
+}
+
+/*
+ * Drop all entries in writeback_flush_pending that overlap [start, end).
+ * Called from the foreground write path to force-writeback overlapped
+ * dirty data instead of bypassing the cache.
+ *
+ * The search below is a standard BST interval lookup that relies on the
+ * tree being pairwise disjoint: an entry's (START_KEY, end_key) range
+ * never overlaps another entry's range.  The disjoint invariant is
+ * enforced by __writeback_pending_insert() which rejects overlapping
+ * inserts.  Without this invariant the search would need the "max subtree
+ * end" augmentation provided by lib/interval_tree.
+ */
+bool bch_writeback_drop_pending(struct cached_dev *dc,
+				struct bkey *start,
+				struct bkey *end)
+{
+	struct writeback_pending *wp, *n;
+	LIST_HEAD(overlap);
+	struct rb_node *node;
+	bool ret = false;
+
+	spin_lock(&dc->writeback_flush_lock);
+	node = dc->writeback_flush_pending.rb_node;
+	while (node) {
+		wp = rb_entry(node, struct writeback_pending, node);
+
+		if (bkey_cmp(end, &START_KEY(&wp->key)) <= 0) {
+			node = node->rb_left;
+		} else if (bkey_cmp(start, &wp->key) >= 0) {
+			node = node->rb_right;
+		} else {
+			rb_erase(&wp->node, &dc->writeback_flush_pending);
+			list_add(&wp->list, &overlap);
+			ret = true;
+			/* restart search from root after erase */
+			node = dc->writeback_flush_pending.rb_node;
+		}
+	}
+	spin_unlock(&dc->writeback_flush_lock);
+
+	list_for_each_entry_safe(wp, n, &overlap, list)
+		mempool_free(wp, dc->writeback_pending_pool);
+
+	return ret;
+}
+
+static bool writeback_finish_batch(struct cached_dev *dc)
+{
+	struct writeback_pending *wp, *n;
+	LIST_HEAD(keys);
+	int flush_ret = 0;
+
+	spin_lock(&dc->writeback_flush_lock);
+	if (RB_EMPTY_ROOT(&dc->writeback_flush_pending)) {
+		spin_unlock(&dc->writeback_flush_lock);
+		return true;
+	}
+	/*
+	 * Detach all entries into a local list. New completions arriving
+	 * during the flush below will be added to the (now empty) tree.
+	 *
+	 * While the entries sit on the local list, they are invisible to
+	 * bch_writeback_drop_pending(), so a concurrent front-end write
+	 * overlapping a flushing key will not force-writeback and instead
+	 * write around the cache.  The resulting invalidation key has
+	 * KEY_PTRS=0 (bch_data_invalidate), which trims or removes the
+	 * overlapping dirty btree key.  The subsequent clean-key insert in
+	 * writeback_clean_key() then hits a pointer mismatch in
+	 * bch_extent_insert_fixup() -> replace_key(), producing an
+	 * insert_collision (ESRCH).  The clean-key insert is a no-op.
+	 * Subsequent reads miss the cache and go to the backing device,
+	 * which has the new data.  No data is lost.
+	 */
+	while (!RB_EMPTY_ROOT(&dc->writeback_flush_pending)) {
+		struct rb_node *rb_node =
+			rb_first(&dc->writeback_flush_pending);
+
+		wp = rb_entry(rb_node, struct writeback_pending, node);
+		rb_erase(rb_node, &dc->writeback_flush_pending);
+		list_add(&wp->list, &keys);
+	}
+	spin_unlock(&dc->writeback_flush_lock);
+
+	flush_ret = writeback_flush(dc);
+
+	if (flush_ret) {
+		/*
+		 * Re-insert all saved entries back into the current tree
+		 * (which may have accumulated new entries during the flush).
+		 * This preserves both old and new keys instead of leaking
+		 * the new ones.
+		 */
+		spin_lock(&dc->writeback_flush_lock);
+		list_for_each_entry_safe(wp, n, &keys, list) {
+			if (!__writeback_pending_insert(dc, wp)) {
+				/*
+				 * A new writeback completion added this range
+				 * while we held the lock for re-insert —
+				 * expected with PASS_PER_FLUSH > 1.
+				 */
+				list_del_init(&wp->list);
+				mempool_free(wp, dc->writeback_pending_pool);
+				continue;
+			}
+			list_del_init(&wp->list);
+		}
+		spin_unlock(&dc->writeback_flush_lock);
+
+		return false;
+	}
+
+	list_for_each_entry_safe(wp, n, &keys, list) {
+		writeback_clean_key(dc, &wp->key);
+		mempool_free(wp, dc->writeback_pending_pool);
+	}
+
+	return true;
+}
+
 static CLOSURE_CALLBACK(write_dirty)
 {
 	closure_type(io, struct dirty_io, cl);
@@ -471,7 +729,14 @@ static CLOSURE_CALLBACK(read_dirty_submit)
 	continue_at(cl, write_dirty, io->dc->writeback_write_wq);
 }
 
-static void read_dirty(struct cached_dev *dc)
+enum writeback_scan_result {
+	WB_SCAN_PARTIAL,
+	WB_SCAN_PARTIAL_WRAP,
+	WB_SCAN_FULL_SEARCHED,
+};
+
+static void read_dirty(struct cached_dev *dc,
+		       enum writeback_scan_result scan_result)
 {
 	unsigned int delay = 0;
 	struct keybuf_key *next, *keys[MAX_WRITEBACKS_IN_PASS], *w;
@@ -585,10 +850,27 @@ static void read_dirty(struct cached_dev *dc)
 	}
 
 	/*
-	 * Wait for outstanding writeback IOs to finish (and keybuf slots to be
-	 * freed) before refilling again
+	 * Wait for outstanding writeback IOs to finish before refilling again.
+	 * Clean keys are inserted after writeback_finish_batch() flushes the
+	 * backing device.
 	 */
 	closure_sync(&cl);
+
+	dc->writeback_flush_passes++;
+	/*
+	 * WB_SCAN_PARTIAL_WRAP covers two sub-cases: (i) the pre-wrap
+	 * flush failed, so keys are still in the pending tree; (ii) the
+	 * wrap scan filled the keybuf before reaching start_pos.  In
+	 * case (ii) the flush is conservative — we could defer it until
+	 * another pass — but the extra flush is cheap and the simpler
+	 * predicate avoids tracking the distinction through refill_dirty.
+	 */
+	if (dc->writeback_flush_passes >= PASS_PER_FLUSH ||
+	    scan_result == WB_SCAN_FULL_SEARCHED ||
+	    scan_result == WB_SCAN_PARTIAL_WRAP) {
+		if (writeback_finish_batch(dc))
+			dc->writeback_flush_passes = 0;
+	}
 }
 
 /* Scan for dirty data */
@@ -652,28 +934,44 @@ static bool dirty_pred(struct keybuf *buf, struct bkey *k)
 static void refill_full_stripes(struct cached_dev *dc)
 {
 	struct keybuf *buf = &dc->writeback_keys;
-	unsigned int start_stripe, next_stripe;
+	unsigned int next_stripe;
 	int stripe;
-	bool wrapped = false;
 
 	stripe = offset_to_stripe(&dc->disk, KEY_OFFSET(&buf->last_scanned));
 	if (stripe < 0)
 		stripe = 0;
 
-	start_stripe = stripe;
-
 	while (1) {
 		stripe = find_next_bit(dc->disk.full_dirty_stripes,
 				       dc->disk.nr_stripes, stripe);
 
+		/*
+		 * Reached the end of stripes.  Do not wrap here — return
+		 * and let refill_dirty() handle the wrap (which optionally
+		 * flushes pending entries first), so keys already in
+		 * writeback_flush_pending are not re-selected.
+		 */
 		if (stripe == dc->disk.nr_stripes)
-			goto next;
+			return;
 
 		next_stripe = find_next_zero_bit(dc->disk.full_dirty_stripes,
 						 dc->disk.nr_stripes, stripe);
 
-		buf->last_scanned = KEY(dc->disk.id,
-					stripe * dc->disk.stripe_size, 0);
+		/*
+		 * Don't rewind last_scanned: if the keybuf filled
+		 * mid-range on the previous pass, keys from the
+		 * start of this stripe up to last_scanned have
+		 * already been written back and may be sitting in
+		 * writeback_flush_pending (still dirty in btree).
+		 * Rewinding would re-select them, creating
+		 * duplicates.
+		 */
+		if (bkey_cmp(&buf->last_scanned,
+			     &KEY(dc->disk.id,
+				  stripe * dc->disk.stripe_size, 0)) < 0)
+			buf->last_scanned = KEY(dc->disk.id,
+						stripe * dc->disk.stripe_size,
+						0);
 
 		bch_refill_keybuf(dc->disk.c, buf,
 				  &KEY(dc->disk.id,
@@ -684,21 +982,19 @@ static void refill_full_stripes(struct cached_dev *dc)
 			return;
 
 		stripe = next_stripe;
-next:
-		if (wrapped && stripe > start_stripe)
-			return;
-
-		if (stripe == dc->disk.nr_stripes) {
-			stripe = 0;
-			wrapped = true;
-		}
 	}
 }
 
 /*
- * Returns true if we scanned the entire disk
+ * Refill the writeback keybuf by scanning the btree for dirty keys.
+ *
+ * IMPORTANT: writeback_lock is held on entry but may be transiently
+ * released mid-function when flushing pending keys before a wrap-around
+ * scan.  Callers must not rely on the lock being held across the call.
+ *
+ * Returns the scan result indicating scan coverage.
  */
-static bool refill_dirty(struct cached_dev *dc)
+static enum writeback_scan_result refill_dirty(struct cached_dev *dc)
 {
 	struct keybuf *buf = &dc->writeback_keys;
 	struct bkey start = KEY(dc->disk.id, 0, 0);
@@ -717,30 +1013,49 @@ static bool refill_dirty(struct cached_dev *dc)
 	if (dc->partial_stripes_expensive) {
 		refill_full_stripes(dc);
 		if (array_freelist_empty(&buf->freelist))
-			return false;
+			return WB_SCAN_PARTIAL;
 	}
 
 	start_pos = buf->last_scanned;
 	bch_refill_keybuf(dc->disk.c, buf, &end, dirty_pred);
 
 	if (bkey_cmp(&buf->last_scanned, &end) < 0)
-		return false;
+		return WB_SCAN_PARTIAL;
 
 	/*
-	 * If we get to the end start scanning again from the beginning, and
-	 * only scan up to where we initially started scanning from:
+	 * Forward scan reached the end. Flush pending writeback keys
+	 * before wrapping around, so the wrap scan won't re-select
+	 * keys that are already on writeback_flush_pending.
+	 */
+	if (!writeback_pending_empty(dc)) {
+		bool flushed;
+
+		up_write(&dc->writeback_lock);
+		flushed = writeback_finish_batch(dc);
+		down_write(&dc->writeback_lock);
+
+		if (!flushed)
+			return WB_SCAN_PARTIAL_WRAP;
+	}
+
+	/*
+	 * Start scanning again from the beginning, up to where we
+	 * initially started.
 	 */
 	buf->last_scanned = start;
 	bch_refill_keybuf(dc->disk.c, buf, &start_pos, dirty_pred);
 
-	return bkey_cmp(&buf->last_scanned, &start_pos) >= 0;
+	if (bkey_cmp(&buf->last_scanned, &start_pos) < 0)
+		return WB_SCAN_PARTIAL_WRAP;
+
+	return WB_SCAN_FULL_SEARCHED;
 }
 
 static int bch_writeback_thread(void *arg)
 {
 	struct cached_dev *dc = arg;
 	struct cache_set *c = dc->disk.c;
-	bool searched_full_index;
+	enum writeback_scan_result searched_full_index;
 
 	bch_ratelimit_reset(&dc->writeback_rate);
 
@@ -765,6 +1080,26 @@ static int bch_writeback_thread(void *arg)
 				break;
 			}
 
+			/*
+			 * Drain writeback_flush_pending before sleeping.
+			 * If writeback_running is cleared mid-cycle (e.g., at
+			 * pass 37 of PASS_PER_FLUSH=40), read_dirty() waits
+			 * for all IOs via closure_sync, so write_dirty_finish()
+			 * has already pushed entries into
+			 * writeback_flush_pending before this sleep check is
+			 * reached.  With writeback stopped, no further
+			 * read_dirty() call fires and the next PASS_PER_FLUSH
+			 * threshold is never reached.
+			 *
+			 * Use continue so the sleep condition is re-evaluated
+			 * after the drain completes.
+			 */
+			if (!writeback_pending_empty(dc)) {
+				set_current_state(TASK_RUNNING);
+				writeback_finish_batch(dc);
+				continue;
+			}
+
 			schedule();
 			continue;
 		}
@@ -772,8 +1107,9 @@ static int bch_writeback_thread(void *arg)
 
 		searched_full_index = refill_dirty(dc);
 
-		if (searched_full_index &&
-		    RB_EMPTY_ROOT(&dc->writeback_keys.keys)) {
+		if (searched_full_index == WB_SCAN_FULL_SEARCHED &&
+		    RB_EMPTY_ROOT(&dc->writeback_keys.keys) &&
+		    writeback_pending_empty(dc)) {
 			atomic_set(&dc->has_dirty, 0);
 			SET_BDEV_STATE(&dc->sb, BDEV_STATE_CLEAN);
 			bch_write_bdev_super(dc, NULL);
@@ -816,9 +1152,9 @@ static int bch_writeback_thread(void *arg)
 
 		up_write(&dc->writeback_lock);
 
-		read_dirty(dc);
+		read_dirty(dc, searched_full_index);
 
-		if (searched_full_index) {
+		if (searched_full_index == WB_SCAN_FULL_SEARCHED) {
 			unsigned int delay = dc->writeback_delay * HZ;
 
 			while (delay &&
@@ -831,8 +1167,40 @@ static int bch_writeback_thread(void *arg)
 		}
 	}
 
-	if (dc->writeback_write_wq)
+	if (dc->writeback_write_wq) {
 		destroy_workqueue(dc->writeback_write_wq);
+		dc->writeback_write_wq = NULL;
+	}
+
+	/*
+	 * Try a final flush-and-clean before draining so pending keys are
+	 * cleaned rather than left dirty for the next mount.
+	 *
+	 * Skip when the cache set is disabled (flush would hang on a dead
+	 * backing device) or the device is detaching (the in-loop drain
+	 * has already cleaned everything; otherwise we tolerate redoing
+	 * the work on next mount).
+	 *
+	 * Note: a backing-device failure that races the flag checks above
+	 * can still hang kthread teardown — bounded by the device's own
+	 * IO-error timeout.
+	 *
+	 * We do NOT set BDEV_STATE_CLEAN here because the in-loop check
+	 * additionally requires WB_SCAN_FULL_SEARCHED (a full btree scan
+	 * found nothing dirty).  Without that gate, the on-disk superblock
+	 * could be marked clean while the btree still holds dirty keys
+	 * that were never scanned by this thread (e.g. kthread_stop from
+	 * bch_cached_dev_attach's error path).  Let the next mount observe
+	 * BDEV_STATE_DIRTY and re-confirm.
+	 */
+	if (!test_bit(CACHE_SET_IO_DISABLE, &c->flags) &&
+	    !test_bit(BCACHE_DEV_DETACHING, &dc->disk.flags))
+		writeback_finish_batch(dc);
+
+	writeback_drain_pending(dc);
+
+	mempool_destroy(dc->writeback_pending_pool);
+	dc->writeback_pending_pool = NULL;
 
 	cached_dev_put(dc);
 	wait_for_kthread_stop();
@@ -1049,6 +1417,9 @@ void bch_cached_dev_writeback_init(struct cached_dev *dc)
 	sema_init(&dc->in_flight, 64);
 	init_rwsem(&dc->writeback_lock);
 	bch_keybuf_init(&dc->writeback_keys);
+	dc->writeback_flush_pending = RB_ROOT;
+	spin_lock_init(&dc->writeback_flush_lock);
+	dc->writeback_flush_passes	= 0;
 
 	dc->writeback_metadata		= true;
 	dc->writeback_running		= false;
@@ -1079,11 +1450,27 @@ int bch_cached_dev_writeback_start(struct cached_dev *dc)
 	if (!dc->writeback_write_wq)
 		return -ENOMEM;
 
+	/*
+	 * Pre-allocate a pool sized to the maximum number of concurrent
+	 * writeback IOs (bounded by the in_flight semaphore, initialised to
+	 * 64).  mempool_alloc(GFP_NOIO) will draw from this reserve when the
+	 * normal allocator fails under memory pressure, guaranteeing that
+	 * writeback_add_to_pending() never drops an entry silently.
+	 */
+	dc->writeback_pending_pool = mempool_create_kmalloc_pool(
+			64, sizeof(struct writeback_pending));
+	if (!dc->writeback_pending_pool) {
+		destroy_workqueue(dc->writeback_write_wq);
+		return -ENOMEM;
+	}
+
 	cached_dev_get(dc);
 	dc->writeback_thread = kthread_create(bch_writeback_thread, dc,
 					      "bcache_writeback");
 	if (IS_ERR(dc->writeback_thread)) {
 		cached_dev_put(dc);
+		mempool_destroy(dc->writeback_pending_pool);
+		dc->writeback_pending_pool = NULL;
 		destroy_workqueue(dc->writeback_write_wq);
 		return PTR_ERR(dc->writeback_thread);
 	}
diff --git a/drivers/md/bcache/writeback.h b/drivers/md/bcache/writeback.h
index 31df71695..51aba51b2 100644
--- a/drivers/md/bcache/writeback.h
+++ b/drivers/md/bcache/writeback.h
@@ -10,6 +10,14 @@
 
 #define MAX_WRITEBACKS_IN_PASS  5
 #define MAX_WRITESIZE_IN_PASS   5000	/* *512b */
+/*
+ * Number of read_dirty() passes before forcing a backing-device flush
+ * and clean-key insert.  Larger values amortize flush cost but may
+ * re-select keys still in writeback_flush_pending (still KEY_DIRTY)
+ * and re-write them, causing IO amplification.  Small values flush
+ * more often, reducing amplification at the cost of more flushes.
+ */
+#define PASS_PER_FLUSH		5
 
 #define WRITEBACK_RATE_UPDATE_SECS_MAX		60
 #define WRITEBACK_RATE_UPDATE_SECS_DEFAULT	5
@@ -21,6 +29,7 @@
 #define BCH_WRITEBACK_FRAGMENT_THRESHOLD_HIGH 64
 
 #define BCH_DIRTY_INIT_THRD_MAX	12
+
 /*
  * 14 (16384ths) is chosen here as something that each backing device
  * should be a reasonable fraction of the share, and not to blow up
@@ -28,6 +37,10 @@
  */
 #define WRITEBACK_SHARE_SHIFT   14
 
+bool bch_writeback_drop_pending(struct cached_dev *dc,
+				struct bkey *start,
+				struct bkey *end);
+
 struct bch_dirty_init_state;
 struct dirty_init_thrd_info {
 	struct bch_dirty_init_state	*state;
-- 
2.52.0.windows.1