79032b0781
Consolidate bch2_gc_check_topology() and btree_node_interior_verify(), and replace them with an improved version, bch2_btree_node_check_topology(). This checks that children of an interior node correctly span the full range of the parent node with no overlaps. Also, ensure that topology repairs at runtime are always a fatal error; in particular, this adds a check in btree_iter_down() - if we don't find a key while walking down the btree that's indicative of a topology error and should be flagged as such, not a null ptr deref. Some checks in btree_update_interior.c remaining BUG_ONS(), because we already checked the node for topology errors when starting the update, and the assertions indicate that we _just_ corrupted the btree node - i.e. the problem can't be that existing on disk corruption, they indicate an actual algorithmic bug. In the future, we'll be annotating the fsck errors list with which recovery pass corrects them; the open coded "run explicit recovery pass or fatal error" in bch2_btree_node_check_topology() will in the future be done for every fsck_err() call. Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
3317 lines
86 KiB
C
3317 lines
86 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include "bcachefs.h"
|
|
#include "bkey_methods.h"
|
|
#include "bkey_buf.h"
|
|
#include "btree_cache.h"
|
|
#include "btree_iter.h"
|
|
#include "btree_journal_iter.h"
|
|
#include "btree_key_cache.h"
|
|
#include "btree_locking.h"
|
|
#include "btree_update.h"
|
|
#include "debug.h"
|
|
#include "error.h"
|
|
#include "extents.h"
|
|
#include "journal.h"
|
|
#include "journal_io.h"
|
|
#include "replicas.h"
|
|
#include "snapshot.h"
|
|
#include "trace.h"
|
|
|
|
#include <linux/random.h>
|
|
#include <linux/prefetch.h>
|
|
|
|
static inline void btree_path_list_remove(struct btree_trans *, struct btree_path *);
|
|
static inline void btree_path_list_add(struct btree_trans *,
|
|
btree_path_idx_t, btree_path_idx_t);
|
|
|
|
static inline unsigned long btree_iter_ip_allocated(struct btree_iter *iter)
|
|
{
|
|
#ifdef TRACK_PATH_ALLOCATED
|
|
return iter->ip_allocated;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static btree_path_idx_t btree_path_alloc(struct btree_trans *, btree_path_idx_t);
|
|
static void bch2_trans_srcu_lock(struct btree_trans *);
|
|
|
|
static inline int __btree_path_cmp(const struct btree_path *l,
|
|
enum btree_id r_btree_id,
|
|
bool r_cached,
|
|
struct bpos r_pos,
|
|
unsigned r_level)
|
|
{
|
|
/*
|
|
* Must match lock ordering as defined by __bch2_btree_node_lock:
|
|
*/
|
|
return cmp_int(l->btree_id, r_btree_id) ?:
|
|
cmp_int((int) l->cached, (int) r_cached) ?:
|
|
bpos_cmp(l->pos, r_pos) ?:
|
|
-cmp_int(l->level, r_level);
|
|
}
|
|
|
|
static inline int btree_path_cmp(const struct btree_path *l,
|
|
const struct btree_path *r)
|
|
{
|
|
return __btree_path_cmp(l, r->btree_id, r->cached, r->pos, r->level);
|
|
}
|
|
|
|
static inline struct bpos bkey_successor(struct btree_iter *iter, struct bpos p)
|
|
{
|
|
/* Are we iterating over keys in all snapshots? */
|
|
if (iter->flags & BTREE_ITER_ALL_SNAPSHOTS) {
|
|
p = bpos_successor(p);
|
|
} else {
|
|
p = bpos_nosnap_successor(p);
|
|
p.snapshot = iter->snapshot;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
static inline struct bpos bkey_predecessor(struct btree_iter *iter, struct bpos p)
|
|
{
|
|
/* Are we iterating over keys in all snapshots? */
|
|
if (iter->flags & BTREE_ITER_ALL_SNAPSHOTS) {
|
|
p = bpos_predecessor(p);
|
|
} else {
|
|
p = bpos_nosnap_predecessor(p);
|
|
p.snapshot = iter->snapshot;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
static inline struct bpos btree_iter_search_key(struct btree_iter *iter)
|
|
{
|
|
struct bpos pos = iter->pos;
|
|
|
|
if ((iter->flags & BTREE_ITER_IS_EXTENTS) &&
|
|
!bkey_eq(pos, POS_MAX))
|
|
pos = bkey_successor(iter, pos);
|
|
return pos;
|
|
}
|
|
|
|
static inline bool btree_path_pos_before_node(struct btree_path *path,
|
|
struct btree *b)
|
|
{
|
|
return bpos_lt(path->pos, b->data->min_key);
|
|
}
|
|
|
|
static inline bool btree_path_pos_after_node(struct btree_path *path,
|
|
struct btree *b)
|
|
{
|
|
return bpos_gt(path->pos, b->key.k.p);
|
|
}
|
|
|
|
static inline bool btree_path_pos_in_node(struct btree_path *path,
|
|
struct btree *b)
|
|
{
|
|
return path->btree_id == b->c.btree_id &&
|
|
!btree_path_pos_before_node(path, b) &&
|
|
!btree_path_pos_after_node(path, b);
|
|
}
|
|
|
|
/* Btree iterator: */
|
|
|
|
#ifdef CONFIG_BCACHEFS_DEBUG
|
|
|
|
static void bch2_btree_path_verify_cached(struct btree_trans *trans,
|
|
struct btree_path *path)
|
|
{
|
|
struct bkey_cached *ck;
|
|
bool locked = btree_node_locked(path, 0);
|
|
|
|
if (!bch2_btree_node_relock(trans, path, 0))
|
|
return;
|
|
|
|
ck = (void *) path->l[0].b;
|
|
BUG_ON(ck->key.btree_id != path->btree_id ||
|
|
!bkey_eq(ck->key.pos, path->pos));
|
|
|
|
if (!locked)
|
|
btree_node_unlock(trans, path, 0);
|
|
}
|
|
|
|
static void bch2_btree_path_verify_level(struct btree_trans *trans,
|
|
struct btree_path *path, unsigned level)
|
|
{
|
|
struct btree_path_level *l;
|
|
struct btree_node_iter tmp;
|
|
bool locked;
|
|
struct bkey_packed *p, *k;
|
|
struct printbuf buf1 = PRINTBUF;
|
|
struct printbuf buf2 = PRINTBUF;
|
|
struct printbuf buf3 = PRINTBUF;
|
|
const char *msg;
|
|
|
|
if (!bch2_debug_check_iterators)
|
|
return;
|
|
|
|
l = &path->l[level];
|
|
tmp = l->iter;
|
|
locked = btree_node_locked(path, level);
|
|
|
|
if (path->cached) {
|
|
if (!level)
|
|
bch2_btree_path_verify_cached(trans, path);
|
|
return;
|
|
}
|
|
|
|
if (!btree_path_node(path, level))
|
|
return;
|
|
|
|
if (!bch2_btree_node_relock_notrace(trans, path, level))
|
|
return;
|
|
|
|
BUG_ON(!btree_path_pos_in_node(path, l->b));
|
|
|
|
bch2_btree_node_iter_verify(&l->iter, l->b);
|
|
|
|
/*
|
|
* For interior nodes, the iterator will have skipped past deleted keys:
|
|
*/
|
|
p = level
|
|
? bch2_btree_node_iter_prev(&tmp, l->b)
|
|
: bch2_btree_node_iter_prev_all(&tmp, l->b);
|
|
k = bch2_btree_node_iter_peek_all(&l->iter, l->b);
|
|
|
|
if (p && bkey_iter_pos_cmp(l->b, p, &path->pos) >= 0) {
|
|
msg = "before";
|
|
goto err;
|
|
}
|
|
|
|
if (k && bkey_iter_pos_cmp(l->b, k, &path->pos) < 0) {
|
|
msg = "after";
|
|
goto err;
|
|
}
|
|
|
|
if (!locked)
|
|
btree_node_unlock(trans, path, level);
|
|
return;
|
|
err:
|
|
bch2_bpos_to_text(&buf1, path->pos);
|
|
|
|
if (p) {
|
|
struct bkey uk = bkey_unpack_key(l->b, p);
|
|
|
|
bch2_bkey_to_text(&buf2, &uk);
|
|
} else {
|
|
prt_printf(&buf2, "(none)");
|
|
}
|
|
|
|
if (k) {
|
|
struct bkey uk = bkey_unpack_key(l->b, k);
|
|
|
|
bch2_bkey_to_text(&buf3, &uk);
|
|
} else {
|
|
prt_printf(&buf3, "(none)");
|
|
}
|
|
|
|
panic("path should be %s key at level %u:\n"
|
|
"path pos %s\n"
|
|
"prev key %s\n"
|
|
"cur key %s\n",
|
|
msg, level, buf1.buf, buf2.buf, buf3.buf);
|
|
}
|
|
|
|
static void bch2_btree_path_verify(struct btree_trans *trans,
|
|
struct btree_path *path)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
unsigned i;
|
|
|
|
EBUG_ON(path->btree_id >= BTREE_ID_NR);
|
|
|
|
for (i = 0; i < (!path->cached ? BTREE_MAX_DEPTH : 1); i++) {
|
|
if (!path->l[i].b) {
|
|
BUG_ON(!path->cached &&
|
|
bch2_btree_id_root(c, path->btree_id)->b->c.level > i);
|
|
break;
|
|
}
|
|
|
|
bch2_btree_path_verify_level(trans, path, i);
|
|
}
|
|
|
|
bch2_btree_path_verify_locks(path);
|
|
}
|
|
|
|
void bch2_trans_verify_paths(struct btree_trans *trans)
|
|
{
|
|
struct btree_path *path;
|
|
unsigned iter;
|
|
|
|
trans_for_each_path(trans, path, iter)
|
|
bch2_btree_path_verify(trans, path);
|
|
}
|
|
|
|
static void bch2_btree_iter_verify(struct btree_iter *iter)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
|
|
BUG_ON(iter->btree_id >= BTREE_ID_NR);
|
|
|
|
BUG_ON(!!(iter->flags & BTREE_ITER_CACHED) != btree_iter_path(trans, iter)->cached);
|
|
|
|
BUG_ON((iter->flags & BTREE_ITER_IS_EXTENTS) &&
|
|
(iter->flags & BTREE_ITER_ALL_SNAPSHOTS));
|
|
|
|
BUG_ON(!(iter->flags & __BTREE_ITER_ALL_SNAPSHOTS) &&
|
|
(iter->flags & BTREE_ITER_ALL_SNAPSHOTS) &&
|
|
!btree_type_has_snapshot_field(iter->btree_id));
|
|
|
|
if (iter->update_path)
|
|
bch2_btree_path_verify(trans, &trans->paths[iter->update_path]);
|
|
bch2_btree_path_verify(trans, btree_iter_path(trans, iter));
|
|
}
|
|
|
|
static void bch2_btree_iter_verify_entry_exit(struct btree_iter *iter)
|
|
{
|
|
BUG_ON((iter->flags & BTREE_ITER_FILTER_SNAPSHOTS) &&
|
|
!iter->pos.snapshot);
|
|
|
|
BUG_ON(!(iter->flags & BTREE_ITER_ALL_SNAPSHOTS) &&
|
|
iter->pos.snapshot != iter->snapshot);
|
|
|
|
BUG_ON(bkey_lt(iter->pos, bkey_start_pos(&iter->k)) ||
|
|
bkey_gt(iter->pos, iter->k.p));
|
|
}
|
|
|
|
static int bch2_btree_iter_verify_ret(struct btree_iter *iter, struct bkey_s_c k)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct btree_iter copy;
|
|
struct bkey_s_c prev;
|
|
int ret = 0;
|
|
|
|
if (!bch2_debug_check_iterators)
|
|
return 0;
|
|
|
|
if (!(iter->flags & BTREE_ITER_FILTER_SNAPSHOTS))
|
|
return 0;
|
|
|
|
if (bkey_err(k) || !k.k)
|
|
return 0;
|
|
|
|
BUG_ON(!bch2_snapshot_is_ancestor(trans->c,
|
|
iter->snapshot,
|
|
k.k->p.snapshot));
|
|
|
|
bch2_trans_iter_init(trans, ©, iter->btree_id, iter->pos,
|
|
BTREE_ITER_NOPRESERVE|
|
|
BTREE_ITER_ALL_SNAPSHOTS);
|
|
prev = bch2_btree_iter_prev(©);
|
|
if (!prev.k)
|
|
goto out;
|
|
|
|
ret = bkey_err(prev);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (bkey_eq(prev.k->p, k.k->p) &&
|
|
bch2_snapshot_is_ancestor(trans->c, iter->snapshot,
|
|
prev.k->p.snapshot) > 0) {
|
|
struct printbuf buf1 = PRINTBUF, buf2 = PRINTBUF;
|
|
|
|
bch2_bkey_to_text(&buf1, k.k);
|
|
bch2_bkey_to_text(&buf2, prev.k);
|
|
|
|
panic("iter snap %u\n"
|
|
"k %s\n"
|
|
"prev %s\n",
|
|
iter->snapshot,
|
|
buf1.buf, buf2.buf);
|
|
}
|
|
out:
|
|
bch2_trans_iter_exit(trans, ©);
|
|
return ret;
|
|
}
|
|
|
|
void bch2_assert_pos_locked(struct btree_trans *trans, enum btree_id id,
|
|
struct bpos pos, bool key_cache)
|
|
{
|
|
struct btree_path *path;
|
|
struct trans_for_each_path_inorder_iter iter;
|
|
struct printbuf buf = PRINTBUF;
|
|
|
|
btree_trans_sort_paths(trans);
|
|
|
|
trans_for_each_path_inorder(trans, path, iter) {
|
|
int cmp = cmp_int(path->btree_id, id) ?:
|
|
cmp_int(path->cached, key_cache);
|
|
|
|
if (cmp > 0)
|
|
break;
|
|
if (cmp < 0)
|
|
continue;
|
|
|
|
if (!btree_node_locked(path, 0) ||
|
|
!path->should_be_locked)
|
|
continue;
|
|
|
|
if (!key_cache) {
|
|
if (bkey_ge(pos, path->l[0].b->data->min_key) &&
|
|
bkey_le(pos, path->l[0].b->key.k.p))
|
|
return;
|
|
} else {
|
|
if (bkey_eq(pos, path->pos))
|
|
return;
|
|
}
|
|
}
|
|
|
|
bch2_dump_trans_paths_updates(trans);
|
|
bch2_bpos_to_text(&buf, pos);
|
|
|
|
panic("not locked: %s %s%s\n",
|
|
bch2_btree_id_str(id), buf.buf,
|
|
key_cache ? " cached" : "");
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void bch2_btree_path_verify_level(struct btree_trans *trans,
|
|
struct btree_path *path, unsigned l) {}
|
|
static inline void bch2_btree_path_verify(struct btree_trans *trans,
|
|
struct btree_path *path) {}
|
|
static inline void bch2_btree_iter_verify(struct btree_iter *iter) {}
|
|
static inline void bch2_btree_iter_verify_entry_exit(struct btree_iter *iter) {}
|
|
static inline int bch2_btree_iter_verify_ret(struct btree_iter *iter, struct bkey_s_c k) { return 0; }
|
|
|
|
#endif
|
|
|
|
/* Btree path: fixups after btree updates */
|
|
|
|
static void btree_node_iter_set_set_pos(struct btree_node_iter *iter,
|
|
struct btree *b,
|
|
struct bset_tree *t,
|
|
struct bkey_packed *k)
|
|
{
|
|
struct btree_node_iter_set *set;
|
|
|
|
btree_node_iter_for_each(iter, set)
|
|
if (set->end == t->end_offset) {
|
|
set->k = __btree_node_key_to_offset(b, k);
|
|
bch2_btree_node_iter_sort(iter, b);
|
|
return;
|
|
}
|
|
|
|
bch2_btree_node_iter_push(iter, b, k, btree_bkey_last(b, t));
|
|
}
|
|
|
|
static void __bch2_btree_path_fix_key_modified(struct btree_path *path,
|
|
struct btree *b,
|
|
struct bkey_packed *where)
|
|
{
|
|
struct btree_path_level *l = &path->l[b->c.level];
|
|
|
|
if (where != bch2_btree_node_iter_peek_all(&l->iter, l->b))
|
|
return;
|
|
|
|
if (bkey_iter_pos_cmp(l->b, where, &path->pos) < 0)
|
|
bch2_btree_node_iter_advance(&l->iter, l->b);
|
|
}
|
|
|
|
void bch2_btree_path_fix_key_modified(struct btree_trans *trans,
|
|
struct btree *b,
|
|
struct bkey_packed *where)
|
|
{
|
|
struct btree_path *path;
|
|
unsigned i;
|
|
|
|
trans_for_each_path_with_node(trans, b, path, i) {
|
|
__bch2_btree_path_fix_key_modified(path, b, where);
|
|
bch2_btree_path_verify_level(trans, path, b->c.level);
|
|
}
|
|
}
|
|
|
|
static void __bch2_btree_node_iter_fix(struct btree_path *path,
|
|
struct btree *b,
|
|
struct btree_node_iter *node_iter,
|
|
struct bset_tree *t,
|
|
struct bkey_packed *where,
|
|
unsigned clobber_u64s,
|
|
unsigned new_u64s)
|
|
{
|
|
const struct bkey_packed *end = btree_bkey_last(b, t);
|
|
struct btree_node_iter_set *set;
|
|
unsigned offset = __btree_node_key_to_offset(b, where);
|
|
int shift = new_u64s - clobber_u64s;
|
|
unsigned old_end = t->end_offset - shift;
|
|
unsigned orig_iter_pos = node_iter->data[0].k;
|
|
bool iter_current_key_modified =
|
|
orig_iter_pos >= offset &&
|
|
orig_iter_pos <= offset + clobber_u64s;
|
|
|
|
btree_node_iter_for_each(node_iter, set)
|
|
if (set->end == old_end)
|
|
goto found;
|
|
|
|
/* didn't find the bset in the iterator - might have to readd it: */
|
|
if (new_u64s &&
|
|
bkey_iter_pos_cmp(b, where, &path->pos) >= 0) {
|
|
bch2_btree_node_iter_push(node_iter, b, where, end);
|
|
goto fixup_done;
|
|
} else {
|
|
/* Iterator is after key that changed */
|
|
return;
|
|
}
|
|
found:
|
|
set->end = t->end_offset;
|
|
|
|
/* Iterator hasn't gotten to the key that changed yet: */
|
|
if (set->k < offset)
|
|
return;
|
|
|
|
if (new_u64s &&
|
|
bkey_iter_pos_cmp(b, where, &path->pos) >= 0) {
|
|
set->k = offset;
|
|
} else if (set->k < offset + clobber_u64s) {
|
|
set->k = offset + new_u64s;
|
|
if (set->k == set->end)
|
|
bch2_btree_node_iter_set_drop(node_iter, set);
|
|
} else {
|
|
/* Iterator is after key that changed */
|
|
set->k = (int) set->k + shift;
|
|
return;
|
|
}
|
|
|
|
bch2_btree_node_iter_sort(node_iter, b);
|
|
fixup_done:
|
|
if (node_iter->data[0].k != orig_iter_pos)
|
|
iter_current_key_modified = true;
|
|
|
|
/*
|
|
* When a new key is added, and the node iterator now points to that
|
|
* key, the iterator might have skipped past deleted keys that should
|
|
* come after the key the iterator now points to. We have to rewind to
|
|
* before those deleted keys - otherwise
|
|
* bch2_btree_node_iter_prev_all() breaks:
|
|
*/
|
|
if (!bch2_btree_node_iter_end(node_iter) &&
|
|
iter_current_key_modified &&
|
|
b->c.level) {
|
|
struct bkey_packed *k, *k2, *p;
|
|
|
|
k = bch2_btree_node_iter_peek_all(node_iter, b);
|
|
|
|
for_each_bset(b, t) {
|
|
bool set_pos = false;
|
|
|
|
if (node_iter->data[0].end == t->end_offset)
|
|
continue;
|
|
|
|
k2 = bch2_btree_node_iter_bset_pos(node_iter, b, t);
|
|
|
|
while ((p = bch2_bkey_prev_all(b, t, k2)) &&
|
|
bkey_iter_cmp(b, k, p) < 0) {
|
|
k2 = p;
|
|
set_pos = true;
|
|
}
|
|
|
|
if (set_pos)
|
|
btree_node_iter_set_set_pos(node_iter,
|
|
b, t, k2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void bch2_btree_node_iter_fix(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
struct btree *b,
|
|
struct btree_node_iter *node_iter,
|
|
struct bkey_packed *where,
|
|
unsigned clobber_u64s,
|
|
unsigned new_u64s)
|
|
{
|
|
struct bset_tree *t = bch2_bkey_to_bset_inlined(b, where);
|
|
struct btree_path *linked;
|
|
unsigned i;
|
|
|
|
if (node_iter != &path->l[b->c.level].iter) {
|
|
__bch2_btree_node_iter_fix(path, b, node_iter, t,
|
|
where, clobber_u64s, new_u64s);
|
|
|
|
if (bch2_debug_check_iterators)
|
|
bch2_btree_node_iter_verify(node_iter, b);
|
|
}
|
|
|
|
trans_for_each_path_with_node(trans, b, linked, i) {
|
|
__bch2_btree_node_iter_fix(linked, b,
|
|
&linked->l[b->c.level].iter, t,
|
|
where, clobber_u64s, new_u64s);
|
|
bch2_btree_path_verify_level(trans, linked, b->c.level);
|
|
}
|
|
}
|
|
|
|
/* Btree path level: pointer to a particular btree node and node iter */
|
|
|
|
static inline struct bkey_s_c __btree_iter_unpack(struct bch_fs *c,
|
|
struct btree_path_level *l,
|
|
struct bkey *u,
|
|
struct bkey_packed *k)
|
|
{
|
|
if (unlikely(!k)) {
|
|
/*
|
|
* signal to bch2_btree_iter_peek_slot() that we're currently at
|
|
* a hole
|
|
*/
|
|
u->type = KEY_TYPE_deleted;
|
|
return bkey_s_c_null;
|
|
}
|
|
|
|
return bkey_disassemble(l->b, k, u);
|
|
}
|
|
|
|
static inline struct bkey_s_c btree_path_level_peek_all(struct bch_fs *c,
|
|
struct btree_path_level *l,
|
|
struct bkey *u)
|
|
{
|
|
return __btree_iter_unpack(c, l, u,
|
|
bch2_btree_node_iter_peek_all(&l->iter, l->b));
|
|
}
|
|
|
|
static inline struct bkey_s_c btree_path_level_peek(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
struct btree_path_level *l,
|
|
struct bkey *u)
|
|
{
|
|
struct bkey_s_c k = __btree_iter_unpack(trans->c, l, u,
|
|
bch2_btree_node_iter_peek(&l->iter, l->b));
|
|
|
|
path->pos = k.k ? k.k->p : l->b->key.k.p;
|
|
trans->paths_sorted = false;
|
|
bch2_btree_path_verify_level(trans, path, l - path->l);
|
|
return k;
|
|
}
|
|
|
|
static inline struct bkey_s_c btree_path_level_prev(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
struct btree_path_level *l,
|
|
struct bkey *u)
|
|
{
|
|
struct bkey_s_c k = __btree_iter_unpack(trans->c, l, u,
|
|
bch2_btree_node_iter_prev(&l->iter, l->b));
|
|
|
|
path->pos = k.k ? k.k->p : l->b->data->min_key;
|
|
trans->paths_sorted = false;
|
|
bch2_btree_path_verify_level(trans, path, l - path->l);
|
|
return k;
|
|
}
|
|
|
|
static inline bool btree_path_advance_to_pos(struct btree_path *path,
|
|
struct btree_path_level *l,
|
|
int max_advance)
|
|
{
|
|
struct bkey_packed *k;
|
|
int nr_advanced = 0;
|
|
|
|
while ((k = bch2_btree_node_iter_peek_all(&l->iter, l->b)) &&
|
|
bkey_iter_pos_cmp(l->b, k, &path->pos) < 0) {
|
|
if (max_advance > 0 && nr_advanced >= max_advance)
|
|
return false;
|
|
|
|
bch2_btree_node_iter_advance(&l->iter, l->b);
|
|
nr_advanced++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void __btree_path_level_init(struct btree_path *path,
|
|
unsigned level)
|
|
{
|
|
struct btree_path_level *l = &path->l[level];
|
|
|
|
bch2_btree_node_iter_init(&l->iter, l->b, &path->pos);
|
|
|
|
/*
|
|
* Iterators to interior nodes should always be pointed at the first non
|
|
* whiteout:
|
|
*/
|
|
if (level)
|
|
bch2_btree_node_iter_peek(&l->iter, l->b);
|
|
}
|
|
|
|
void bch2_btree_path_level_init(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
struct btree *b)
|
|
{
|
|
BUG_ON(path->cached);
|
|
|
|
EBUG_ON(!btree_path_pos_in_node(path, b));
|
|
|
|
path->l[b->c.level].lock_seq = six_lock_seq(&b->c.lock);
|
|
path->l[b->c.level].b = b;
|
|
__btree_path_level_init(path, b->c.level);
|
|
}
|
|
|
|
/* Btree path: fixups after btree node updates: */
|
|
|
|
static void bch2_trans_revalidate_updates_in_node(struct btree_trans *trans, struct btree *b)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
|
|
trans_for_each_update(trans, i)
|
|
if (!i->cached &&
|
|
i->level == b->c.level &&
|
|
i->btree_id == b->c.btree_id &&
|
|
bpos_cmp(i->k->k.p, b->data->min_key) >= 0 &&
|
|
bpos_cmp(i->k->k.p, b->data->max_key) <= 0) {
|
|
i->old_v = bch2_btree_path_peek_slot(trans->paths + i->path, &i->old_k).v;
|
|
|
|
if (unlikely(trans->journal_replay_not_finished)) {
|
|
struct bkey_i *j_k =
|
|
bch2_journal_keys_peek_slot(c, i->btree_id, i->level,
|
|
i->k->k.p);
|
|
|
|
if (j_k) {
|
|
i->old_k = j_k->k;
|
|
i->old_v = &j_k->v;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A btree node is being replaced - update the iterator to point to the new
|
|
* node:
|
|
*/
|
|
void bch2_trans_node_add(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
struct btree *b)
|
|
{
|
|
struct btree_path *prev;
|
|
|
|
BUG_ON(!btree_path_pos_in_node(path, b));
|
|
|
|
while ((prev = prev_btree_path(trans, path)) &&
|
|
btree_path_pos_in_node(prev, b))
|
|
path = prev;
|
|
|
|
for (;
|
|
path && btree_path_pos_in_node(path, b);
|
|
path = next_btree_path(trans, path))
|
|
if (path->uptodate == BTREE_ITER_UPTODATE && !path->cached) {
|
|
enum btree_node_locked_type t =
|
|
btree_lock_want(path, b->c.level);
|
|
|
|
if (t != BTREE_NODE_UNLOCKED) {
|
|
btree_node_unlock(trans, path, b->c.level);
|
|
six_lock_increment(&b->c.lock, (enum six_lock_type) t);
|
|
mark_btree_node_locked(trans, path, b->c.level, t);
|
|
}
|
|
|
|
bch2_btree_path_level_init(trans, path, b);
|
|
}
|
|
|
|
bch2_trans_revalidate_updates_in_node(trans, b);
|
|
}
|
|
|
|
/*
|
|
* A btree node has been modified in such a way as to invalidate iterators - fix
|
|
* them:
|
|
*/
|
|
void bch2_trans_node_reinit_iter(struct btree_trans *trans, struct btree *b)
|
|
{
|
|
struct btree_path *path;
|
|
unsigned i;
|
|
|
|
trans_for_each_path_with_node(trans, b, path, i)
|
|
__btree_path_level_init(path, b->c.level);
|
|
|
|
bch2_trans_revalidate_updates_in_node(trans, b);
|
|
}
|
|
|
|
/* Btree path: traverse, set_pos: */
|
|
|
|
static inline int btree_path_lock_root(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
unsigned depth_want,
|
|
unsigned long trace_ip)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct btree *b, **rootp = &bch2_btree_id_root(c, path->btree_id)->b;
|
|
enum six_lock_type lock_type;
|
|
unsigned i;
|
|
int ret;
|
|
|
|
EBUG_ON(path->nodes_locked);
|
|
|
|
while (1) {
|
|
b = READ_ONCE(*rootp);
|
|
path->level = READ_ONCE(b->c.level);
|
|
|
|
if (unlikely(path->level < depth_want)) {
|
|
/*
|
|
* the root is at a lower depth than the depth we want:
|
|
* got to the end of the btree, or we're walking nodes
|
|
* greater than some depth and there are no nodes >=
|
|
* that depth
|
|
*/
|
|
path->level = depth_want;
|
|
for (i = path->level; i < BTREE_MAX_DEPTH; i++)
|
|
path->l[i].b = NULL;
|
|
return 1;
|
|
}
|
|
|
|
lock_type = __btree_lock_want(path, path->level);
|
|
ret = btree_node_lock(trans, path, &b->c,
|
|
path->level, lock_type, trace_ip);
|
|
if (unlikely(ret)) {
|
|
if (bch2_err_matches(ret, BCH_ERR_lock_fail_root_changed))
|
|
continue;
|
|
if (bch2_err_matches(ret, BCH_ERR_transaction_restart))
|
|
return ret;
|
|
BUG();
|
|
}
|
|
|
|
if (likely(b == READ_ONCE(*rootp) &&
|
|
b->c.level == path->level &&
|
|
!race_fault())) {
|
|
for (i = 0; i < path->level; i++)
|
|
path->l[i].b = ERR_PTR(-BCH_ERR_no_btree_node_lock_root);
|
|
path->l[path->level].b = b;
|
|
for (i = path->level + 1; i < BTREE_MAX_DEPTH; i++)
|
|
path->l[i].b = NULL;
|
|
|
|
mark_btree_node_locked(trans, path, path->level,
|
|
(enum btree_node_locked_type) lock_type);
|
|
bch2_btree_path_level_init(trans, path, b);
|
|
return 0;
|
|
}
|
|
|
|
six_unlock_type(&b->c.lock, lock_type);
|
|
}
|
|
}
|
|
|
|
noinline
|
|
static int btree_path_prefetch(struct btree_trans *trans, struct btree_path *path)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct btree_path_level *l = path_l(path);
|
|
struct btree_node_iter node_iter = l->iter;
|
|
struct bkey_packed *k;
|
|
struct bkey_buf tmp;
|
|
unsigned nr = test_bit(BCH_FS_started, &c->flags)
|
|
? (path->level > 1 ? 0 : 2)
|
|
: (path->level > 1 ? 1 : 16);
|
|
bool was_locked = btree_node_locked(path, path->level);
|
|
int ret = 0;
|
|
|
|
bch2_bkey_buf_init(&tmp);
|
|
|
|
while (nr-- && !ret) {
|
|
if (!bch2_btree_node_relock(trans, path, path->level))
|
|
break;
|
|
|
|
bch2_btree_node_iter_advance(&node_iter, l->b);
|
|
k = bch2_btree_node_iter_peek(&node_iter, l->b);
|
|
if (!k)
|
|
break;
|
|
|
|
bch2_bkey_buf_unpack(&tmp, c, l->b, k);
|
|
ret = bch2_btree_node_prefetch(trans, path, tmp.k, path->btree_id,
|
|
path->level - 1);
|
|
}
|
|
|
|
if (!was_locked)
|
|
btree_node_unlock(trans, path, path->level);
|
|
|
|
bch2_bkey_buf_exit(&tmp, c);
|
|
return ret;
|
|
}
|
|
|
|
static int btree_path_prefetch_j(struct btree_trans *trans, struct btree_path *path,
|
|
struct btree_and_journal_iter *jiter)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct bkey_s_c k;
|
|
struct bkey_buf tmp;
|
|
unsigned nr = test_bit(BCH_FS_started, &c->flags)
|
|
? (path->level > 1 ? 0 : 2)
|
|
: (path->level > 1 ? 1 : 16);
|
|
bool was_locked = btree_node_locked(path, path->level);
|
|
int ret = 0;
|
|
|
|
bch2_bkey_buf_init(&tmp);
|
|
|
|
while (nr-- && !ret) {
|
|
if (!bch2_btree_node_relock(trans, path, path->level))
|
|
break;
|
|
|
|
bch2_btree_and_journal_iter_advance(jiter);
|
|
k = bch2_btree_and_journal_iter_peek(jiter);
|
|
if (!k.k)
|
|
break;
|
|
|
|
bch2_bkey_buf_reassemble(&tmp, c, k);
|
|
ret = bch2_btree_node_prefetch(trans, path, tmp.k, path->btree_id,
|
|
path->level - 1);
|
|
}
|
|
|
|
if (!was_locked)
|
|
btree_node_unlock(trans, path, path->level);
|
|
|
|
bch2_bkey_buf_exit(&tmp, c);
|
|
return ret;
|
|
}
|
|
|
|
static noinline void btree_node_mem_ptr_set(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
unsigned plevel, struct btree *b)
|
|
{
|
|
struct btree_path_level *l = &path->l[plevel];
|
|
bool locked = btree_node_locked(path, plevel);
|
|
struct bkey_packed *k;
|
|
struct bch_btree_ptr_v2 *bp;
|
|
|
|
if (!bch2_btree_node_relock(trans, path, plevel))
|
|
return;
|
|
|
|
k = bch2_btree_node_iter_peek_all(&l->iter, l->b);
|
|
BUG_ON(k->type != KEY_TYPE_btree_ptr_v2);
|
|
|
|
bp = (void *) bkeyp_val(&l->b->format, k);
|
|
bp->mem_ptr = (unsigned long)b;
|
|
|
|
if (!locked)
|
|
btree_node_unlock(trans, path, plevel);
|
|
}
|
|
|
|
static noinline int btree_node_iter_and_journal_peek(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
unsigned flags,
|
|
struct bkey_buf *out)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct btree_path_level *l = path_l(path);
|
|
struct btree_and_journal_iter jiter;
|
|
struct bkey_s_c k;
|
|
int ret = 0;
|
|
|
|
__bch2_btree_and_journal_iter_init_node_iter(trans, &jiter, l->b, l->iter, path->pos);
|
|
|
|
k = bch2_btree_and_journal_iter_peek(&jiter);
|
|
|
|
bch2_bkey_buf_reassemble(out, c, k);
|
|
|
|
if ((flags & BTREE_ITER_PREFETCH) &&
|
|
c->opts.btree_node_prefetch)
|
|
ret = btree_path_prefetch_j(trans, path, &jiter);
|
|
|
|
bch2_btree_and_journal_iter_exit(&jiter);
|
|
return ret;
|
|
}
|
|
|
|
static __always_inline int btree_path_down(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
unsigned flags,
|
|
unsigned long trace_ip)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct btree_path_level *l = path_l(path);
|
|
struct btree *b;
|
|
unsigned level = path->level - 1;
|
|
enum six_lock_type lock_type = __btree_lock_want(path, level);
|
|
struct bkey_buf tmp;
|
|
int ret;
|
|
|
|
EBUG_ON(!btree_node_locked(path, path->level));
|
|
|
|
bch2_bkey_buf_init(&tmp);
|
|
|
|
if (unlikely(trans->journal_replay_not_finished)) {
|
|
ret = btree_node_iter_and_journal_peek(trans, path, flags, &tmp);
|
|
if (ret)
|
|
goto err;
|
|
} else {
|
|
struct bkey_packed *k = bch2_btree_node_iter_peek(&l->iter, l->b);
|
|
if (!k) {
|
|
struct printbuf buf = PRINTBUF;
|
|
|
|
prt_str(&buf, "node not found at pos ");
|
|
bch2_bpos_to_text(&buf, path->pos);
|
|
prt_str(&buf, " within parent node ");
|
|
bch2_bkey_val_to_text(&buf, c, bkey_i_to_s_c(&l->b->key));
|
|
|
|
bch2_fs_fatal_error(c, "%s", buf.buf);
|
|
printbuf_exit(&buf);
|
|
ret = -BCH_ERR_btree_need_topology_repair;
|
|
goto err;
|
|
}
|
|
|
|
bch2_bkey_buf_unpack(&tmp, c, l->b, k);
|
|
|
|
if ((flags & BTREE_ITER_PREFETCH) &&
|
|
c->opts.btree_node_prefetch) {
|
|
ret = btree_path_prefetch(trans, path);
|
|
if (ret)
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
b = bch2_btree_node_get(trans, path, tmp.k, level, lock_type, trace_ip);
|
|
ret = PTR_ERR_OR_ZERO(b);
|
|
if (unlikely(ret))
|
|
goto err;
|
|
|
|
if (likely(!trans->journal_replay_not_finished &&
|
|
tmp.k->k.type == KEY_TYPE_btree_ptr_v2) &&
|
|
unlikely(b != btree_node_mem_ptr(tmp.k)))
|
|
btree_node_mem_ptr_set(trans, path, level + 1, b);
|
|
|
|
if (btree_node_read_locked(path, level + 1))
|
|
btree_node_unlock(trans, path, level + 1);
|
|
|
|
mark_btree_node_locked(trans, path, level,
|
|
(enum btree_node_locked_type) lock_type);
|
|
path->level = level;
|
|
bch2_btree_path_level_init(trans, path, b);
|
|
|
|
bch2_btree_path_verify_locks(path);
|
|
err:
|
|
bch2_bkey_buf_exit(&tmp, c);
|
|
return ret;
|
|
}
|
|
|
|
static int bch2_btree_path_traverse_all(struct btree_trans *trans)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
struct btree_path *path;
|
|
unsigned long trace_ip = _RET_IP_;
|
|
unsigned i;
|
|
int ret = 0;
|
|
|
|
if (trans->in_traverse_all)
|
|
return -BCH_ERR_transaction_restart_in_traverse_all;
|
|
|
|
trans->in_traverse_all = true;
|
|
retry_all:
|
|
trans->restarted = 0;
|
|
trans->last_restarted_ip = 0;
|
|
|
|
trans_for_each_path(trans, path, i)
|
|
path->should_be_locked = false;
|
|
|
|
btree_trans_sort_paths(trans);
|
|
|
|
bch2_trans_unlock(trans);
|
|
cond_resched();
|
|
|
|
if (unlikely(trans->memory_allocation_failure)) {
|
|
struct closure cl;
|
|
|
|
closure_init_stack(&cl);
|
|
|
|
do {
|
|
ret = bch2_btree_cache_cannibalize_lock(trans, &cl);
|
|
closure_sync(&cl);
|
|
} while (ret);
|
|
}
|
|
|
|
/* Now, redo traversals in correct order: */
|
|
i = 0;
|
|
while (i < trans->nr_sorted) {
|
|
btree_path_idx_t idx = trans->sorted[i];
|
|
|
|
/*
|
|
* Traversing a path can cause another path to be added at about
|
|
* the same position:
|
|
*/
|
|
if (trans->paths[idx].uptodate) {
|
|
__btree_path_get(&trans->paths[idx], false);
|
|
ret = bch2_btree_path_traverse_one(trans, idx, 0, _THIS_IP_);
|
|
__btree_path_put(&trans->paths[idx], false);
|
|
|
|
if (bch2_err_matches(ret, BCH_ERR_transaction_restart) ||
|
|
bch2_err_matches(ret, ENOMEM))
|
|
goto retry_all;
|
|
if (ret)
|
|
goto err;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We used to assert that all paths had been traversed here
|
|
* (path->uptodate < BTREE_ITER_NEED_TRAVERSE); however, since
|
|
* path->should_be_locked is not set yet, we might have unlocked and
|
|
* then failed to relock a path - that's fine.
|
|
*/
|
|
err:
|
|
bch2_btree_cache_cannibalize_unlock(trans);
|
|
|
|
trans->in_traverse_all = false;
|
|
|
|
trace_and_count(c, trans_traverse_all, trans, trace_ip);
|
|
return ret;
|
|
}
|
|
|
|
static inline bool btree_path_check_pos_in_node(struct btree_path *path,
|
|
unsigned l, int check_pos)
|
|
{
|
|
if (check_pos < 0 && btree_path_pos_before_node(path, path->l[l].b))
|
|
return false;
|
|
if (check_pos > 0 && btree_path_pos_after_node(path, path->l[l].b))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static inline bool btree_path_good_node(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
unsigned l, int check_pos)
|
|
{
|
|
return is_btree_node(path, l) &&
|
|
bch2_btree_node_relock(trans, path, l) &&
|
|
btree_path_check_pos_in_node(path, l, check_pos);
|
|
}
|
|
|
|
static void btree_path_set_level_down(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
unsigned new_level)
|
|
{
|
|
unsigned l;
|
|
|
|
path->level = new_level;
|
|
|
|
for (l = path->level + 1; l < BTREE_MAX_DEPTH; l++)
|
|
if (btree_lock_want(path, l) == BTREE_NODE_UNLOCKED)
|
|
btree_node_unlock(trans, path, l);
|
|
|
|
btree_path_set_dirty(path, BTREE_ITER_NEED_TRAVERSE);
|
|
bch2_btree_path_verify(trans, path);
|
|
}
|
|
|
|
static noinline unsigned __btree_path_up_until_good_node(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
int check_pos)
|
|
{
|
|
unsigned i, l = path->level;
|
|
again:
|
|
while (btree_path_node(path, l) &&
|
|
!btree_path_good_node(trans, path, l, check_pos))
|
|
__btree_path_set_level_up(trans, path, l++);
|
|
|
|
/* If we need intent locks, take them too: */
|
|
for (i = l + 1;
|
|
i < path->locks_want && btree_path_node(path, i);
|
|
i++)
|
|
if (!bch2_btree_node_relock(trans, path, i)) {
|
|
while (l <= i)
|
|
__btree_path_set_level_up(trans, path, l++);
|
|
goto again;
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
static inline unsigned btree_path_up_until_good_node(struct btree_trans *trans,
|
|
struct btree_path *path,
|
|
int check_pos)
|
|
{
|
|
return likely(btree_node_locked(path, path->level) &&
|
|
btree_path_check_pos_in_node(path, path->level, check_pos))
|
|
? path->level
|
|
: __btree_path_up_until_good_node(trans, path, check_pos);
|
|
}
|
|
|
|
/*
|
|
* This is the main state machine for walking down the btree - walks down to a
|
|
* specified depth
|
|
*
|
|
* Returns 0 on success, -EIO on error (error reading in a btree node).
|
|
*
|
|
* On error, caller (peek_node()/peek_key()) must return NULL; the error is
|
|
* stashed in the iterator and returned from bch2_trans_exit().
|
|
*/
|
|
int bch2_btree_path_traverse_one(struct btree_trans *trans,
|
|
btree_path_idx_t path_idx,
|
|
unsigned flags,
|
|
unsigned long trace_ip)
|
|
{
|
|
struct btree_path *path = &trans->paths[path_idx];
|
|
unsigned depth_want = path->level;
|
|
int ret = -((int) trans->restarted);
|
|
|
|
if (unlikely(ret))
|
|
goto out;
|
|
|
|
if (unlikely(!trans->srcu_held))
|
|
bch2_trans_srcu_lock(trans);
|
|
|
|
/*
|
|
* Ensure we obey path->should_be_locked: if it's set, we can't unlock
|
|
* and re-traverse the path without a transaction restart:
|
|
*/
|
|
if (path->should_be_locked) {
|
|
ret = bch2_btree_path_relock(trans, path, trace_ip);
|
|
goto out;
|
|
}
|
|
|
|
if (path->cached) {
|
|
ret = bch2_btree_path_traverse_cached(trans, path, flags);
|
|
goto out;
|
|
}
|
|
|
|
path = &trans->paths[path_idx];
|
|
|
|
if (unlikely(path->level >= BTREE_MAX_DEPTH))
|
|
goto out_uptodate;
|
|
|
|
path->level = btree_path_up_until_good_node(trans, path, 0);
|
|
|
|
EBUG_ON(btree_path_node(path, path->level) &&
|
|
!btree_node_locked(path, path->level));
|
|
|
|
/*
|
|
* Note: path->nodes[path->level] may be temporarily NULL here - that
|
|
* would indicate to other code that we got to the end of the btree,
|
|
* here it indicates that relocking the root failed - it's critical that
|
|
* btree_path_lock_root() comes next and that it can't fail
|
|
*/
|
|
while (path->level > depth_want) {
|
|
ret = btree_path_node(path, path->level)
|
|
? btree_path_down(trans, path, flags, trace_ip)
|
|
: btree_path_lock_root(trans, path, depth_want, trace_ip);
|
|
if (unlikely(ret)) {
|
|
if (ret == 1) {
|
|
/*
|
|
* No nodes at this level - got to the end of
|
|
* the btree:
|
|
*/
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
__bch2_btree_path_unlock(trans, path);
|
|
path->level = depth_want;
|
|
path->l[path->level].b = ERR_PTR(ret);
|
|
goto out;
|
|
}
|
|
}
|
|
out_uptodate:
|
|
path->uptodate = BTREE_ITER_UPTODATE;
|
|
out:
|
|
if (bch2_err_matches(ret, BCH_ERR_transaction_restart) != !!trans->restarted)
|
|
panic("ret %s (%i) trans->restarted %s (%i)\n",
|
|
bch2_err_str(ret), ret,
|
|
bch2_err_str(trans->restarted), trans->restarted);
|
|
bch2_btree_path_verify(trans, path);
|
|
return ret;
|
|
}
|
|
|
|
static inline void btree_path_copy(struct btree_trans *trans, struct btree_path *dst,
|
|
struct btree_path *src)
|
|
{
|
|
unsigned i, offset = offsetof(struct btree_path, pos);
|
|
|
|
memcpy((void *) dst + offset,
|
|
(void *) src + offset,
|
|
sizeof(struct btree_path) - offset);
|
|
|
|
for (i = 0; i < BTREE_MAX_DEPTH; i++) {
|
|
unsigned t = btree_node_locked_type(dst, i);
|
|
|
|
if (t != BTREE_NODE_UNLOCKED)
|
|
six_lock_increment(&dst->l[i].b->c.lock, t);
|
|
}
|
|
}
|
|
|
|
static btree_path_idx_t btree_path_clone(struct btree_trans *trans, btree_path_idx_t src,
|
|
bool intent)
|
|
{
|
|
btree_path_idx_t new = btree_path_alloc(trans, src);
|
|
btree_path_copy(trans, trans->paths + new, trans->paths + src);
|
|
__btree_path_get(trans->paths + new, intent);
|
|
return new;
|
|
}
|
|
|
|
__flatten
|
|
btree_path_idx_t __bch2_btree_path_make_mut(struct btree_trans *trans,
|
|
btree_path_idx_t path, bool intent, unsigned long ip)
|
|
{
|
|
__btree_path_put(trans->paths + path, intent);
|
|
path = btree_path_clone(trans, path, intent);
|
|
trans->paths[path].preserve = false;
|
|
return path;
|
|
}
|
|
|
|
btree_path_idx_t __must_check
|
|
__bch2_btree_path_set_pos(struct btree_trans *trans,
|
|
btree_path_idx_t path_idx, struct bpos new_pos,
|
|
bool intent, unsigned long ip)
|
|
{
|
|
int cmp = bpos_cmp(new_pos, trans->paths[path_idx].pos);
|
|
|
|
bch2_trans_verify_not_in_restart(trans);
|
|
EBUG_ON(!trans->paths[path_idx].ref);
|
|
|
|
path_idx = bch2_btree_path_make_mut(trans, path_idx, intent, ip);
|
|
|
|
struct btree_path *path = trans->paths + path_idx;
|
|
path->pos = new_pos;
|
|
trans->paths_sorted = false;
|
|
|
|
if (unlikely(path->cached)) {
|
|
btree_node_unlock(trans, path, 0);
|
|
path->l[0].b = ERR_PTR(-BCH_ERR_no_btree_node_up);
|
|
btree_path_set_dirty(path, BTREE_ITER_NEED_TRAVERSE);
|
|
goto out;
|
|
}
|
|
|
|
unsigned level = btree_path_up_until_good_node(trans, path, cmp);
|
|
|
|
if (btree_path_node(path, level)) {
|
|
struct btree_path_level *l = &path->l[level];
|
|
|
|
BUG_ON(!btree_node_locked(path, level));
|
|
/*
|
|
* We might have to skip over many keys, or just a few: try
|
|
* advancing the node iterator, and if we have to skip over too
|
|
* many keys just reinit it (or if we're rewinding, since that
|
|
* is expensive).
|
|
*/
|
|
if (cmp < 0 ||
|
|
!btree_path_advance_to_pos(path, l, 8))
|
|
bch2_btree_node_iter_init(&l->iter, l->b, &path->pos);
|
|
|
|
/*
|
|
* Iterators to interior nodes should always be pointed at the first non
|
|
* whiteout:
|
|
*/
|
|
if (unlikely(level))
|
|
bch2_btree_node_iter_peek(&l->iter, l->b);
|
|
}
|
|
|
|
if (unlikely(level != path->level)) {
|
|
btree_path_set_dirty(path, BTREE_ITER_NEED_TRAVERSE);
|
|
__bch2_btree_path_unlock(trans, path);
|
|
}
|
|
out:
|
|
bch2_btree_path_verify(trans, path);
|
|
return path_idx;
|
|
}
|
|
|
|
/* Btree path: main interface: */
|
|
|
|
static struct btree_path *have_path_at_pos(struct btree_trans *trans, struct btree_path *path)
|
|
{
|
|
struct btree_path *sib;
|
|
|
|
sib = prev_btree_path(trans, path);
|
|
if (sib && !btree_path_cmp(sib, path))
|
|
return sib;
|
|
|
|
sib = next_btree_path(trans, path);
|
|
if (sib && !btree_path_cmp(sib, path))
|
|
return sib;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct btree_path *have_node_at_pos(struct btree_trans *trans, struct btree_path *path)
|
|
{
|
|
struct btree_path *sib;
|
|
|
|
sib = prev_btree_path(trans, path);
|
|
if (sib && sib->level == path->level && path_l(sib)->b == path_l(path)->b)
|
|
return sib;
|
|
|
|
sib = next_btree_path(trans, path);
|
|
if (sib && sib->level == path->level && path_l(sib)->b == path_l(path)->b)
|
|
return sib;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline void __bch2_path_free(struct btree_trans *trans, btree_path_idx_t path)
|
|
{
|
|
__bch2_btree_path_unlock(trans, trans->paths + path);
|
|
btree_path_list_remove(trans, trans->paths + path);
|
|
__clear_bit(path, trans->paths_allocated);
|
|
}
|
|
|
|
void bch2_path_put(struct btree_trans *trans, btree_path_idx_t path_idx, bool intent)
|
|
{
|
|
struct btree_path *path = trans->paths + path_idx, *dup;
|
|
|
|
if (!__btree_path_put(path, intent))
|
|
return;
|
|
|
|
dup = path->preserve
|
|
? have_path_at_pos(trans, path)
|
|
: have_node_at_pos(trans, path);
|
|
|
|
if (!dup && !(!path->preserve && !is_btree_node(path, path->level)))
|
|
return;
|
|
|
|
if (path->should_be_locked &&
|
|
!trans->restarted &&
|
|
(!dup || !bch2_btree_path_relock_norestart(trans, dup)))
|
|
return;
|
|
|
|
if (dup) {
|
|
dup->preserve |= path->preserve;
|
|
dup->should_be_locked |= path->should_be_locked;
|
|
}
|
|
|
|
__bch2_path_free(trans, path_idx);
|
|
}
|
|
|
|
static void bch2_path_put_nokeep(struct btree_trans *trans, btree_path_idx_t path,
|
|
bool intent)
|
|
{
|
|
if (!__btree_path_put(trans->paths + path, intent))
|
|
return;
|
|
|
|
__bch2_path_free(trans, path);
|
|
}
|
|
|
|
void __noreturn bch2_trans_restart_error(struct btree_trans *trans, u32 restart_count)
|
|
{
|
|
panic("trans->restart_count %u, should be %u, last restarted by %pS\n",
|
|
trans->restart_count, restart_count,
|
|
(void *) trans->last_begin_ip);
|
|
}
|
|
|
|
void __noreturn bch2_trans_in_restart_error(struct btree_trans *trans)
|
|
{
|
|
panic("in transaction restart: %s, last restarted by %pS\n",
|
|
bch2_err_str(trans->restarted),
|
|
(void *) trans->last_restarted_ip);
|
|
}
|
|
|
|
noinline __cold
|
|
void bch2_trans_updates_to_text(struct printbuf *buf, struct btree_trans *trans)
|
|
{
|
|
prt_printf(buf, "transaction updates for %s journal seq %llu",
|
|
trans->fn, trans->journal_res.seq);
|
|
prt_newline(buf);
|
|
printbuf_indent_add(buf, 2);
|
|
|
|
trans_for_each_update(trans, i) {
|
|
struct bkey_s_c old = { &i->old_k, i->old_v };
|
|
|
|
prt_printf(buf, "update: btree=%s cached=%u %pS",
|
|
bch2_btree_id_str(i->btree_id),
|
|
i->cached,
|
|
(void *) i->ip_allocated);
|
|
prt_newline(buf);
|
|
|
|
prt_printf(buf, " old ");
|
|
bch2_bkey_val_to_text(buf, trans->c, old);
|
|
prt_newline(buf);
|
|
|
|
prt_printf(buf, " new ");
|
|
bch2_bkey_val_to_text(buf, trans->c, bkey_i_to_s_c(i->k));
|
|
prt_newline(buf);
|
|
}
|
|
|
|
for (struct jset_entry *e = trans->journal_entries;
|
|
e != btree_trans_journal_entries_top(trans);
|
|
e = vstruct_next(e))
|
|
bch2_journal_entry_to_text(buf, trans->c, e);
|
|
|
|
printbuf_indent_sub(buf, 2);
|
|
}
|
|
|
|
noinline __cold
|
|
void bch2_dump_trans_updates(struct btree_trans *trans)
|
|
{
|
|
struct printbuf buf = PRINTBUF;
|
|
|
|
bch2_trans_updates_to_text(&buf, trans);
|
|
bch2_print_string_as_lines(KERN_ERR, buf.buf);
|
|
printbuf_exit(&buf);
|
|
}
|
|
|
|
static void bch2_btree_path_to_text(struct printbuf *out, struct btree_trans *trans, btree_path_idx_t path_idx)
|
|
{
|
|
struct btree_path *path = trans->paths + path_idx;
|
|
|
|
prt_printf(out, "path: idx %2u ref %u:%u %c %c btree=%s l=%u pos ",
|
|
path_idx, path->ref, path->intent_ref,
|
|
path->preserve ? 'P' : ' ',
|
|
path->should_be_locked ? 'S' : ' ',
|
|
bch2_btree_id_str(path->btree_id),
|
|
path->level);
|
|
bch2_bpos_to_text(out, path->pos);
|
|
|
|
prt_printf(out, " locks %u", path->nodes_locked);
|
|
#ifdef TRACK_PATH_ALLOCATED
|
|
prt_printf(out, " %pS", (void *) path->ip_allocated);
|
|
#endif
|
|
prt_newline(out);
|
|
}
|
|
|
|
static noinline __cold
|
|
void __bch2_trans_paths_to_text(struct printbuf *out, struct btree_trans *trans,
|
|
bool nosort)
|
|
{
|
|
struct trans_for_each_path_inorder_iter iter;
|
|
|
|
if (!nosort)
|
|
btree_trans_sort_paths(trans);
|
|
|
|
trans_for_each_path_idx_inorder(trans, iter)
|
|
bch2_btree_path_to_text(out, trans, iter.path_idx);
|
|
}
|
|
|
|
noinline __cold
|
|
void bch2_trans_paths_to_text(struct printbuf *out, struct btree_trans *trans)
|
|
{
|
|
__bch2_trans_paths_to_text(out, trans, false);
|
|
}
|
|
|
|
static noinline __cold
|
|
void __bch2_dump_trans_paths_updates(struct btree_trans *trans, bool nosort)
|
|
{
|
|
struct printbuf buf = PRINTBUF;
|
|
|
|
__bch2_trans_paths_to_text(&buf, trans, nosort);
|
|
bch2_trans_updates_to_text(&buf, trans);
|
|
|
|
bch2_print_string_as_lines(KERN_ERR, buf.buf);
|
|
printbuf_exit(&buf);
|
|
}
|
|
|
|
noinline __cold
|
|
void bch2_dump_trans_paths_updates(struct btree_trans *trans)
|
|
{
|
|
__bch2_dump_trans_paths_updates(trans, false);
|
|
}
|
|
|
|
noinline __cold
|
|
static void bch2_trans_update_max_paths(struct btree_trans *trans)
|
|
{
|
|
struct btree_transaction_stats *s = btree_trans_stats(trans);
|
|
struct printbuf buf = PRINTBUF;
|
|
size_t nr = bitmap_weight(trans->paths_allocated, trans->nr_paths);
|
|
|
|
bch2_trans_paths_to_text(&buf, trans);
|
|
|
|
if (!buf.allocation_failure) {
|
|
mutex_lock(&s->lock);
|
|
if (nr > s->nr_max_paths) {
|
|
s->nr_max_paths = nr;
|
|
swap(s->max_paths_text, buf.buf);
|
|
}
|
|
mutex_unlock(&s->lock);
|
|
}
|
|
|
|
printbuf_exit(&buf);
|
|
|
|
trans->nr_paths_max = nr;
|
|
}
|
|
|
|
noinline __cold
|
|
int __bch2_btree_trans_too_many_iters(struct btree_trans *trans)
|
|
{
|
|
if (trace_trans_restart_too_many_iters_enabled()) {
|
|
struct printbuf buf = PRINTBUF;
|
|
|
|
bch2_trans_paths_to_text(&buf, trans);
|
|
trace_trans_restart_too_many_iters(trans, _THIS_IP_, buf.buf);
|
|
printbuf_exit(&buf);
|
|
}
|
|
|
|
count_event(trans->c, trans_restart_too_many_iters);
|
|
|
|
return btree_trans_restart(trans, BCH_ERR_transaction_restart_too_many_iters);
|
|
}
|
|
|
|
static noinline void btree_path_overflow(struct btree_trans *trans)
|
|
{
|
|
bch2_dump_trans_paths_updates(trans);
|
|
bch_err(trans->c, "trans path overflow");
|
|
}
|
|
|
|
static noinline void btree_paths_realloc(struct btree_trans *trans)
|
|
{
|
|
unsigned nr = trans->nr_paths * 2;
|
|
|
|
void *p = kvzalloc(BITS_TO_LONGS(nr) * sizeof(unsigned long) +
|
|
sizeof(struct btree_trans_paths) +
|
|
nr * sizeof(struct btree_path) +
|
|
nr * sizeof(btree_path_idx_t) + 8 +
|
|
nr * sizeof(struct btree_insert_entry), GFP_KERNEL|__GFP_NOFAIL);
|
|
|
|
unsigned long *paths_allocated = p;
|
|
memcpy(paths_allocated, trans->paths_allocated, BITS_TO_LONGS(trans->nr_paths) * sizeof(unsigned long));
|
|
p += BITS_TO_LONGS(nr) * sizeof(unsigned long);
|
|
|
|
p += sizeof(struct btree_trans_paths);
|
|
struct btree_path *paths = p;
|
|
*trans_paths_nr(paths) = nr;
|
|
memcpy(paths, trans->paths, trans->nr_paths * sizeof(struct btree_path));
|
|
p += nr * sizeof(struct btree_path);
|
|
|
|
btree_path_idx_t *sorted = p;
|
|
memcpy(sorted, trans->sorted, trans->nr_sorted * sizeof(btree_path_idx_t));
|
|
p += nr * sizeof(btree_path_idx_t) + 8;
|
|
|
|
struct btree_insert_entry *updates = p;
|
|
memcpy(updates, trans->updates, trans->nr_paths * sizeof(struct btree_insert_entry));
|
|
|
|
unsigned long *old = trans->paths_allocated;
|
|
|
|
rcu_assign_pointer(trans->paths_allocated, paths_allocated);
|
|
rcu_assign_pointer(trans->paths, paths);
|
|
rcu_assign_pointer(trans->sorted, sorted);
|
|
rcu_assign_pointer(trans->updates, updates);
|
|
|
|
trans->nr_paths = nr;
|
|
|
|
if (old != trans->_paths_allocated)
|
|
kfree_rcu_mightsleep(old);
|
|
}
|
|
|
|
static inline btree_path_idx_t btree_path_alloc(struct btree_trans *trans,
|
|
btree_path_idx_t pos)
|
|
{
|
|
btree_path_idx_t idx = find_first_zero_bit(trans->paths_allocated, trans->nr_paths);
|
|
|
|
if (unlikely(idx == trans->nr_paths)) {
|
|
if (trans->nr_paths == BTREE_ITER_MAX) {
|
|
btree_path_overflow(trans);
|
|
return 0;
|
|
}
|
|
|
|
btree_paths_realloc(trans);
|
|
}
|
|
|
|
/*
|
|
* Do this before marking the new path as allocated, since it won't be
|
|
* initialized yet:
|
|
*/
|
|
if (unlikely(idx > trans->nr_paths_max))
|
|
bch2_trans_update_max_paths(trans);
|
|
|
|
__set_bit(idx, trans->paths_allocated);
|
|
|
|
struct btree_path *path = &trans->paths[idx];
|
|
path->ref = 0;
|
|
path->intent_ref = 0;
|
|
path->nodes_locked = 0;
|
|
|
|
btree_path_list_add(trans, pos, idx);
|
|
trans->paths_sorted = false;
|
|
return idx;
|
|
}
|
|
|
|
btree_path_idx_t bch2_path_get(struct btree_trans *trans,
|
|
enum btree_id btree_id, struct bpos pos,
|
|
unsigned locks_want, unsigned level,
|
|
unsigned flags, unsigned long ip)
|
|
{
|
|
struct btree_path *path;
|
|
bool cached = flags & BTREE_ITER_CACHED;
|
|
bool intent = flags & BTREE_ITER_INTENT;
|
|
struct trans_for_each_path_inorder_iter iter;
|
|
btree_path_idx_t path_pos = 0, path_idx;
|
|
|
|
bch2_trans_verify_not_in_restart(trans);
|
|
bch2_trans_verify_locks(trans);
|
|
|
|
btree_trans_sort_paths(trans);
|
|
|
|
trans_for_each_path_inorder(trans, path, iter) {
|
|
if (__btree_path_cmp(path,
|
|
btree_id,
|
|
cached,
|
|
pos,
|
|
level) > 0)
|
|
break;
|
|
|
|
path_pos = iter.path_idx;
|
|
}
|
|
|
|
if (path_pos &&
|
|
trans->paths[path_pos].cached == cached &&
|
|
trans->paths[path_pos].btree_id == btree_id &&
|
|
trans->paths[path_pos].level == level) {
|
|
__btree_path_get(trans->paths + path_pos, intent);
|
|
path_idx = bch2_btree_path_set_pos(trans, path_pos, pos, intent, ip);
|
|
path = trans->paths + path_idx;
|
|
} else {
|
|
path_idx = btree_path_alloc(trans, path_pos);
|
|
path = trans->paths + path_idx;
|
|
|
|
__btree_path_get(path, intent);
|
|
path->pos = pos;
|
|
path->btree_id = btree_id;
|
|
path->cached = cached;
|
|
path->uptodate = BTREE_ITER_NEED_TRAVERSE;
|
|
path->should_be_locked = false;
|
|
path->level = level;
|
|
path->locks_want = locks_want;
|
|
path->nodes_locked = 0;
|
|
for (unsigned i = 0; i < ARRAY_SIZE(path->l); i++)
|
|
path->l[i].b = ERR_PTR(-BCH_ERR_no_btree_node_init);
|
|
#ifdef TRACK_PATH_ALLOCATED
|
|
path->ip_allocated = ip;
|
|
#endif
|
|
trans->paths_sorted = false;
|
|
}
|
|
|
|
if (!(flags & BTREE_ITER_NOPRESERVE))
|
|
path->preserve = true;
|
|
|
|
if (path->intent_ref)
|
|
locks_want = max(locks_want, level + 1);
|
|
|
|
/*
|
|
* If the path has locks_want greater than requested, we don't downgrade
|
|
* it here - on transaction restart because btree node split needs to
|
|
* upgrade locks, we might be putting/getting the iterator again.
|
|
* Downgrading iterators only happens via bch2_trans_downgrade(), after
|
|
* a successful transaction commit.
|
|
*/
|
|
|
|
locks_want = min(locks_want, BTREE_MAX_DEPTH);
|
|
if (locks_want > path->locks_want)
|
|
bch2_btree_path_upgrade_noupgrade_sibs(trans, path, locks_want, NULL);
|
|
|
|
return path_idx;
|
|
}
|
|
|
|
struct bkey_s_c bch2_btree_path_peek_slot(struct btree_path *path, struct bkey *u)
|
|
{
|
|
|
|
struct btree_path_level *l = path_l(path);
|
|
struct bkey_packed *_k;
|
|
struct bkey_s_c k;
|
|
|
|
if (unlikely(!l->b))
|
|
return bkey_s_c_null;
|
|
|
|
EBUG_ON(path->uptodate != BTREE_ITER_UPTODATE);
|
|
EBUG_ON(!btree_node_locked(path, path->level));
|
|
|
|
if (!path->cached) {
|
|
_k = bch2_btree_node_iter_peek_all(&l->iter, l->b);
|
|
k = _k ? bkey_disassemble(l->b, _k, u) : bkey_s_c_null;
|
|
|
|
EBUG_ON(k.k && bkey_deleted(k.k) && bpos_eq(k.k->p, path->pos));
|
|
|
|
if (!k.k || !bpos_eq(path->pos, k.k->p))
|
|
goto hole;
|
|
} else {
|
|
struct bkey_cached *ck = (void *) path->l[0].b;
|
|
|
|
EBUG_ON(ck &&
|
|
(path->btree_id != ck->key.btree_id ||
|
|
!bkey_eq(path->pos, ck->key.pos)));
|
|
if (!ck || !ck->valid)
|
|
return bkey_s_c_null;
|
|
|
|
*u = ck->k->k;
|
|
k = bkey_i_to_s_c(ck->k);
|
|
}
|
|
|
|
return k;
|
|
hole:
|
|
bkey_init(u);
|
|
u->p = path->pos;
|
|
return (struct bkey_s_c) { u, NULL };
|
|
}
|
|
|
|
/* Btree iterators: */
|
|
|
|
int __must_check
|
|
__bch2_btree_iter_traverse(struct btree_iter *iter)
|
|
{
|
|
return bch2_btree_path_traverse(iter->trans, iter->path, iter->flags);
|
|
}
|
|
|
|
int __must_check
|
|
bch2_btree_iter_traverse(struct btree_iter *iter)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
int ret;
|
|
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path,
|
|
btree_iter_search_key(iter),
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
|
|
ret = bch2_btree_path_traverse(iter->trans, iter->path, iter->flags);
|
|
if (ret)
|
|
return ret;
|
|
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
if (btree_path_node(path, path->level))
|
|
btree_path_set_should_be_locked(path);
|
|
return 0;
|
|
}
|
|
|
|
/* Iterate across nodes (leaf and interior nodes) */
|
|
|
|
struct btree *bch2_btree_iter_peek_node(struct btree_iter *iter)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct btree *b = NULL;
|
|
int ret;
|
|
|
|
EBUG_ON(trans->paths[iter->path].cached);
|
|
bch2_btree_iter_verify(iter);
|
|
|
|
ret = bch2_btree_path_traverse(trans, iter->path, iter->flags);
|
|
if (ret)
|
|
goto err;
|
|
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
b = btree_path_node(path, path->level);
|
|
if (!b)
|
|
goto out;
|
|
|
|
BUG_ON(bpos_lt(b->key.k.p, iter->pos));
|
|
|
|
bkey_init(&iter->k);
|
|
iter->k.p = iter->pos = b->key.k.p;
|
|
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path, b->key.k.p,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
btree_path_set_should_be_locked(btree_iter_path(trans, iter));
|
|
out:
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
bch2_btree_iter_verify(iter);
|
|
|
|
return b;
|
|
err:
|
|
b = ERR_PTR(ret);
|
|
goto out;
|
|
}
|
|
|
|
struct btree *bch2_btree_iter_peek_node_and_restart(struct btree_iter *iter)
|
|
{
|
|
struct btree *b;
|
|
|
|
while (b = bch2_btree_iter_peek_node(iter),
|
|
bch2_err_matches(PTR_ERR_OR_ZERO(b), BCH_ERR_transaction_restart))
|
|
bch2_trans_begin(iter->trans);
|
|
|
|
return b;
|
|
}
|
|
|
|
struct btree *bch2_btree_iter_next_node(struct btree_iter *iter)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct btree *b = NULL;
|
|
int ret;
|
|
|
|
EBUG_ON(trans->paths[iter->path].cached);
|
|
bch2_trans_verify_not_in_restart(trans);
|
|
bch2_btree_iter_verify(iter);
|
|
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
|
|
/* already at end? */
|
|
if (!btree_path_node(path, path->level))
|
|
return NULL;
|
|
|
|
/* got to end? */
|
|
if (!btree_path_node(path, path->level + 1)) {
|
|
btree_path_set_level_up(trans, path);
|
|
return NULL;
|
|
}
|
|
|
|
if (!bch2_btree_node_relock(trans, path, path->level + 1)) {
|
|
__bch2_btree_path_unlock(trans, path);
|
|
path->l[path->level].b = ERR_PTR(-BCH_ERR_no_btree_node_relock);
|
|
path->l[path->level + 1].b = ERR_PTR(-BCH_ERR_no_btree_node_relock);
|
|
btree_path_set_dirty(path, BTREE_ITER_NEED_TRAVERSE);
|
|
trace_and_count(trans->c, trans_restart_relock_next_node, trans, _THIS_IP_, path);
|
|
ret = btree_trans_restart(trans, BCH_ERR_transaction_restart_relock);
|
|
goto err;
|
|
}
|
|
|
|
b = btree_path_node(path, path->level + 1);
|
|
|
|
if (bpos_eq(iter->pos, b->key.k.p)) {
|
|
__btree_path_set_level_up(trans, path, path->level++);
|
|
} else {
|
|
/*
|
|
* Haven't gotten to the end of the parent node: go back down to
|
|
* the next child node
|
|
*/
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path,
|
|
bpos_successor(iter->pos),
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
|
|
path = btree_iter_path(trans, iter);
|
|
btree_path_set_level_down(trans, path, iter->min_depth);
|
|
|
|
ret = bch2_btree_path_traverse(trans, iter->path, iter->flags);
|
|
if (ret)
|
|
goto err;
|
|
|
|
path = btree_iter_path(trans, iter);
|
|
b = path->l[path->level].b;
|
|
}
|
|
|
|
bkey_init(&iter->k);
|
|
iter->k.p = iter->pos = b->key.k.p;
|
|
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path, b->key.k.p,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
btree_path_set_should_be_locked(btree_iter_path(trans, iter));
|
|
EBUG_ON(btree_iter_path(trans, iter)->uptodate);
|
|
out:
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
bch2_btree_iter_verify(iter);
|
|
|
|
return b;
|
|
err:
|
|
b = ERR_PTR(ret);
|
|
goto out;
|
|
}
|
|
|
|
/* Iterate across keys (in leaf nodes only) */
|
|
|
|
inline bool bch2_btree_iter_advance(struct btree_iter *iter)
|
|
{
|
|
struct bpos pos = iter->k.p;
|
|
bool ret = !(iter->flags & BTREE_ITER_ALL_SNAPSHOTS
|
|
? bpos_eq(pos, SPOS_MAX)
|
|
: bkey_eq(pos, SPOS_MAX));
|
|
|
|
if (ret && !(iter->flags & BTREE_ITER_IS_EXTENTS))
|
|
pos = bkey_successor(iter, pos);
|
|
bch2_btree_iter_set_pos(iter, pos);
|
|
return ret;
|
|
}
|
|
|
|
inline bool bch2_btree_iter_rewind(struct btree_iter *iter)
|
|
{
|
|
struct bpos pos = bkey_start_pos(&iter->k);
|
|
bool ret = !(iter->flags & BTREE_ITER_ALL_SNAPSHOTS
|
|
? bpos_eq(pos, POS_MIN)
|
|
: bkey_eq(pos, POS_MIN));
|
|
|
|
if (ret && !(iter->flags & BTREE_ITER_IS_EXTENTS))
|
|
pos = bkey_predecessor(iter, pos);
|
|
bch2_btree_iter_set_pos(iter, pos);
|
|
return ret;
|
|
}
|
|
|
|
static noinline
|
|
void bch2_btree_trans_peek_prev_updates(struct btree_trans *trans, struct btree_iter *iter,
|
|
struct bkey_s_c *k)
|
|
{
|
|
struct bpos end = path_l(btree_iter_path(trans, iter))->b->data->min_key;
|
|
|
|
trans_for_each_update(trans, i)
|
|
if (!i->key_cache_already_flushed &&
|
|
i->btree_id == iter->btree_id &&
|
|
bpos_le(i->k->k.p, iter->pos) &&
|
|
bpos_ge(i->k->k.p, k->k ? k->k->p : end)) {
|
|
iter->k = i->k->k;
|
|
*k = bkey_i_to_s_c(i->k);
|
|
}
|
|
}
|
|
|
|
static noinline
|
|
void bch2_btree_trans_peek_updates(struct btree_trans *trans, struct btree_iter *iter,
|
|
struct bkey_s_c *k)
|
|
{
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
struct bpos end = path_l(path)->b->key.k.p;
|
|
|
|
trans_for_each_update(trans, i)
|
|
if (!i->key_cache_already_flushed &&
|
|
i->btree_id == iter->btree_id &&
|
|
bpos_ge(i->k->k.p, path->pos) &&
|
|
bpos_le(i->k->k.p, k->k ? k->k->p : end)) {
|
|
iter->k = i->k->k;
|
|
*k = bkey_i_to_s_c(i->k);
|
|
}
|
|
}
|
|
|
|
static noinline
|
|
void bch2_btree_trans_peek_slot_updates(struct btree_trans *trans, struct btree_iter *iter,
|
|
struct bkey_s_c *k)
|
|
{
|
|
trans_for_each_update(trans, i)
|
|
if (!i->key_cache_already_flushed &&
|
|
i->btree_id == iter->btree_id &&
|
|
bpos_eq(i->k->k.p, iter->pos)) {
|
|
iter->k = i->k->k;
|
|
*k = bkey_i_to_s_c(i->k);
|
|
}
|
|
}
|
|
|
|
static struct bkey_i *bch2_btree_journal_peek(struct btree_trans *trans,
|
|
struct btree_iter *iter,
|
|
struct bpos end_pos)
|
|
{
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
|
|
return bch2_journal_keys_peek_upto(trans->c, iter->btree_id,
|
|
path->level,
|
|
path->pos,
|
|
end_pos,
|
|
&iter->journal_idx);
|
|
}
|
|
|
|
static noinline
|
|
struct bkey_s_c btree_trans_peek_slot_journal(struct btree_trans *trans,
|
|
struct btree_iter *iter)
|
|
{
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
struct bkey_i *k = bch2_btree_journal_peek(trans, iter, path->pos);
|
|
|
|
if (k) {
|
|
iter->k = k->k;
|
|
return bkey_i_to_s_c(k);
|
|
} else {
|
|
return bkey_s_c_null;
|
|
}
|
|
}
|
|
|
|
static noinline
|
|
struct bkey_s_c btree_trans_peek_journal(struct btree_trans *trans,
|
|
struct btree_iter *iter,
|
|
struct bkey_s_c k)
|
|
{
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
struct bkey_i *next_journal =
|
|
bch2_btree_journal_peek(trans, iter,
|
|
k.k ? k.k->p : path_l(path)->b->key.k.p);
|
|
|
|
if (next_journal) {
|
|
iter->k = next_journal->k;
|
|
k = bkey_i_to_s_c(next_journal);
|
|
}
|
|
|
|
return k;
|
|
}
|
|
|
|
/*
|
|
* Checks btree key cache for key at iter->pos and returns it if present, or
|
|
* bkey_s_c_null:
|
|
*/
|
|
static noinline
|
|
struct bkey_s_c btree_trans_peek_key_cache(struct btree_iter *iter, struct bpos pos)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct bch_fs *c = trans->c;
|
|
struct bkey u;
|
|
struct bkey_s_c k;
|
|
int ret;
|
|
|
|
if ((iter->flags & BTREE_ITER_KEY_CACHE_FILL) &&
|
|
bpos_eq(iter->pos, pos))
|
|
return bkey_s_c_null;
|
|
|
|
if (!bch2_btree_key_cache_find(c, iter->btree_id, pos))
|
|
return bkey_s_c_null;
|
|
|
|
if (!iter->key_cache_path)
|
|
iter->key_cache_path = bch2_path_get(trans, iter->btree_id, pos,
|
|
iter->flags & BTREE_ITER_INTENT, 0,
|
|
iter->flags|BTREE_ITER_CACHED|
|
|
BTREE_ITER_CACHED_NOFILL,
|
|
_THIS_IP_);
|
|
|
|
iter->key_cache_path = bch2_btree_path_set_pos(trans, iter->key_cache_path, pos,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
|
|
ret = bch2_btree_path_traverse(trans, iter->key_cache_path,
|
|
iter->flags|BTREE_ITER_CACHED) ?:
|
|
bch2_btree_path_relock(trans, btree_iter_path(trans, iter), _THIS_IP_);
|
|
if (unlikely(ret))
|
|
return bkey_s_c_err(ret);
|
|
|
|
btree_path_set_should_be_locked(trans->paths + iter->key_cache_path);
|
|
|
|
k = bch2_btree_path_peek_slot(trans->paths + iter->key_cache_path, &u);
|
|
if (k.k && !bkey_err(k)) {
|
|
iter->k = u;
|
|
k.k = &iter->k;
|
|
}
|
|
return k;
|
|
}
|
|
|
|
static struct bkey_s_c __bch2_btree_iter_peek(struct btree_iter *iter, struct bpos search_key)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct bkey_s_c k, k2;
|
|
int ret;
|
|
|
|
EBUG_ON(btree_iter_path(trans, iter)->cached);
|
|
bch2_btree_iter_verify(iter);
|
|
|
|
while (1) {
|
|
struct btree_path_level *l;
|
|
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path, search_key,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
|
|
ret = bch2_btree_path_traverse(trans, iter->path, iter->flags);
|
|
if (unlikely(ret)) {
|
|
/* ensure that iter->k is consistent with iter->pos: */
|
|
bch2_btree_iter_set_pos(iter, iter->pos);
|
|
k = bkey_s_c_err(ret);
|
|
goto out;
|
|
}
|
|
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
l = path_l(path);
|
|
|
|
if (unlikely(!l->b)) {
|
|
/* No btree nodes at requested level: */
|
|
bch2_btree_iter_set_pos(iter, SPOS_MAX);
|
|
k = bkey_s_c_null;
|
|
goto out;
|
|
}
|
|
|
|
btree_path_set_should_be_locked(path);
|
|
|
|
k = btree_path_level_peek_all(trans->c, l, &iter->k);
|
|
|
|
if (unlikely(iter->flags & BTREE_ITER_WITH_KEY_CACHE) &&
|
|
k.k &&
|
|
(k2 = btree_trans_peek_key_cache(iter, k.k->p)).k) {
|
|
k = k2;
|
|
ret = bkey_err(k);
|
|
if (ret) {
|
|
bch2_btree_iter_set_pos(iter, iter->pos);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (unlikely(iter->flags & BTREE_ITER_WITH_JOURNAL))
|
|
k = btree_trans_peek_journal(trans, iter, k);
|
|
|
|
if (unlikely((iter->flags & BTREE_ITER_WITH_UPDATES) &&
|
|
trans->nr_updates))
|
|
bch2_btree_trans_peek_updates(trans, iter, &k);
|
|
|
|
if (k.k && bkey_deleted(k.k)) {
|
|
/*
|
|
* If we've got a whiteout, and it's after the search
|
|
* key, advance the search key to the whiteout instead
|
|
* of just after the whiteout - it might be a btree
|
|
* whiteout, with a real key at the same position, since
|
|
* in the btree deleted keys sort before non deleted.
|
|
*/
|
|
search_key = !bpos_eq(search_key, k.k->p)
|
|
? k.k->p
|
|
: bpos_successor(k.k->p);
|
|
continue;
|
|
}
|
|
|
|
if (likely(k.k)) {
|
|
break;
|
|
} else if (likely(!bpos_eq(l->b->key.k.p, SPOS_MAX))) {
|
|
/* Advance to next leaf node: */
|
|
search_key = bpos_successor(l->b->key.k.p);
|
|
} else {
|
|
/* End of btree: */
|
|
bch2_btree_iter_set_pos(iter, SPOS_MAX);
|
|
k = bkey_s_c_null;
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
bch2_btree_iter_verify(iter);
|
|
|
|
return k;
|
|
}
|
|
|
|
/**
|
|
* bch2_btree_iter_peek_upto() - returns first key greater than or equal to
|
|
* iterator's current position
|
|
* @iter: iterator to peek from
|
|
* @end: search limit: returns keys less than or equal to @end
|
|
*
|
|
* Returns: key if found, or an error extractable with bkey_err().
|
|
*/
|
|
struct bkey_s_c bch2_btree_iter_peek_upto(struct btree_iter *iter, struct bpos end)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct bpos search_key = btree_iter_search_key(iter);
|
|
struct bkey_s_c k;
|
|
struct bpos iter_pos;
|
|
int ret;
|
|
|
|
EBUG_ON((iter->flags & BTREE_ITER_FILTER_SNAPSHOTS) && bkey_eq(end, POS_MAX));
|
|
|
|
if (iter->update_path) {
|
|
bch2_path_put_nokeep(trans, iter->update_path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
iter->update_path = 0;
|
|
}
|
|
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
|
|
while (1) {
|
|
k = __bch2_btree_iter_peek(iter, search_key);
|
|
if (unlikely(!k.k))
|
|
goto end;
|
|
if (unlikely(bkey_err(k)))
|
|
goto out_no_locked;
|
|
|
|
/*
|
|
* We need to check against @end before FILTER_SNAPSHOTS because
|
|
* if we get to a different inode that requested we might be
|
|
* seeing keys for a different snapshot tree that will all be
|
|
* filtered out.
|
|
*
|
|
* But we can't do the full check here, because bkey_start_pos()
|
|
* isn't monotonically increasing before FILTER_SNAPSHOTS, and
|
|
* that's what we check against in extents mode:
|
|
*/
|
|
if (unlikely(!(iter->flags & BTREE_ITER_IS_EXTENTS)
|
|
? bkey_gt(k.k->p, end)
|
|
: k.k->p.inode > end.inode))
|
|
goto end;
|
|
|
|
if (iter->update_path &&
|
|
!bkey_eq(trans->paths[iter->update_path].pos, k.k->p)) {
|
|
bch2_path_put_nokeep(trans, iter->update_path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
iter->update_path = 0;
|
|
}
|
|
|
|
if ((iter->flags & BTREE_ITER_FILTER_SNAPSHOTS) &&
|
|
(iter->flags & BTREE_ITER_INTENT) &&
|
|
!(iter->flags & BTREE_ITER_IS_EXTENTS) &&
|
|
!iter->update_path) {
|
|
struct bpos pos = k.k->p;
|
|
|
|
if (pos.snapshot < iter->snapshot) {
|
|
search_key = bpos_successor(k.k->p);
|
|
continue;
|
|
}
|
|
|
|
pos.snapshot = iter->snapshot;
|
|
|
|
/*
|
|
* advance, same as on exit for iter->path, but only up
|
|
* to snapshot
|
|
*/
|
|
__btree_path_get(trans->paths + iter->path, iter->flags & BTREE_ITER_INTENT);
|
|
iter->update_path = iter->path;
|
|
|
|
iter->update_path = bch2_btree_path_set_pos(trans,
|
|
iter->update_path, pos,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
_THIS_IP_);
|
|
ret = bch2_btree_path_traverse(trans, iter->update_path, iter->flags);
|
|
if (unlikely(ret)) {
|
|
k = bkey_s_c_err(ret);
|
|
goto out_no_locked;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We can never have a key in a leaf node at POS_MAX, so
|
|
* we don't have to check these successor() calls:
|
|
*/
|
|
if ((iter->flags & BTREE_ITER_FILTER_SNAPSHOTS) &&
|
|
!bch2_snapshot_is_ancestor(trans->c,
|
|
iter->snapshot,
|
|
k.k->p.snapshot)) {
|
|
search_key = bpos_successor(k.k->p);
|
|
continue;
|
|
}
|
|
|
|
if (bkey_whiteout(k.k) &&
|
|
!(iter->flags & BTREE_ITER_ALL_SNAPSHOTS)) {
|
|
search_key = bkey_successor(iter, k.k->p);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* iter->pos should be mononotically increasing, and always be
|
|
* equal to the key we just returned - except extents can
|
|
* straddle iter->pos:
|
|
*/
|
|
if (!(iter->flags & BTREE_ITER_IS_EXTENTS))
|
|
iter_pos = k.k->p;
|
|
else
|
|
iter_pos = bkey_max(iter->pos, bkey_start_pos(k.k));
|
|
|
|
if (unlikely(!(iter->flags & BTREE_ITER_IS_EXTENTS)
|
|
? bkey_gt(iter_pos, end)
|
|
: bkey_ge(iter_pos, end)))
|
|
goto end;
|
|
|
|
break;
|
|
}
|
|
|
|
iter->pos = iter_pos;
|
|
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path, k.k->p,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
|
|
btree_path_set_should_be_locked(btree_iter_path(trans, iter));
|
|
out_no_locked:
|
|
if (iter->update_path) {
|
|
ret = bch2_btree_path_relock(trans, trans->paths + iter->update_path, _THIS_IP_);
|
|
if (unlikely(ret))
|
|
k = bkey_s_c_err(ret);
|
|
else
|
|
btree_path_set_should_be_locked(trans->paths + iter->update_path);
|
|
}
|
|
|
|
if (!(iter->flags & BTREE_ITER_ALL_SNAPSHOTS))
|
|
iter->pos.snapshot = iter->snapshot;
|
|
|
|
ret = bch2_btree_iter_verify_ret(iter, k);
|
|
if (unlikely(ret)) {
|
|
bch2_btree_iter_set_pos(iter, iter->pos);
|
|
k = bkey_s_c_err(ret);
|
|
}
|
|
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
|
|
return k;
|
|
end:
|
|
bch2_btree_iter_set_pos(iter, end);
|
|
k = bkey_s_c_null;
|
|
goto out_no_locked;
|
|
}
|
|
|
|
/**
|
|
* bch2_btree_iter_next() - returns first key greater than iterator's current
|
|
* position
|
|
* @iter: iterator to peek from
|
|
*
|
|
* Returns: key if found, or an error extractable with bkey_err().
|
|
*/
|
|
struct bkey_s_c bch2_btree_iter_next(struct btree_iter *iter)
|
|
{
|
|
if (!bch2_btree_iter_advance(iter))
|
|
return bkey_s_c_null;
|
|
|
|
return bch2_btree_iter_peek(iter);
|
|
}
|
|
|
|
/**
|
|
* bch2_btree_iter_peek_prev() - returns first key less than or equal to
|
|
* iterator's current position
|
|
* @iter: iterator to peek from
|
|
*
|
|
* Returns: key if found, or an error extractable with bkey_err().
|
|
*/
|
|
struct bkey_s_c bch2_btree_iter_peek_prev(struct btree_iter *iter)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct bpos search_key = iter->pos;
|
|
struct bkey_s_c k;
|
|
struct bkey saved_k;
|
|
const struct bch_val *saved_v;
|
|
btree_path_idx_t saved_path = 0;
|
|
int ret;
|
|
|
|
EBUG_ON(btree_iter_path(trans, iter)->cached ||
|
|
btree_iter_path(trans, iter)->level);
|
|
|
|
if (iter->flags & BTREE_ITER_WITH_JOURNAL)
|
|
return bkey_s_c_err(-BCH_ERR_btree_iter_with_journal_not_supported);
|
|
|
|
bch2_btree_iter_verify(iter);
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
|
|
if (iter->flags & BTREE_ITER_FILTER_SNAPSHOTS)
|
|
search_key.snapshot = U32_MAX;
|
|
|
|
while (1) {
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path, search_key,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
|
|
ret = bch2_btree_path_traverse(trans, iter->path, iter->flags);
|
|
if (unlikely(ret)) {
|
|
/* ensure that iter->k is consistent with iter->pos: */
|
|
bch2_btree_iter_set_pos(iter, iter->pos);
|
|
k = bkey_s_c_err(ret);
|
|
goto out_no_locked;
|
|
}
|
|
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
|
|
k = btree_path_level_peek(trans, path, &path->l[0], &iter->k);
|
|
if (!k.k ||
|
|
((iter->flags & BTREE_ITER_IS_EXTENTS)
|
|
? bpos_ge(bkey_start_pos(k.k), search_key)
|
|
: bpos_gt(k.k->p, search_key)))
|
|
k = btree_path_level_prev(trans, path, &path->l[0], &iter->k);
|
|
|
|
if (unlikely((iter->flags & BTREE_ITER_WITH_UPDATES) &&
|
|
trans->nr_updates))
|
|
bch2_btree_trans_peek_prev_updates(trans, iter, &k);
|
|
|
|
if (likely(k.k)) {
|
|
if (iter->flags & BTREE_ITER_FILTER_SNAPSHOTS) {
|
|
if (k.k->p.snapshot == iter->snapshot)
|
|
goto got_key;
|
|
|
|
/*
|
|
* If we have a saved candidate, and we're no
|
|
* longer at the same _key_ (not pos), return
|
|
* that candidate
|
|
*/
|
|
if (saved_path && !bkey_eq(k.k->p, saved_k.p)) {
|
|
bch2_path_put_nokeep(trans, iter->path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
iter->path = saved_path;
|
|
saved_path = 0;
|
|
iter->k = saved_k;
|
|
k.v = saved_v;
|
|
goto got_key;
|
|
}
|
|
|
|
if (bch2_snapshot_is_ancestor(trans->c,
|
|
iter->snapshot,
|
|
k.k->p.snapshot)) {
|
|
if (saved_path)
|
|
bch2_path_put_nokeep(trans, saved_path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
saved_path = btree_path_clone(trans, iter->path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
path = btree_iter_path(trans, iter);
|
|
saved_k = *k.k;
|
|
saved_v = k.v;
|
|
}
|
|
|
|
search_key = bpos_predecessor(k.k->p);
|
|
continue;
|
|
}
|
|
got_key:
|
|
if (bkey_whiteout(k.k) &&
|
|
!(iter->flags & BTREE_ITER_ALL_SNAPSHOTS)) {
|
|
search_key = bkey_predecessor(iter, k.k->p);
|
|
if (iter->flags & BTREE_ITER_FILTER_SNAPSHOTS)
|
|
search_key.snapshot = U32_MAX;
|
|
continue;
|
|
}
|
|
|
|
btree_path_set_should_be_locked(path);
|
|
break;
|
|
} else if (likely(!bpos_eq(path->l[0].b->data->min_key, POS_MIN))) {
|
|
/* Advance to previous leaf node: */
|
|
search_key = bpos_predecessor(path->l[0].b->data->min_key);
|
|
} else {
|
|
/* Start of btree: */
|
|
bch2_btree_iter_set_pos(iter, POS_MIN);
|
|
k = bkey_s_c_null;
|
|
goto out_no_locked;
|
|
}
|
|
}
|
|
|
|
EBUG_ON(bkey_gt(bkey_start_pos(k.k), iter->pos));
|
|
|
|
/* Extents can straddle iter->pos: */
|
|
if (bkey_lt(k.k->p, iter->pos))
|
|
iter->pos = k.k->p;
|
|
|
|
if (iter->flags & BTREE_ITER_FILTER_SNAPSHOTS)
|
|
iter->pos.snapshot = iter->snapshot;
|
|
out_no_locked:
|
|
if (saved_path)
|
|
bch2_path_put_nokeep(trans, saved_path, iter->flags & BTREE_ITER_INTENT);
|
|
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
bch2_btree_iter_verify(iter);
|
|
|
|
return k;
|
|
}
|
|
|
|
/**
|
|
* bch2_btree_iter_prev() - returns first key less than iterator's current
|
|
* position
|
|
* @iter: iterator to peek from
|
|
*
|
|
* Returns: key if found, or an error extractable with bkey_err().
|
|
*/
|
|
struct bkey_s_c bch2_btree_iter_prev(struct btree_iter *iter)
|
|
{
|
|
if (!bch2_btree_iter_rewind(iter))
|
|
return bkey_s_c_null;
|
|
|
|
return bch2_btree_iter_peek_prev(iter);
|
|
}
|
|
|
|
struct bkey_s_c bch2_btree_iter_peek_slot(struct btree_iter *iter)
|
|
{
|
|
struct btree_trans *trans = iter->trans;
|
|
struct bpos search_key;
|
|
struct bkey_s_c k;
|
|
int ret;
|
|
|
|
bch2_btree_iter_verify(iter);
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
EBUG_ON(btree_iter_path(trans, iter)->level && (iter->flags & BTREE_ITER_WITH_KEY_CACHE));
|
|
|
|
/* extents can't span inode numbers: */
|
|
if ((iter->flags & BTREE_ITER_IS_EXTENTS) &&
|
|
unlikely(iter->pos.offset == KEY_OFFSET_MAX)) {
|
|
if (iter->pos.inode == KEY_INODE_MAX)
|
|
return bkey_s_c_null;
|
|
|
|
bch2_btree_iter_set_pos(iter, bpos_nosnap_successor(iter->pos));
|
|
}
|
|
|
|
search_key = btree_iter_search_key(iter);
|
|
iter->path = bch2_btree_path_set_pos(trans, iter->path, search_key,
|
|
iter->flags & BTREE_ITER_INTENT,
|
|
btree_iter_ip_allocated(iter));
|
|
|
|
ret = bch2_btree_path_traverse(trans, iter->path, iter->flags);
|
|
if (unlikely(ret)) {
|
|
k = bkey_s_c_err(ret);
|
|
goto out_no_locked;
|
|
}
|
|
|
|
if ((iter->flags & BTREE_ITER_CACHED) ||
|
|
!(iter->flags & (BTREE_ITER_IS_EXTENTS|BTREE_ITER_FILTER_SNAPSHOTS))) {
|
|
k = bkey_s_c_null;
|
|
|
|
if (unlikely((iter->flags & BTREE_ITER_WITH_UPDATES) &&
|
|
trans->nr_updates)) {
|
|
bch2_btree_trans_peek_slot_updates(trans, iter, &k);
|
|
if (k.k)
|
|
goto out;
|
|
}
|
|
|
|
if (unlikely(iter->flags & BTREE_ITER_WITH_JOURNAL) &&
|
|
(k = btree_trans_peek_slot_journal(trans, iter)).k)
|
|
goto out;
|
|
|
|
if (unlikely(iter->flags & BTREE_ITER_WITH_KEY_CACHE) &&
|
|
(k = btree_trans_peek_key_cache(iter, iter->pos)).k) {
|
|
if (!bkey_err(k))
|
|
iter->k = *k.k;
|
|
/* We're not returning a key from iter->path: */
|
|
goto out_no_locked;
|
|
}
|
|
|
|
k = bch2_btree_path_peek_slot(trans->paths + iter->path, &iter->k);
|
|
if (unlikely(!k.k))
|
|
goto out_no_locked;
|
|
} else {
|
|
struct bpos next;
|
|
struct bpos end = iter->pos;
|
|
|
|
if (iter->flags & BTREE_ITER_IS_EXTENTS)
|
|
end.offset = U64_MAX;
|
|
|
|
EBUG_ON(btree_iter_path(trans, iter)->level);
|
|
|
|
if (iter->flags & BTREE_ITER_INTENT) {
|
|
struct btree_iter iter2;
|
|
|
|
bch2_trans_copy_iter(&iter2, iter);
|
|
k = bch2_btree_iter_peek_upto(&iter2, end);
|
|
|
|
if (k.k && !bkey_err(k)) {
|
|
swap(iter->key_cache_path, iter2.key_cache_path);
|
|
iter->k = iter2.k;
|
|
k.k = &iter->k;
|
|
}
|
|
bch2_trans_iter_exit(trans, &iter2);
|
|
} else {
|
|
struct bpos pos = iter->pos;
|
|
|
|
k = bch2_btree_iter_peek_upto(iter, end);
|
|
if (unlikely(bkey_err(k)))
|
|
bch2_btree_iter_set_pos(iter, pos);
|
|
else
|
|
iter->pos = pos;
|
|
}
|
|
|
|
if (unlikely(bkey_err(k)))
|
|
goto out_no_locked;
|
|
|
|
next = k.k ? bkey_start_pos(k.k) : POS_MAX;
|
|
|
|
if (bkey_lt(iter->pos, next)) {
|
|
bkey_init(&iter->k);
|
|
iter->k.p = iter->pos;
|
|
|
|
if (iter->flags & BTREE_ITER_IS_EXTENTS) {
|
|
bch2_key_resize(&iter->k,
|
|
min_t(u64, KEY_SIZE_MAX,
|
|
(next.inode == iter->pos.inode
|
|
? next.offset
|
|
: KEY_OFFSET_MAX) -
|
|
iter->pos.offset));
|
|
EBUG_ON(!iter->k.size);
|
|
}
|
|
|
|
k = (struct bkey_s_c) { &iter->k, NULL };
|
|
}
|
|
}
|
|
out:
|
|
btree_path_set_should_be_locked(btree_iter_path(trans, iter));
|
|
out_no_locked:
|
|
bch2_btree_iter_verify_entry_exit(iter);
|
|
bch2_btree_iter_verify(iter);
|
|
ret = bch2_btree_iter_verify_ret(iter, k);
|
|
if (unlikely(ret))
|
|
return bkey_s_c_err(ret);
|
|
|
|
return k;
|
|
}
|
|
|
|
struct bkey_s_c bch2_btree_iter_next_slot(struct btree_iter *iter)
|
|
{
|
|
if (!bch2_btree_iter_advance(iter))
|
|
return bkey_s_c_null;
|
|
|
|
return bch2_btree_iter_peek_slot(iter);
|
|
}
|
|
|
|
struct bkey_s_c bch2_btree_iter_prev_slot(struct btree_iter *iter)
|
|
{
|
|
if (!bch2_btree_iter_rewind(iter))
|
|
return bkey_s_c_null;
|
|
|
|
return bch2_btree_iter_peek_slot(iter);
|
|
}
|
|
|
|
struct bkey_s_c bch2_btree_iter_peek_and_restart_outlined(struct btree_iter *iter)
|
|
{
|
|
struct bkey_s_c k;
|
|
|
|
while (btree_trans_too_many_iters(iter->trans) ||
|
|
(k = bch2_btree_iter_peek_type(iter, iter->flags),
|
|
bch2_err_matches(bkey_err(k), BCH_ERR_transaction_restart)))
|
|
bch2_trans_begin(iter->trans);
|
|
|
|
return k;
|
|
}
|
|
|
|
/* new transactional stuff: */
|
|
|
|
#ifdef CONFIG_BCACHEFS_DEBUG
|
|
static void btree_trans_verify_sorted_refs(struct btree_trans *trans)
|
|
{
|
|
struct btree_path *path;
|
|
unsigned i;
|
|
|
|
BUG_ON(trans->nr_sorted != bitmap_weight(trans->paths_allocated, trans->nr_paths) - 1);
|
|
|
|
trans_for_each_path(trans, path, i) {
|
|
BUG_ON(path->sorted_idx >= trans->nr_sorted);
|
|
BUG_ON(trans->sorted[path->sorted_idx] != i);
|
|
}
|
|
|
|
for (i = 0; i < trans->nr_sorted; i++) {
|
|
unsigned idx = trans->sorted[i];
|
|
|
|
BUG_ON(!test_bit(idx, trans->paths_allocated));
|
|
BUG_ON(trans->paths[idx].sorted_idx != i);
|
|
}
|
|
}
|
|
|
|
static void btree_trans_verify_sorted(struct btree_trans *trans)
|
|
{
|
|
struct btree_path *path, *prev = NULL;
|
|
struct trans_for_each_path_inorder_iter iter;
|
|
|
|
if (!bch2_debug_check_iterators)
|
|
return;
|
|
|
|
trans_for_each_path_inorder(trans, path, iter) {
|
|
if (prev && btree_path_cmp(prev, path) > 0) {
|
|
__bch2_dump_trans_paths_updates(trans, true);
|
|
panic("trans paths out of order!\n");
|
|
}
|
|
prev = path;
|
|
}
|
|
}
|
|
#else
|
|
static inline void btree_trans_verify_sorted_refs(struct btree_trans *trans) {}
|
|
static inline void btree_trans_verify_sorted(struct btree_trans *trans) {}
|
|
#endif
|
|
|
|
void __bch2_btree_trans_sort_paths(struct btree_trans *trans)
|
|
{
|
|
int i, l = 0, r = trans->nr_sorted, inc = 1;
|
|
bool swapped;
|
|
|
|
btree_trans_verify_sorted_refs(trans);
|
|
|
|
if (trans->paths_sorted)
|
|
goto out;
|
|
|
|
/*
|
|
* Cocktail shaker sort: this is efficient because iterators will be
|
|
* mostly sorted.
|
|
*/
|
|
do {
|
|
swapped = false;
|
|
|
|
for (i = inc > 0 ? l : r - 2;
|
|
i + 1 < r && i >= l;
|
|
i += inc) {
|
|
if (btree_path_cmp(trans->paths + trans->sorted[i],
|
|
trans->paths + trans->sorted[i + 1]) > 0) {
|
|
swap(trans->sorted[i], trans->sorted[i + 1]);
|
|
trans->paths[trans->sorted[i]].sorted_idx = i;
|
|
trans->paths[trans->sorted[i + 1]].sorted_idx = i + 1;
|
|
swapped = true;
|
|
}
|
|
}
|
|
|
|
if (inc > 0)
|
|
--r;
|
|
else
|
|
l++;
|
|
inc = -inc;
|
|
} while (swapped);
|
|
|
|
trans->paths_sorted = true;
|
|
out:
|
|
btree_trans_verify_sorted(trans);
|
|
}
|
|
|
|
static inline void btree_path_list_remove(struct btree_trans *trans,
|
|
struct btree_path *path)
|
|
{
|
|
EBUG_ON(path->sorted_idx >= trans->nr_sorted);
|
|
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
|
|
trans->nr_sorted--;
|
|
memmove_u64s_down_small(trans->sorted + path->sorted_idx,
|
|
trans->sorted + path->sorted_idx + 1,
|
|
DIV_ROUND_UP(trans->nr_sorted - path->sorted_idx,
|
|
sizeof(u64) / sizeof(btree_path_idx_t)));
|
|
#else
|
|
array_remove_item(trans->sorted, trans->nr_sorted, path->sorted_idx);
|
|
#endif
|
|
for (unsigned i = path->sorted_idx; i < trans->nr_sorted; i++)
|
|
trans->paths[trans->sorted[i]].sorted_idx = i;
|
|
}
|
|
|
|
static inline void btree_path_list_add(struct btree_trans *trans,
|
|
btree_path_idx_t pos,
|
|
btree_path_idx_t path_idx)
|
|
{
|
|
struct btree_path *path = trans->paths + path_idx;
|
|
|
|
path->sorted_idx = pos ? trans->paths[pos].sorted_idx + 1 : trans->nr_sorted;
|
|
|
|
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
|
|
memmove_u64s_up_small(trans->sorted + path->sorted_idx + 1,
|
|
trans->sorted + path->sorted_idx,
|
|
DIV_ROUND_UP(trans->nr_sorted - path->sorted_idx,
|
|
sizeof(u64) / sizeof(btree_path_idx_t)));
|
|
trans->nr_sorted++;
|
|
trans->sorted[path->sorted_idx] = path_idx;
|
|
#else
|
|
array_insert_item(trans->sorted, trans->nr_sorted, path->sorted_idx, path_idx);
|
|
#endif
|
|
|
|
for (unsigned i = path->sorted_idx; i < trans->nr_sorted; i++)
|
|
trans->paths[trans->sorted[i]].sorted_idx = i;
|
|
|
|
btree_trans_verify_sorted_refs(trans);
|
|
}
|
|
|
|
void bch2_trans_iter_exit(struct btree_trans *trans, struct btree_iter *iter)
|
|
{
|
|
if (iter->update_path)
|
|
bch2_path_put_nokeep(trans, iter->update_path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
if (iter->path)
|
|
bch2_path_put(trans, iter->path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
if (iter->key_cache_path)
|
|
bch2_path_put(trans, iter->key_cache_path,
|
|
iter->flags & BTREE_ITER_INTENT);
|
|
iter->path = 0;
|
|
iter->update_path = 0;
|
|
iter->key_cache_path = 0;
|
|
iter->trans = NULL;
|
|
}
|
|
|
|
void bch2_trans_iter_init_outlined(struct btree_trans *trans,
|
|
struct btree_iter *iter,
|
|
enum btree_id btree_id, struct bpos pos,
|
|
unsigned flags)
|
|
{
|
|
bch2_trans_iter_init_common(trans, iter, btree_id, pos, 0, 0,
|
|
bch2_btree_iter_flags(trans, btree_id, flags),
|
|
_RET_IP_);
|
|
}
|
|
|
|
void bch2_trans_node_iter_init(struct btree_trans *trans,
|
|
struct btree_iter *iter,
|
|
enum btree_id btree_id,
|
|
struct bpos pos,
|
|
unsigned locks_want,
|
|
unsigned depth,
|
|
unsigned flags)
|
|
{
|
|
flags |= BTREE_ITER_NOT_EXTENTS;
|
|
flags |= __BTREE_ITER_ALL_SNAPSHOTS;
|
|
flags |= BTREE_ITER_ALL_SNAPSHOTS;
|
|
|
|
bch2_trans_iter_init_common(trans, iter, btree_id, pos, locks_want, depth,
|
|
__bch2_btree_iter_flags(trans, btree_id, flags),
|
|
_RET_IP_);
|
|
|
|
iter->min_depth = depth;
|
|
|
|
struct btree_path *path = btree_iter_path(trans, iter);
|
|
BUG_ON(path->locks_want < min(locks_want, BTREE_MAX_DEPTH));
|
|
BUG_ON(path->level != depth);
|
|
BUG_ON(iter->min_depth != depth);
|
|
}
|
|
|
|
void bch2_trans_copy_iter(struct btree_iter *dst, struct btree_iter *src)
|
|
{
|
|
struct btree_trans *trans = src->trans;
|
|
|
|
*dst = *src;
|
|
#ifdef TRACK_PATH_ALLOCATED
|
|
dst->ip_allocated = _RET_IP_;
|
|
#endif
|
|
if (src->path)
|
|
__btree_path_get(trans->paths + src->path, src->flags & BTREE_ITER_INTENT);
|
|
if (src->update_path)
|
|
__btree_path_get(trans->paths + src->update_path, src->flags & BTREE_ITER_INTENT);
|
|
dst->key_cache_path = 0;
|
|
}
|
|
|
|
void *__bch2_trans_kmalloc(struct btree_trans *trans, size_t size)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
unsigned new_top = trans->mem_top + size;
|
|
unsigned old_bytes = trans->mem_bytes;
|
|
unsigned new_bytes = roundup_pow_of_two(new_top);
|
|
int ret;
|
|
void *new_mem;
|
|
void *p;
|
|
|
|
WARN_ON_ONCE(new_bytes > BTREE_TRANS_MEM_MAX);
|
|
|
|
struct btree_transaction_stats *s = btree_trans_stats(trans);
|
|
s->max_mem = max(s->max_mem, new_bytes);
|
|
|
|
if (trans->used_mempool) {
|
|
if (trans->mem_bytes >= new_bytes)
|
|
goto out_change_top;
|
|
|
|
/* No more space from mempool item, need malloc new one */
|
|
new_mem = kmalloc(new_bytes, GFP_NOWAIT|__GFP_NOWARN);
|
|
if (unlikely(!new_mem)) {
|
|
bch2_trans_unlock(trans);
|
|
|
|
new_mem = kmalloc(new_bytes, GFP_KERNEL);
|
|
if (!new_mem)
|
|
return ERR_PTR(-BCH_ERR_ENOMEM_trans_kmalloc);
|
|
|
|
ret = bch2_trans_relock(trans);
|
|
if (ret) {
|
|
kfree(new_mem);
|
|
return ERR_PTR(ret);
|
|
}
|
|
}
|
|
memcpy(new_mem, trans->mem, trans->mem_top);
|
|
trans->used_mempool = false;
|
|
mempool_free(trans->mem, &c->btree_trans_mem_pool);
|
|
goto out_new_mem;
|
|
}
|
|
|
|
new_mem = krealloc(trans->mem, new_bytes, GFP_NOWAIT|__GFP_NOWARN);
|
|
if (unlikely(!new_mem)) {
|
|
bch2_trans_unlock(trans);
|
|
|
|
new_mem = krealloc(trans->mem, new_bytes, GFP_KERNEL);
|
|
if (!new_mem && new_bytes <= BTREE_TRANS_MEM_MAX) {
|
|
new_mem = mempool_alloc(&c->btree_trans_mem_pool, GFP_KERNEL);
|
|
new_bytes = BTREE_TRANS_MEM_MAX;
|
|
memcpy(new_mem, trans->mem, trans->mem_top);
|
|
trans->used_mempool = true;
|
|
kfree(trans->mem);
|
|
}
|
|
|
|
if (!new_mem)
|
|
return ERR_PTR(-BCH_ERR_ENOMEM_trans_kmalloc);
|
|
|
|
trans->mem = new_mem;
|
|
trans->mem_bytes = new_bytes;
|
|
|
|
ret = bch2_trans_relock(trans);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
}
|
|
out_new_mem:
|
|
trans->mem = new_mem;
|
|
trans->mem_bytes = new_bytes;
|
|
|
|
if (old_bytes) {
|
|
trace_and_count(c, trans_restart_mem_realloced, trans, _RET_IP_, new_bytes);
|
|
return ERR_PTR(btree_trans_restart(trans, BCH_ERR_transaction_restart_mem_realloced));
|
|
}
|
|
out_change_top:
|
|
p = trans->mem + trans->mem_top;
|
|
trans->mem_top += size;
|
|
memset(p, 0, size);
|
|
return p;
|
|
}
|
|
|
|
static inline void check_srcu_held_too_long(struct btree_trans *trans)
|
|
{
|
|
WARN(trans->srcu_held && time_after(jiffies, trans->srcu_lock_time + HZ * 10),
|
|
"btree trans held srcu lock (delaying memory reclaim) for %lu seconds",
|
|
(jiffies - trans->srcu_lock_time) / HZ);
|
|
}
|
|
|
|
void bch2_trans_srcu_unlock(struct btree_trans *trans)
|
|
{
|
|
if (trans->srcu_held) {
|
|
struct bch_fs *c = trans->c;
|
|
struct btree_path *path;
|
|
unsigned i;
|
|
|
|
trans_for_each_path(trans, path, i)
|
|
if (path->cached && !btree_node_locked(path, 0))
|
|
path->l[0].b = ERR_PTR(-BCH_ERR_no_btree_node_srcu_reset);
|
|
|
|
check_srcu_held_too_long(trans);
|
|
srcu_read_unlock(&c->btree_trans_barrier, trans->srcu_idx);
|
|
trans->srcu_held = false;
|
|
}
|
|
}
|
|
|
|
static void bch2_trans_srcu_lock(struct btree_trans *trans)
|
|
{
|
|
if (!trans->srcu_held) {
|
|
trans->srcu_idx = srcu_read_lock(&trans->c->btree_trans_barrier);
|
|
trans->srcu_lock_time = jiffies;
|
|
trans->srcu_held = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* bch2_trans_begin() - reset a transaction after a interrupted attempt
|
|
* @trans: transaction to reset
|
|
*
|
|
* Returns: current restart counter, to be used with trans_was_restarted()
|
|
*
|
|
* While iterating over nodes or updating nodes a attempt to lock a btree node
|
|
* may return BCH_ERR_transaction_restart when the trylock fails. When this
|
|
* occurs bch2_trans_begin() should be called and the transaction retried.
|
|
*/
|
|
u32 bch2_trans_begin(struct btree_trans *trans)
|
|
{
|
|
struct btree_path *path;
|
|
unsigned i;
|
|
u64 now;
|
|
|
|
bch2_trans_reset_updates(trans);
|
|
|
|
trans->restart_count++;
|
|
trans->mem_top = 0;
|
|
trans->journal_entries = NULL;
|
|
|
|
trans_for_each_path(trans, path, i) {
|
|
path->should_be_locked = false;
|
|
|
|
/*
|
|
* If the transaction wasn't restarted, we're presuming to be
|
|
* doing something new: dont keep iterators excpt the ones that
|
|
* are in use - except for the subvolumes btree:
|
|
*/
|
|
if (!trans->restarted && path->btree_id != BTREE_ID_subvolumes)
|
|
path->preserve = false;
|
|
|
|
/*
|
|
* XXX: we probably shouldn't be doing this if the transaction
|
|
* was restarted, but currently we still overflow transaction
|
|
* iterators if we do that
|
|
*/
|
|
if (!path->ref && !path->preserve)
|
|
__bch2_path_free(trans, i);
|
|
else
|
|
path->preserve = false;
|
|
}
|
|
|
|
now = local_clock();
|
|
|
|
if (!IS_ENABLED(CONFIG_BCACHEFS_NO_LATENCY_ACCT) &&
|
|
time_after64(now, trans->last_begin_time + 10))
|
|
__bch2_time_stats_update(&btree_trans_stats(trans)->duration,
|
|
trans->last_begin_time, now);
|
|
|
|
if (!trans->restarted &&
|
|
(need_resched() ||
|
|
time_after64(now, trans->last_begin_time + BTREE_TRANS_MAX_LOCK_HOLD_TIME_NS))) {
|
|
drop_locks_do(trans, (cond_resched(), 0));
|
|
now = local_clock();
|
|
}
|
|
trans->last_begin_time = now;
|
|
|
|
if (unlikely(trans->srcu_held &&
|
|
time_after(jiffies, trans->srcu_lock_time + msecs_to_jiffies(10))))
|
|
bch2_trans_srcu_unlock(trans);
|
|
|
|
trans->last_begin_ip = _RET_IP_;
|
|
if (trans->restarted) {
|
|
bch2_btree_path_traverse_all(trans);
|
|
trans->notrace_relock_fail = false;
|
|
}
|
|
|
|
return trans->restart_count;
|
|
}
|
|
|
|
const char *bch2_btree_transaction_fns[BCH_TRANSACTIONS_NR] = { "(unknown)" };
|
|
|
|
unsigned bch2_trans_get_fn_idx(const char *fn)
|
|
{
|
|
for (unsigned i = 0; i < ARRAY_SIZE(bch2_btree_transaction_fns); i++)
|
|
if (!bch2_btree_transaction_fns[i] ||
|
|
bch2_btree_transaction_fns[i] == fn) {
|
|
bch2_btree_transaction_fns[i] = fn;
|
|
return i;
|
|
}
|
|
|
|
pr_warn_once("BCH_TRANSACTIONS_NR not big enough!");
|
|
return 0;
|
|
}
|
|
|
|
struct btree_trans *__bch2_trans_get(struct bch_fs *c, unsigned fn_idx)
|
|
__acquires(&c->btree_trans_barrier)
|
|
{
|
|
struct btree_trans *trans;
|
|
|
|
if (IS_ENABLED(__KERNEL__)) {
|
|
trans = this_cpu_xchg(c->btree_trans_bufs->trans, NULL);
|
|
if (trans) {
|
|
memset(trans, 0, offsetof(struct btree_trans, list));
|
|
goto got_trans;
|
|
}
|
|
}
|
|
|
|
trans = mempool_alloc(&c->btree_trans_pool, GFP_NOFS);
|
|
memset(trans, 0, sizeof(*trans));
|
|
closure_init_stack(&trans->ref);
|
|
|
|
seqmutex_lock(&c->btree_trans_lock);
|
|
if (IS_ENABLED(CONFIG_BCACHEFS_DEBUG)) {
|
|
struct btree_trans *pos;
|
|
pid_t pid = current->pid;
|
|
|
|
trans->locking_wait.task = current;
|
|
|
|
list_for_each_entry(pos, &c->btree_trans_list, list) {
|
|
struct task_struct *pos_task = READ_ONCE(pos->locking_wait.task);
|
|
/*
|
|
* We'd much prefer to be stricter here and completely
|
|
* disallow multiple btree_trans in the same thread -
|
|
* but the data move path calls bch2_write when we
|
|
* already have a btree_trans initialized.
|
|
*/
|
|
BUG_ON(pos_task &&
|
|
pid == pos_task->pid &&
|
|
bch2_trans_locked(pos));
|
|
|
|
if (pos_task && pid < pos_task->pid) {
|
|
list_add_tail(&trans->list, &pos->list);
|
|
goto list_add_done;
|
|
}
|
|
}
|
|
}
|
|
list_add_tail(&trans->list, &c->btree_trans_list);
|
|
list_add_done:
|
|
seqmutex_unlock(&c->btree_trans_lock);
|
|
got_trans:
|
|
trans->c = c;
|
|
trans->last_begin_time = local_clock();
|
|
trans->fn_idx = fn_idx;
|
|
trans->locking_wait.task = current;
|
|
trans->journal_replay_not_finished =
|
|
unlikely(!test_bit(JOURNAL_REPLAY_DONE, &c->journal.flags)) &&
|
|
atomic_inc_not_zero(&c->journal_keys.ref);
|
|
trans->nr_paths = ARRAY_SIZE(trans->_paths);
|
|
trans->paths_allocated = trans->_paths_allocated;
|
|
trans->sorted = trans->_sorted;
|
|
trans->paths = trans->_paths;
|
|
trans->updates = trans->_updates;
|
|
|
|
*trans_paths_nr(trans->paths) = BTREE_ITER_INITIAL;
|
|
|
|
trans->paths_allocated[0] = 1;
|
|
|
|
if (fn_idx < BCH_TRANSACTIONS_NR) {
|
|
trans->fn = bch2_btree_transaction_fns[fn_idx];
|
|
|
|
struct btree_transaction_stats *s = &c->btree_transaction_stats[fn_idx];
|
|
|
|
if (s->max_mem) {
|
|
unsigned expected_mem_bytes = roundup_pow_of_two(s->max_mem);
|
|
|
|
trans->mem = kmalloc(expected_mem_bytes, GFP_KERNEL);
|
|
if (likely(trans->mem))
|
|
trans->mem_bytes = expected_mem_bytes;
|
|
}
|
|
|
|
trans->nr_paths_max = s->nr_max_paths;
|
|
trans->journal_entries_size = s->journal_entries_size;
|
|
}
|
|
|
|
trans->srcu_idx = srcu_read_lock(&c->btree_trans_barrier);
|
|
trans->srcu_lock_time = jiffies;
|
|
trans->srcu_held = true;
|
|
return trans;
|
|
}
|
|
|
|
static void check_btree_paths_leaked(struct btree_trans *trans)
|
|
{
|
|
#ifdef CONFIG_BCACHEFS_DEBUG
|
|
struct bch_fs *c = trans->c;
|
|
struct btree_path *path;
|
|
unsigned i;
|
|
|
|
trans_for_each_path(trans, path, i)
|
|
if (path->ref)
|
|
goto leaked;
|
|
return;
|
|
leaked:
|
|
bch_err(c, "btree paths leaked from %s!", trans->fn);
|
|
trans_for_each_path(trans, path, i)
|
|
if (path->ref)
|
|
printk(KERN_ERR " btree %s %pS\n",
|
|
bch2_btree_id_str(path->btree_id),
|
|
(void *) path->ip_allocated);
|
|
/* Be noisy about this: */
|
|
bch2_fatal_error(c);
|
|
#endif
|
|
}
|
|
|
|
void bch2_trans_put(struct btree_trans *trans)
|
|
__releases(&c->btree_trans_barrier)
|
|
{
|
|
struct bch_fs *c = trans->c;
|
|
|
|
bch2_trans_unlock(trans);
|
|
|
|
trans_for_each_update(trans, i)
|
|
__btree_path_put(trans->paths + i->path, true);
|
|
trans->nr_updates = 0;
|
|
trans->locking_wait.task = NULL;
|
|
|
|
check_btree_paths_leaked(trans);
|
|
|
|
if (trans->srcu_held) {
|
|
check_srcu_held_too_long(trans);
|
|
srcu_read_unlock(&c->btree_trans_barrier, trans->srcu_idx);
|
|
}
|
|
|
|
if (trans->fs_usage_deltas) {
|
|
if (trans->fs_usage_deltas->size + sizeof(trans->fs_usage_deltas) ==
|
|
REPLICAS_DELTA_LIST_MAX)
|
|
mempool_free(trans->fs_usage_deltas,
|
|
&c->replicas_delta_pool);
|
|
else
|
|
kfree(trans->fs_usage_deltas);
|
|
}
|
|
|
|
if (unlikely(trans->journal_replay_not_finished))
|
|
bch2_journal_keys_put(c);
|
|
|
|
unsigned long *paths_allocated = trans->paths_allocated;
|
|
trans->paths_allocated = NULL;
|
|
trans->paths = NULL;
|
|
|
|
if (paths_allocated != trans->_paths_allocated)
|
|
kvfree_rcu_mightsleep(paths_allocated);
|
|
|
|
if (trans->used_mempool)
|
|
mempool_free(trans->mem, &c->btree_trans_mem_pool);
|
|
else
|
|
kfree(trans->mem);
|
|
|
|
/* Userspace doesn't have a real percpu implementation: */
|
|
if (IS_ENABLED(__KERNEL__))
|
|
trans = this_cpu_xchg(c->btree_trans_bufs->trans, trans);
|
|
|
|
if (trans) {
|
|
closure_sync(&trans->ref);
|
|
|
|
seqmutex_lock(&c->btree_trans_lock);
|
|
list_del(&trans->list);
|
|
seqmutex_unlock(&c->btree_trans_lock);
|
|
|
|
mempool_free(trans, &c->btree_trans_pool);
|
|
}
|
|
}
|
|
|
|
static void __maybe_unused
|
|
bch2_btree_bkey_cached_common_to_text(struct printbuf *out,
|
|
struct btree_bkey_cached_common *b)
|
|
{
|
|
struct six_lock_count c = six_lock_counts(&b->lock);
|
|
struct task_struct *owner;
|
|
pid_t pid;
|
|
|
|
rcu_read_lock();
|
|
owner = READ_ONCE(b->lock.owner);
|
|
pid = owner ? owner->pid : 0;
|
|
rcu_read_unlock();
|
|
|
|
prt_tab(out);
|
|
prt_printf(out, "%px %c l=%u %s:", b, b->cached ? 'c' : 'b',
|
|
b->level, bch2_btree_id_str(b->btree_id));
|
|
bch2_bpos_to_text(out, btree_node_pos(b));
|
|
|
|
prt_tab(out);
|
|
prt_printf(out, " locks %u:%u:%u held by pid %u",
|
|
c.n[0], c.n[1], c.n[2], pid);
|
|
}
|
|
|
|
void bch2_btree_trans_to_text(struct printbuf *out, struct btree_trans *trans)
|
|
{
|
|
struct btree_bkey_cached_common *b;
|
|
static char lock_types[] = { 'r', 'i', 'w' };
|
|
struct task_struct *task = READ_ONCE(trans->locking_wait.task);
|
|
unsigned l, idx;
|
|
|
|
/* before rcu_read_lock(): */
|
|
bch2_printbuf_make_room(out, 4096);
|
|
|
|
if (!out->nr_tabstops) {
|
|
printbuf_tabstop_push(out, 16);
|
|
printbuf_tabstop_push(out, 32);
|
|
}
|
|
|
|
prt_printf(out, "%i %s\n", task ? task->pid : 0, trans->fn);
|
|
|
|
/* trans->paths is rcu protected vs. freeing */
|
|
rcu_read_lock();
|
|
out->atomic++;
|
|
|
|
struct btree_path *paths = rcu_dereference(trans->paths);
|
|
if (!paths)
|
|
goto out;
|
|
|
|
unsigned long *paths_allocated = trans_paths_allocated(paths);
|
|
|
|
trans_for_each_path_idx_from(paths_allocated, *trans_paths_nr(paths), idx, 1) {
|
|
struct btree_path *path = paths + idx;
|
|
if (!path->nodes_locked)
|
|
continue;
|
|
|
|
prt_printf(out, " path %u %c l=%u %s:",
|
|
idx,
|
|
path->cached ? 'c' : 'b',
|
|
path->level,
|
|
bch2_btree_id_str(path->btree_id));
|
|
bch2_bpos_to_text(out, path->pos);
|
|
prt_newline(out);
|
|
|
|
for (l = 0; l < BTREE_MAX_DEPTH; l++) {
|
|
if (btree_node_locked(path, l) &&
|
|
!IS_ERR_OR_NULL(b = (void *) READ_ONCE(path->l[l].b))) {
|
|
prt_printf(out, " %c l=%u ",
|
|
lock_types[btree_node_locked_type(path, l)], l);
|
|
bch2_btree_bkey_cached_common_to_text(out, b);
|
|
prt_newline(out);
|
|
}
|
|
}
|
|
}
|
|
|
|
b = READ_ONCE(trans->locking);
|
|
if (b) {
|
|
prt_printf(out, " blocked for %lluus on",
|
|
div_u64(local_clock() - trans->locking_wait.start_time,
|
|
1000));
|
|
prt_newline(out);
|
|
prt_printf(out, " %c", lock_types[trans->locking_wait.lock_want]);
|
|
bch2_btree_bkey_cached_common_to_text(out, b);
|
|
prt_newline(out);
|
|
}
|
|
out:
|
|
--out->atomic;
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
void bch2_fs_btree_iter_exit(struct bch_fs *c)
|
|
{
|
|
struct btree_transaction_stats *s;
|
|
struct btree_trans *trans;
|
|
int cpu;
|
|
|
|
if (c->btree_trans_bufs)
|
|
for_each_possible_cpu(cpu) {
|
|
struct btree_trans *trans =
|
|
per_cpu_ptr(c->btree_trans_bufs, cpu)->trans;
|
|
|
|
if (trans) {
|
|
closure_sync(&trans->ref);
|
|
|
|
seqmutex_lock(&c->btree_trans_lock);
|
|
list_del(&trans->list);
|
|
seqmutex_unlock(&c->btree_trans_lock);
|
|
}
|
|
kfree(trans);
|
|
}
|
|
free_percpu(c->btree_trans_bufs);
|
|
|
|
trans = list_first_entry_or_null(&c->btree_trans_list, struct btree_trans, list);
|
|
if (trans)
|
|
panic("%s leaked btree_trans\n", trans->fn);
|
|
|
|
for (s = c->btree_transaction_stats;
|
|
s < c->btree_transaction_stats + ARRAY_SIZE(c->btree_transaction_stats);
|
|
s++) {
|
|
kfree(s->max_paths_text);
|
|
bch2_time_stats_exit(&s->lock_hold_times);
|
|
}
|
|
|
|
if (c->btree_trans_barrier_initialized)
|
|
cleanup_srcu_struct(&c->btree_trans_barrier);
|
|
mempool_exit(&c->btree_trans_mem_pool);
|
|
mempool_exit(&c->btree_trans_pool);
|
|
}
|
|
|
|
void bch2_fs_btree_iter_init_early(struct bch_fs *c)
|
|
{
|
|
struct btree_transaction_stats *s;
|
|
|
|
for (s = c->btree_transaction_stats;
|
|
s < c->btree_transaction_stats + ARRAY_SIZE(c->btree_transaction_stats);
|
|
s++) {
|
|
bch2_time_stats_init(&s->duration);
|
|
bch2_time_stats_init(&s->lock_hold_times);
|
|
mutex_init(&s->lock);
|
|
}
|
|
|
|
INIT_LIST_HEAD(&c->btree_trans_list);
|
|
seqmutex_init(&c->btree_trans_lock);
|
|
}
|
|
|
|
int bch2_fs_btree_iter_init(struct bch_fs *c)
|
|
{
|
|
int ret;
|
|
|
|
c->btree_trans_bufs = alloc_percpu(struct btree_trans_buf);
|
|
if (!c->btree_trans_bufs)
|
|
return -ENOMEM;
|
|
|
|
ret = mempool_init_kmalloc_pool(&c->btree_trans_pool, 1,
|
|
sizeof(struct btree_trans)) ?:
|
|
mempool_init_kmalloc_pool(&c->btree_trans_mem_pool, 1,
|
|
BTREE_TRANS_MEM_MAX) ?:
|
|
init_srcu_struct(&c->btree_trans_barrier);
|
|
if (!ret)
|
|
c->btree_trans_barrier_initialized = true;
|
|
return ret;
|
|
}
|