[PATCH RFC] bcache: flush backing device before cleaning the writeback dirty keys
[email protected] Tue, 26 May 2026 13:44:38 +0800
| Newsgroups | org.kernel.vger.linux-bcache |
|---|---|
| Message-ID | <[email protected]> |
From: Coly Li <[email protected]> After dirty data on cache device is written back to backing device, the dirty keys of these written dirty data are set to clean and inserted back to the btree. There is no explicit flush bio sent to the backing device, whether the dirty data is flushed onto disk media or still in hard drive's DRAM cache, it is uncertain. If a power failure occurs after the cleaned keys are inserted back to the btree while dirty data still stays in hard drive's DRAM cache, after the reboot a stale data of the written data may happen because the dirty key is clean now, but dirty data has not been persisted indeed. This patch introduce struct writeback_batch, and allocates a on-stack batch in read_dirty(), uses it to collect all successful writeback I/Os (a.k.a struct dirty_io). Then in write_dirty_finish() the successfully written back dirty key is not cleaned and inserted back to the btree immediately, it is added into batch->keys list and delayed to a new function writeback_finish_batch() to be cleaned and inserted back to the btree. In writeback_finish_batch, before inserting back all the cleaned keys to the btree by writeback_clean_key(), an explicit flush bio is issued to backing device if a real writeback I/O once happened. By this method, when writeback I/Os are busy, a flush bio will be issued around every 500 writeback I/O based on current code. Therefore an explicit backing device flush bio can be issued in time (every batch writeback I/Os in read_dirty()), and in a reasonable frequency. Now this patch just passes compiling, not survive long time pressure testing yet. Fixes: cafe563591446 ("bcache: A block layer cache") Reported-by: Zhou Jifeng <[email protected]> Cc: [email protected] Signed-off-by: Coly Li <[email protected]> --- drivers/md/bcache/writeback.c | 133 +++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 27 deletions(-) diff --git a/drivers/md/bcache/writeback.c b/drivers/md/bcache/writeback.c index 4b237074f453..81b4514d6532 100644 --- a/drivers/md/bcache/writeback.c +++ b/drivers/md/bcache/writeback.c @@ -320,14 +320,22 @@ static unsigned int writeback_delay(struct cached_dev *dc, } struct dirty_io { struct closure cl; struct cached_dev *dc; + struct writeback_batch *batch; + struct list_head list; uint16_t sequence; struct bio bio; }; +struct writeback_batch { + struct list_head keys; + spinlock_t lock; + bool need_flush; +}; + static void dirty_init(struct keybuf_key *w) { struct dirty_io *io = w->private; struct bio *bio = &io->bio; @@ -351,42 +359,41 @@ static CLOSURE_CALLBACK(dirty_io_destructor) 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); + /* + * Temporarily add this key into io->batch->keys. Once all dirty keys + * in dc->writeback_keys are written back to backing device, a flush + * bio will be issued to backing device by writeback_finish_batch(). + * Then before all clean keys are inserted back to btree, the writeback + * dirty data is for sure flushed to backing device. + * If power failure occurs after the clean key is inserted back to the + * btree, the dirty data has already been in the disk platter and a + * stale clean bkey can be avoided. + */ + if (written) { + INIT_LIST_HEAD(&io->list); + if (io->batch->need_flush != true) + io->batch->need_flush = true; + spin_lock(&io->batch->lock); + list_add_tail(&io->list, &io->batch->keys); + spin_unlock(&io->batch->lock); + } else { + bch_keybuf_del(&dc->writeback_keys, w); } - bch_keybuf_del(&dc->writeback_keys, w); up(&dc->in_flight); - closure_return_with_destructor(cl, dirty_io_destructor); + if (written) + closure_return(cl); + else + closure_return_with_destructor(cl, dirty_io_destructor); } static void dirty_endio(struct bio *bio) { struct keybuf_key *w = bio->bi_private; @@ -398,10 +405,74 @@ static void dirty_endio(struct bio *bio) } closure_put(&io->cl); } +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 keybuf_key *w) +{ + 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); +} + +static void writeback_finish_batch(struct cached_dev *dc, + struct writeback_batch *batch) +{ + struct dirty_io *io, *t; + int flush_ret = 0; + + if (batch->need_flush) + flush_ret = writeback_flush(dc); + + list_for_each_entry_safe(io, t, &batch->keys, list) { + struct keybuf_key *w = io->bio.bi_private; + + list_del(&io->list); + + if (flush_ret == 0) + writeback_clean_key(dc, w); + else + atomic_long_inc(&dc->disk.c->writeback_keys_failed); + + bch_keybuf_del(&dc->writeback_keys, w); + kfree(io); + } +} + static CLOSURE_CALLBACK(write_dirty) { closure_type(io, struct dirty_io, cl); struct keybuf_key *w = io->bio.bi_private; struct cached_dev *dc = io->dc; @@ -478,15 +549,20 @@ static void read_dirty(struct cached_dev *dc) size_t size; int nk, i; struct dirty_io *io; struct closure cl; uint16_t sequence = 0; + struct writeback_batch batch; BUG_ON(!llist_empty(&dc->writeback_ordering_wait.list)); atomic_set(&dc->writeback_sequence_next, sequence); closure_init_stack(&cl); + INIT_LIST_HEAD(&batch.keys); + spin_lock_init(&batch.lock); + batch.need_flush = false; + /* * XXX: if we error, background writeback just spins. Should use some * mempools. */ @@ -542,10 +618,11 @@ static void read_dirty(struct cached_dev *dc) if (!io) goto err; w->private = io; io->dc = dc; + io->batch = &batch; io->sequence = sequence++; dirty_init(w); io->bio.bi_opf = REQ_OP_READ; io->bio.bi_iter.bi_sector = PTR_OFFSET(&w->key, 0); @@ -583,14 +660,16 @@ static void read_dirty(struct cached_dev *dc) err: bch_keybuf_del(&dc->writeback_keys, w); } /* - * Wait for outstanding writeback IOs to finish (and keybuf slots to be - * freed) before refilling again + * Wait for outstanding writeback IOs to finish, then flush the + * backing device, insert the clean keys back to btree, and free + * keybuf slots. */ closure_sync(&cl); + writeback_finish_batch(dc, &batch); } /* Scan for dirty data */ void bcache_dev_sectors_dirty_add(struct cache_set *c, unsigned int inode, -- 2.47.3