All of lore.kernel.org
 help / color / mirror / Atom feed
From: Coly Li <colyli@suse.de>
To: axboe@kernel.dk
Cc: linux-bcache@vger.kernel.org, linux-block@vger.kernel.org,
	Coly Li <colyli@suse.de>,
	stable@vger.kernel.org
Subject: [PATCH 35/37] bcache: fix race in btree_flush_write()
Date: Fri, 28 Jun 2019 19:59:58 +0800	[thread overview]
Message-ID: <20190628120000.40753-36-colyli@suse.de> (raw)
In-Reply-To: <20190628120000.40753-1-colyli@suse.de>

There is a race between mca_reap(), btree_node_free() and journal code
btree_flush_write(), which results very rare and strange deadlock or
panic and are very hard to reproduce.

Let me explain how the race happens. In btree_flush_write() one btree
node with oldest journal pin is selected, then it is flushed to cache
device, the select-and-flush is a two steps operation. Between these two
steps, there are something may happen inside the race window,
- The selected btree node was reaped by mca_reap() and allocated to
  other requesters for other btree node.
- The slected btree node was selected, flushed and released by mca
  shrink callback bch_mca_scan().
When btree_flush_write() tries to flush the selected btree node, firstly
b->write_lock is held by mutex_lock(). If the race happens and the
memory of selected btree node is allocated to other btree node, if that
btree node's write_lock is held already, a deadlock very probably
happens here. A worse case is the memory of the selected btree node is
released, then all references to this btree node (e.g. b->write_lock)
will trigger NULL pointer deference panic.

