Add scoutfs_btree_free_blocks

Add a btree function for freeing all the blocks in a btree without
having to cow the blocks to track which refs have been freed.  We use a
key from the caller to track which portions of the tree have been freed.

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2021-06-17 09:36:00 -07:00
parent 3488b4e6e0
commit 3d1a0f06c0
3 changed files with 211 additions and 0 deletions
+163
View File
@@ -2173,3 +2173,166 @@ out:
return ret;
}
/*
* Free all the blocks referenced by a btree. The btree is only read,
* this does not update the blocks as it frees. The caller ensures that
* these btrees aren't been modified.
*
* The caller's key tracks which blocks have been freed. It must be
* initialized to zeros before the first call to start freeing blocks.
* Once a block is freed the key is updated such that the freed block
* will not be read again.
*
* Returns 0 when progress has been made successfully, which includes
* partial progress. The key is set to all ones once we've freed all
* the blocks.
*
* This works by descending to the last parent block and freeing all its
* leaf blocks without reading them. As it descends it remembers the
* number of parent blocks which were traversed through their final
* child ref. If we free all the leaf blocks then all these parent
* blocks are no longer needed and can be freed. The caller's key is
* updated to past the subtree that we just freed and we retry the
* descent from the root through the next set of parents to the next set
* of leaf blocks to free.
*/
int scoutfs_btree_free_blocks(struct super_block *sb,
struct scoutfs_alloc *alloc,
struct scoutfs_block_writer *wri,
struct scoutfs_key *key,
struct scoutfs_btree_root *root, int alloc_low)
{
u64 blknos[SCOUTFS_BTREE_MAX_HEIGHT];
struct scoutfs_block *bl = NULL;
struct scoutfs_btree_item *item;
struct scoutfs_btree_block *bt;
struct scoutfs_block_ref ref;
struct scoutfs_avl_node *node;
struct scoutfs_avl_node *next;
struct scoutfs_key par_next;
int nr_par;
int level;
int ret;
int i;
if (WARN_ON_ONCE(root->height > ARRAY_SIZE(blknos)))
return -EIO; /* XXX corruption */
if (root->height == 0) {
scoutfs_key_set_ones(key);
return 0;
}
if (scoutfs_key_is_ones(key))
return 0;
/* just free a single leaf block */
if (root->height == 1) {
ret = scoutfs_free_meta(sb, alloc, wri,
le64_to_cpu(root->ref.blkno));
if (ret == 0) {
trace_scoutfs_btree_free_blocks_single(sb, root,
le64_to_cpu(root->ref.blkno));
scoutfs_key_set_ones(key);
}
goto out;
}
for (;;) {
/* start the walk at the root block */
level = root->height - 1;
ref = root->ref;
scoutfs_key_set_ones(&par_next);
nr_par = 0;
/* read blocks until we read the last parent */
for (;;) {
scoutfs_block_put(sb, bl);
bl = NULL;
ret = get_ref_block(sb, alloc, wri, 0, &ref, &bl);
if (ret < 0)
goto out;
bt = bl->data;
node = scoutfs_avl_search(&bt->item_root, cmp_key_item,
key, NULL, NULL, &next, NULL);
if (node == NULL)
node = next;
/* should never descend into parent with no more refs */
if (WARN_ON_ONCE(node == NULL)) {
ret = -EIO;
goto out;
}
/* we'll free refs in the last parent */
if (level == 1)
break;
item = node_item(node);
next = scoutfs_avl_next(&bt->item_root, node);
if (next) {
/* didn't take last ref, still need parents */
nr_par = 0;
par_next = *item_key(item);
scoutfs_key_inc(&par_next);
} else {
/* final ref, could free after all leaves */
blknos[nr_par++] = le64_to_cpu(bt->hdr.blkno);
}
memcpy(&ref, item_val(bt, item), sizeof(ref));
level--;
}
/* free all leaf block refs in last parent */
while (node) {
/* make sure we can always free parents after leaves */
if (scoutfs_alloc_meta_low(sb, alloc,
alloc_low + nr_par + 1)) {
ret = 0;
goto out;
}
item = node_item(node);
memcpy(&ref, item_val(bt, item), sizeof(ref));
trace_scoutfs_btree_free_blocks_leaf(sb, root,
le64_to_cpu(ref.blkno));
ret = scoutfs_free_meta(sb, alloc, wri,
le64_to_cpu(ref.blkno));
if (ret < 0)
goto out;
node = scoutfs_avl_next(&bt->item_root, node);
if (node) {
/* done with keys in child we just freed */
*key = *item_key(item);
scoutfs_key_inc(key);
}
}
/* now that leaves are freed, free any empty parents */
for (i = 0; i < nr_par; i++) {
trace_scoutfs_btree_free_blocks_parent(sb, root,
blknos[i]);
ret = scoutfs_free_meta(sb, alloc, wri, blknos[i]);
BUG_ON(ret); /* checked meta low, freed should fit */
}
/* restart walk past the subtree we just freed */
*key = par_next;
/* but done if we just freed all parents down right spine */
if (scoutfs_key_is_ones(&par_next)) {
ret = 0;
goto out;
}
}
out:
scoutfs_block_put(sb, bl);
return ret;
}
+6
View File
@@ -128,6 +128,12 @@ int scoutfs_btree_merge(struct super_block *sb,
scoutfs_btree_merge_is_del_t merge_is_del, bool subtree,
int drop_val, int dirty_limit, int alloc_low);
int scoutfs_btree_free_blocks(struct super_block *sb,
struct scoutfs_alloc *alloc,
struct scoutfs_block_writer *wri,
struct scoutfs_key *key,
struct scoutfs_btree_root *root, int alloc_low);
void scoutfs_btree_put_iref(struct scoutfs_btree_item_ref *iref);
#endif
+42
View File
@@ -1760,6 +1760,48 @@ TRACE_EVENT(scoutfs_btree_merge_items,
sk_trace_args(f_key), __entry->f_val_len, __entry->is_del)
);
DECLARE_EVENT_CLASS(scoutfs_btree_free_blocks,
TP_PROTO(struct super_block *sb, struct scoutfs_btree_root *root,
u64 blkno),
TP_ARGS(sb, root, blkno),
TP_STRUCT__entry(
SCSB_TRACE_FIELDS
__field(__u64, root_blkno)
__field(__u64, root_seq)
__field(__u8, root_height)
__field(__u64, blkno)
),
TP_fast_assign(
SCSB_TRACE_ASSIGN(sb);
__entry->root_blkno = le64_to_cpu(root->ref.blkno);
__entry->root_seq = le64_to_cpu(root->ref.seq);
__entry->root_height = root->height;
__entry->blkno = blkno;
),
TP_printk(SCSBF" root blkno %llu seq %llu height %u, free blkno %llu",
SCSB_TRACE_ARGS, __entry->root_blkno, __entry->root_seq,
__entry->root_height, __entry->blkno)
);
DEFINE_EVENT(scoutfs_btree_free_blocks, scoutfs_btree_free_blocks_single,
TP_PROTO(struct super_block *sb, struct scoutfs_btree_root *root,
u64 blkno),
TP_ARGS(sb, root, blkno)
);
DEFINE_EVENT(scoutfs_btree_free_blocks, scoutfs_btree_free_blocks_leaf,
TP_PROTO(struct super_block *sb, struct scoutfs_btree_root *root,
u64 blkno),
TP_ARGS(sb, root, blkno)
);
DEFINE_EVENT(scoutfs_btree_free_blocks, scoutfs_btree_free_blocks_parent,
TP_PROTO(struct super_block *sb, struct scoutfs_btree_root *root,
u64 blkno),
TP_ARGS(sb, root, blkno)
);
TRACE_EVENT(scoutfs_online_offline_blocks,
TP_PROTO(struct inode *inode, s64 on_delta, s64 off_delta,
u64 on_now, u64 off_now),