mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-25 20:14:25 +08:00
bcachefs: use prejournaled key updates for write buffer flushes
The write buffer mechanism journals keys twice in certain situations. A key is always journaled on write buffer insertion, and is potentially journaled again if a write buffer flush falls into either of the slow btree insert paths. This has shown to cause journal recovery ordering problems in the event of an untimely crash. For example, consider if a key is inserted into index 0 of a write buffer, the active write buffer switches to index 1, the key is deleted in index 1, and then index 0 is flushed. If the original key is rejournaled in the btree update from the index 0 flush, the (now deleted) key is journaled in a seq buffer ahead of the latest version of key (which was journaled when the key was deleted in index 1). If the fs crashes while this is still observable in the log, recovery sees the key from the btree update after the delete key from the write buffer insert, which is the incorrect order. This problem is occasionally reproduced by generic/388 and generally manifests as one or more backpointer entry inconsistencies. To avoid this problem, never rejournal write buffered key updates to the associated btree. Instead, use prejournaled key updates to pass the journal seq of the write buffer insert down to the btree insert, which updates the btree leaf pin to reflect the seq of the key. Note that tracking the seq is required instead of just using NOJOURNAL here because otherwise we lose protection of the write buffer pin when the buffer is flushed, which means the key can fall off the tail of the on-disk journal before the btree leaf is flushed and lead to similar recovery inconsistencies. Signed-off-by: Brian Foster <bfoster@redhat.com> Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
eabb10dc95
commit
60a5b89800
@ -75,7 +75,7 @@ static int bch2_btree_write_buffer_flush_one(struct btree_trans *trans,
|
||||
}
|
||||
return 0;
|
||||
trans_commit:
|
||||
return bch2_trans_update(trans, iter, &wb->k, 0) ?:
|
||||
return bch2_trans_update_seq(trans, wb->journal_seq, iter, &wb->k, 0) ?:
|
||||
bch2_trans_commit(trans, NULL, NULL,
|
||||
commit_flags|
|
||||
BTREE_INSERT_NOCHECK_RW|
|
||||
@ -103,6 +103,32 @@ static union btree_write_buffer_state btree_write_buffer_switch(struct btree_wri
|
||||
return old;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update a btree with a write buffered key using the journal seq of the
|
||||
* original write buffer insert.
|
||||
*
|
||||
* It is not safe to rejournal the key once it has been inserted into the write
|
||||
* buffer because that may break recovery ordering. For example, the key may
|
||||
* have already been modified in the active write buffer in a seq that comes
|
||||
* before the current transaction. If we were to journal this key again and
|
||||
* crash, recovery would process updates in the wrong order.
|
||||
*/
|
||||
static int
|
||||
btree_write_buffered_insert(struct btree_trans *trans,
|
||||
struct btree_write_buffered_key *wb)
|
||||
{
|
||||
struct btree_iter iter;
|
||||
int ret;
|
||||
|
||||
bch2_trans_iter_init(trans, &iter, wb->btree, bkey_start_pos(&wb->k.k),
|
||||
BTREE_ITER_CACHED|BTREE_ITER_INTENT);
|
||||
|
||||
ret = bch2_btree_iter_traverse(&iter) ?:
|
||||
bch2_trans_update_seq(trans, wb->journal_seq, &iter, &wb->k, 0);
|
||||
bch2_trans_iter_exit(trans, &iter);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __bch2_btree_write_buffer_flush(struct btree_trans *trans, unsigned commit_flags,
|
||||
bool locked)
|
||||
{
|
||||
@ -238,7 +264,7 @@ slowpath:
|
||||
commit_flags|
|
||||
BTREE_INSERT_NOFAIL|
|
||||
BTREE_INSERT_JOURNAL_RECLAIM,
|
||||
__bch2_btree_insert(trans, i->btree, &i->k, 0));
|
||||
btree_write_buffered_insert(trans, i));
|
||||
if (bch2_fs_fatal_err_on(ret, c, "%s: insert error %s", __func__, bch2_err_str(ret)))
|
||||
break;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user