This race was introduced in commit cafe56359144 ("bcache: A block layer
cache"), and enlarged by commit c4dc2497d50d ("bcache: fix high CPU
occupancy during journal"), which selected 128 btree nodes and flushed
them one-by-one in a quite long time period.

Such race is not easy to reproduce before. On a Lenovo SR650 server with
48 Xeon cores, and configure 1 NVMe SSD as cache device, a MD raid0
device assembled by 3 NVMe SSDs as backing device, this race can be
observed around every 10,000 times btree_flush_write() gets called. Both
deadlock and kernel panic all happened as aftermath of the race.

The idea of the fix is to add a btree flag BTREE_NODE_journal_flush. It
is set when selecting btree nodes, and cleared after btree nodes
flushed. Then when mca_reap() selects a btree node with this bit set,
this btree node will be skipped. Since mca_reap() only reaps btree node
without BTREE_NODE_journal_flush flag, such race is avoided.

Once corner case should be noticed, that is btree_node_free(). It might
be called in some error handling code path. For example the following
code piece from btree_split(),
        2149 err_free2:
        2150         bkey_put(b->c, &n2->key);
        2151         btree_node_free(n2);
        2152         rw_unlock(true, n2);
        2153 err_free1:
        2154         bkey_put(b->c, &n1->key);
        2155         btree_node_free(n1);
        2156         rw_unlock(true, n1);
At line 2151 and 2155, the btree node n2 and n1 are released without
mac_reap(), so BTREE_NODE_journal_flush also needs to be checked here.
If btree_node_free() is called directly in such error handling path,
and the selected btree node has BTREE_NODE_journal_flush bit set, just
delay for 1 us and retry again. In this case this btree node won't
be skipped, just retry until the BTREE_NODE_journal_flush bit cleared,
and free the btree node memory.

Fixes: cafe56359144 ("bcache: A block layer cache")
Signed-off-by: Coly Li <colyli@suse.de>
Reported-and-tested-by: kbuild test robot <lkp@intel.com>
Cc: stable@vger.kernel.org
---
 drivers/md/bcache/btree.c   | 28 +++++++++++++++++++++++++++-
 drivers/md/bcache/btree.h   |  2 ++
 drivers/md/bcache/journal.c |  7 +++++++
 3 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/drivers/md/bcache/btree.c b/drivers/md/bcache/btree.c
index 846306c3a887..ba434d9ac720 100644
--- a/drivers/md/bcache/btree.c
+++ b/drivers/md/bcache/btree.c
@@ -35,7 +35,7 @@
 #include <linux/rcupdate.h>
 #include <linux/sched/clock.h>
 #include <linux/rculist.h>
-
+#include <linux/delay.h>
 #include <trace/events/bcache.h>
 
 /*
@@ -659,12 +659,25 @@ static int mca_reap(struct btree *b, unsigned int min_order, bool flush)
 		up(&b->io_mutex);
 	}
 
+retry:
 	/*
 	 * BTREE_NODE_dirty might be cleared in btree_flush_btree() by
 	 * __bch_btree_node_write(). To avoid an extra flush, acquire
 	 * b->write_lock before checking BTREE_NODE_dirty bit.
 	 */
 	mutex_lock(&b->write_lock);
+	/*
+	 * If this btree node is selected in btree_flush_write() by journal
+	 * code, delay and retry until the node is flushed by journal code
+	 * and BTREE_NODE_journal_flush bit cleared by btree_flush_write().
+	 */
+	if (btree_node_journal_flush(b)) {
+		pr_debug("bnode %p is flushing by journal, retry", b);
+		mutex_unlock(&b->write_lock);
+		udelay(1);
+		goto retry;
+	}
+
 	if (btree_node_dirty(b))
 		__bch_btree_node_write(b, &cl);
 	mutex_unlock(&b->write_lock);
@@ -1081,7 +1094,20 @@ static void btree_node_free(struct btree *b)
 
 	BUG_ON(b == b->c->root);
 
+retry:
 	mutex_lock(&b->write_lock);
+	/*
+	 * If the btree node is selected and flushing in btree_flush_write(),
+	 * delay and retry until the BTREE_NODE_journal_flush bit cleared,
+	 * then it is safe to free the btree node here. Otherwise this btree
+	 * node will be in race condition.
+	 */
+	if (btree_node_journal_flush(b)) {
+		mutex_unlock(&b->write_lock);
+		pr_debug("bnode %p journal_flush set, retry", b);
+		udelay(1);
+		goto retry;
+	}
 
 	if (btree_node_dirty(b)) {
 		btree_complete_write(b, btree_current_write(b));
diff --git a/drivers/md/bcache/btree.h b/drivers/md/bcache/btree.h
index d1c72ef64edf..76cfd121a486 100644
--- a/drivers/md/bcache/btree.h
+++ b/drivers/md/bcache/btree.h
@@ -158,11 +158,13 @@ enum btree_flags {
 	BTREE_NODE_io_error,
 	BTREE_NODE_dirty,
 	BTREE_NODE_write_idx,
+	BTREE_NODE_journal_flush,
 };
 
 BTREE_FLAG(io_error);
 BTREE_FLAG(dirty);
 BTREE_FLAG(write_idx);
+BTREE_FLAG(journal_flush);
 
 static inline struct btree_write *btree_current_write(struct btree *b)
 {
diff --git a/drivers/md/bcache/journal.c b/drivers/md/bcache/journal.c
index 1218e3cada3c..a1e3e1fcea6e 100644
--- a/drivers/md/bcache/journal.c
+++ b/drivers/md/bcache/journal.c
@@ -430,6 +430,7 @@ static void btree_flush_write(struct cache_set *c)
 retry:
 	best = NULL;
 
+	mutex_lock(&c->bucket_lock);
 	for_each_cached_btree(b, c, i)
 		if (btree_current_write(b)->journal) {
 			if (!best)
@@ -442,15 +443,21 @@ static void btree_flush_write(struct cache_set *c)
 		}
 
 	b = best;
+	if (b)
+		set_btree_node_journal_flush(b);
+	mutex_unlock(&c->bucket_lock);
+
 	if (b) {
 		mutex_lock(&b->write_lock);
 		if (!btree_current_write(b)->journal) {
+			clear_bit(BTREE_NODE_journal_flush, &b->flags);
 			mutex_unlock(&b->write_lock);
 			/* We raced */
 			goto retry;
 		}
 
 		__bch_btree_node_write(b, NULL);
+		clear_bit(BTREE_NODE_journal_flush, &b->flags);
 		mutex_unlock(&b->write_lock);
 	}
 }
-- 
2.16.4


  parent reply	other threads:[~2019-06-28 12:02 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-06-28 11:59 [PATCH 00/37] bcache patches for Linux v5.3 Coly Li
2019-06-28 11:59 ` [PATCH 01/37] bcache: don't set max writeback rate if gc is running Coly Li
2019-06-28 11:59 ` [PATCH 02/37] bcache: check c->gc_thread by IS_ERR_OR_NULL in cache_set_flush() Coly Li
2019-06-28 11:59 ` [PATCH 03/37] bcache: fix return value error in bch_journal_read() Coly Li
2019-06-28 11:59 ` [PATCH 04/37] Revert "bcache: set CACHE_SET_IO_DISABLE in bch_cached_dev_error()" Coly Li
2019-06-28 11:59 ` [PATCH 05/37] bcache: avoid flushing btree node in cache_set_flush() if io disabled Coly Li
2019-06-28 11:59 ` [PATCH 06/37] bcache: ignore read-ahead request failure on backing device Coly Li
2019-06-28 11:59 ` [PATCH 07/37] bcache: add io error counting in write_bdev_super_endio() Coly Li
2019-06-28 11:59 ` [PATCH 08/37] bcache: remove unnecessary prefetch() in bset_search_tree() Coly Li
2019-06-28 11:59 ` [PATCH 09/37] bcache: use sysfs_match_string() instead of __sysfs_match_string() Coly Li
2019-06-28 11:59 ` [PATCH 10/37] bcache: add return value check to bch_cached_dev_run() Coly Li
2019-06-28 11:59 ` [PATCH 11/37] bcache: remove unncessary code in bch_btree_keys_init() Coly Li
2019-06-28 11:59 ` [PATCH 12/37] bcache: check CACHE_SET_IO_DISABLE in allocator code Coly Li
2019-06-28 11:59 ` [PATCH 13/37] bcache: check CACHE_SET_IO_DISABLE bit in bch_journal() Coly Li
2019-06-28 11:59 ` [PATCH 14/37] bcache: more detailed error message to bcache_device_link() Coly Li
2019-06-28 11:59 ` [PATCH 15/37] bcache: add more error message in bch_cached_dev_attach() Coly Li
2019-06-28 11:59 ` [PATCH 16/37] bcache: improve error message in bch_cached_dev_run() Coly Li
2019-06-28 11:59 ` [PATCH 17/37] bcache: remove "XXX:" comment line from run_cache_set() Coly Li
2019-06-28 11:59 ` [PATCH 18/37] bcache: make bset_search_tree() be more understandable Coly Li
2019-06-28 11:59 ` [PATCH 19/37] bcache: add pendings_cleanup to stop pending bcache device Coly Li
2019-06-28 11:59 ` [PATCH 20/37] bcache: fix mistaken sysfs entry for io_error counter Coly Li
2019-06-28 11:59 ` [PATCH 21/37] bcache: destroy dc->writeback_write_wq if failed to create dc->writeback_thread Coly Li
2019-06-28 11:59 ` [PATCH 22/37] bcache: stop writeback kthread and kworker when bch_cached_dev_run() failed Coly Li
2019-06-28 11:59 ` [PATCH 23/37] bcache: avoid a deadlock in bcache_reboot() Coly Li
2019-06-28 11:59 ` [PATCH 24/37] bcache: acquire bch_register_lock later in cached_dev_detach_finish() Coly Li
2019-06-28 11:59 ` [PATCH 25/37] bcache: acquire bch_register_lock later in cached_dev_free() Coly Li
2019-06-28 11:59 ` [PATCH 26/37] bcache: fix potential deadlock in cached_def_free() Coly Li
2019-06-28 11:59 ` [PATCH 27/37] bcache: add code comments for journal_read_bucket() Coly Li
2019-06-28 11:59 ` [PATCH 28/37] bcache: set largest seq to ja->seq[bucket_index] in journal_read_bucket() Coly Li
2019-06-28 11:59 ` [PATCH 29/37] bcache: shrink btree node cache after bch_btree_check() Coly Li
2019-06-28 11:59 ` [PATCH 30/37] bcache: Revert "bcache: free heap cache_set->flush_btree in bch_journal_free" Coly Li
2019-06-28 11:59 ` [PATCH 31/37] bcache: Revert "bcache: fix high CPU occupancy during journal" Coly Li
2019-06-28 11:59 ` [PATCH 32/37] bcache: only clear BTREE_NODE_dirty bit when it is set Coly Li
2019-06-28 11:59 ` [PATCH 33/37] bcache: add comments for mutex_lock(&b->write_lock) Coly Li
2019-06-28 11:59 ` [PATCH 34/37] bcache: remove retry_flush_write from struct cache_set Coly Li
2019-06-28 11:59 ` Coly Li [this message]
2019-06-28 11:59 ` [PATCH 36/37] bcache: performance improvement for btree_flush_write() Coly Li
2019-06-28 12:00 ` [PATCH 37/37] bcache: add reclaimed_journal_buckets to struct cache_set Coly Li
2019-06-28 13:42 ` [PATCH 00/37] bcache patches for Linux v5.3 Jens Axboe

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190628120000.40753-36-colyli@suse.de \
    --to=colyli@suse.de \
    --cc=axboe@kernel.dk \
    --cc=linux-bcache@vger.kernel.org \
    --cc=linux-block@vger.kernel.org \
    --cc=stable@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.