summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@gmail.com>2015-08-14 22:24:34 -0800
committerKent Overstreet <kent.overstreet@gmail.com>2016-10-07 12:34:44 -0800
commit3b4484c3888dd4132fffd589be059c66122dbc58 (patch)
treee75c448e0013a72a6066d27c129e81ebe9e5d3be
parent05672b45c40c4b5e280828bbc8dd597cbc5b3edf (diff)
bcache: Fix an accounting bug
-rw-r--r--drivers/md/bcache/btree_update.c23
1 files changed, 18 insertions, 5 deletions
diff --git a/drivers/md/bcache/btree_update.c b/drivers/md/bcache/btree_update.c
index f8c0fc2f0767..c6feb4c33eb8 100644
--- a/drivers/md/bcache/btree_update.c
+++ b/drivers/md/bcache/btree_update.c
@@ -123,19 +123,32 @@ static void bch_pending_btree_node_free_insert_done(struct cache_set *c,
found:
d->index_update_done = true;
+ /*
+ * We're dropping @k from the btree, but it's still live until the index
+ * update is persistent so we need to keep a reference around for mark
+ * and sweep to find - that's primarily what the btree_node_pending_free
+ * list is for.
+ *
+ * So here (when we set index_update_done = true), we're moving an
+ * existing reference to a different part of the larger "gc keyspace" -
+ * and the new position comes after the old position, since GC marks the
+ * pending free list after it walks the btree.
+ *
+ * If we move the reference while mark and sweep is _between_ the old
+ * and the new position, mark and sweep will see the reference twice and
+ * it'll get double accounted - so check for that here and subtract to
+ * cancel out one of mark and sweep's markings if necessary:
+ */
+
if ((b
? !gc_will_visit_node(c, b)
: !gc_will_visit_root(c, id)) &&
gc_will_visit(c, GC_PHASE_PENDING_DELETE, POS_MIN, 0))
bch_mark_pointers(c, NULL,
bkey_i_to_s_c_extent(&d->key),
- CACHE_BTREE_NODE_SIZE(&c->sb),
+ -CACHE_BTREE_NODE_SIZE(&c->sb),
false, true, true);
- /*
- * XXX; check gc position and mark/unmark as needed
- */
-
mutex_unlock(&c->btree_node_pending_free_lock);
}