diff --git a/kmod/src/btree.c b/kmod/src/btree.c index f2e16924..96a20ef8 100644 --- a/kmod/src/btree.c +++ b/kmod/src/btree.c @@ -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; +} diff --git a/kmod/src/btree.h b/kmod/src/btree.h index 63447669..3d27fec2 100644 --- a/kmod/src/btree.h +++ b/kmod/src/btree.h @@ -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 diff --git a/kmod/src/scoutfs_trace.h b/kmod/src/scoutfs_trace.h index 4e58ef7a..fb5ea548 100644 --- a/kmod/src/scoutfs_trace.h +++ b/kmod/src/scoutfs_trace.h @@ -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),