From 5e2009f939240ebadb29292ea0e3b7ff452341a1 Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Wed, 8 Apr 2026 13:27:42 -0700 Subject: [PATCH] Avoid double counting deltas from non-input finalized log trees. Readers currently accumulate all finalized log tree deltas into a single bucket for deciding whether they are already in fs_root or not, but, finalized trees that aren't inputs to a current merge will have higher seqs, and thus we may be double applying deltas already merged into fs_root. To distinguish, scoutfs_totl_merge_contribute() needs to know the merge status item seq. We change wkic's get_roots() from using the SCOUTFS_NET_CMD_GET_ROOTS RPC to reading the superblock directly. This is needed because totl merge resolution has to use the same data as the btree roots it is operating on, thus we can't grab it from a SCOUTFS_NET_CMD_GET_ROOTS packet - it likely is different. Signed-off-by: Auke Kok --- kmod/src/forest.c | 12 ++++--- kmod/src/forest.h | 6 ++-- kmod/src/wkic.c | 81 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 77 insertions(+), 22 deletions(-) diff --git a/kmod/src/forest.c b/kmod/src/forest.c index fee22785..38681273 100644 --- a/kmod/src/forest.c +++ b/kmod/src/forest.c @@ -239,9 +239,9 @@ static int forest_read_items(struct super_block *sb, struct scoutfs_key *key, u6 * to reset their state and retry with a newer version of the btrees. */ int scoutfs_forest_read_items_roots(struct super_block *sb, struct scoutfs_net_roots *roots, - struct scoutfs_key *key, struct scoutfs_key *bloom_key, - struct scoutfs_key *start, struct scoutfs_key *end, - scoutfs_forest_item_cb cb, void *arg) + u64 merge_input_seq, struct scoutfs_key *key, + struct scoutfs_key *bloom_key, struct scoutfs_key *start, + struct scoutfs_key *end, scoutfs_forest_item_cb cb, void *arg) { struct forest_read_items_data rid = { .cb = cb, @@ -317,7 +317,9 @@ int scoutfs_forest_read_items_roots(struct super_block *sb, struct scoutfs_net_r scoutfs_inc_counter(sb, forest_bloom_pass); - if ((le64_to_cpu(lt.flags) & SCOUTFS_LOG_TREES_FINALIZED)) + if ((le64_to_cpu(lt.flags) & SCOUTFS_LOG_TREES_FINALIZED) && + (merge_input_seq == 0 || + le64_to_cpu(lt.finalize_seq) < merge_input_seq)) rid.fic |= FIC_MERGE_INPUT; ret = scoutfs_btree_read_items(sb, <.item_root, key, start, @@ -345,7 +347,7 @@ int scoutfs_forest_read_items(struct super_block *sb, ret = scoutfs_client_get_roots(sb, &roots); if (ret == 0) - ret = scoutfs_forest_read_items_roots(sb, &roots, key, bloom_key, start, end, + ret = scoutfs_forest_read_items_roots(sb, &roots, 0, key, bloom_key, start, end, cb, arg); return ret; } diff --git a/kmod/src/forest.h b/kmod/src/forest.h index 0a11a14b..a3d5e4b2 100644 --- a/kmod/src/forest.h +++ b/kmod/src/forest.h @@ -25,9 +25,9 @@ int scoutfs_forest_read_items(struct super_block *sb, struct scoutfs_key *end, scoutfs_forest_item_cb cb, void *arg); int scoutfs_forest_read_items_roots(struct super_block *sb, struct scoutfs_net_roots *roots, - struct scoutfs_key *key, struct scoutfs_key *bloom_key, - struct scoutfs_key *start, struct scoutfs_key *end, - scoutfs_forest_item_cb cb, void *arg); + u64 merge_input_seq, struct scoutfs_key *key, + struct scoutfs_key *bloom_key, struct scoutfs_key *start, + struct scoutfs_key *end, scoutfs_forest_item_cb cb, void *arg); int scoutfs_forest_set_bloom_bits(struct super_block *sb, struct scoutfs_lock *lock); void scoutfs_forest_set_max_seq(struct super_block *sb, u64 max_seq); diff --git a/kmod/src/wkic.c b/kmod/src/wkic.c index 58737c62..eee77f95 100644 --- a/kmod/src/wkic.c +++ b/kmod/src/wkic.c @@ -95,6 +95,7 @@ struct wkic_info { /* block reading slow path */ struct mutex roots_mutex; struct scoutfs_net_roots roots; + u64 merge_input_seq; u64 roots_read_seq; ktime_t roots_expire; @@ -805,29 +806,79 @@ static void free_page_list(struct super_block *sb, struct list_head *list) * read_seq number so that we can compare the age of the items in cached * pages. Only one request to refresh the roots is in progress at a * time. This is the slow path that's only used when the cache isn't - * populated and the roots aren't cached. The root request is fast - * enough, especially compared to the resulting item reading IO, that we - * don't mind hiding it behind a trivial mutex. + * populated and the roots aren't cached. + * + * We read roots directly from the on-disk superblock rather than + * requesting them from the server so that we can also read the + * log_merge btree from the same superblock. The merge status item + * seq tells us which finalized log trees are inputs to the current + * merge, which is needed to correctly resolve totl delta items. */ +static int refresh_roots(struct super_block *sb, struct wkic_info *winf) +{ + struct scoutfs_super_block *super; + struct scoutfs_log_merge_status *stat; + SCOUTFS_BTREE_ITEM_REF(iref); + struct scoutfs_key key; + int ret; + + super = kmalloc(sizeof(*super), GFP_NOFS); + if (!super) + return -ENOMEM; + + ret = scoutfs_read_super(sb, super); + if (ret < 0) + goto out; + + winf->roots = (struct scoutfs_net_roots){ + .fs_root = super->fs_root, + .logs_root = super->logs_root, + .srch_root = super->srch_root, + }; + + winf->merge_input_seq = 0; + if (super->log_merge.ref.blkno) { + scoutfs_key_set_zeros(&key); + key.sk_zone = SCOUTFS_LOG_MERGE_STATUS_ZONE; + ret = scoutfs_btree_lookup(sb, &super->log_merge, &key, &iref); + if (ret == 0) { + if (iref.val_len == sizeof(*stat)) { + stat = iref.val; + winf->merge_input_seq = le64_to_cpu(stat->seq); + } else { + ret = -EUCLEAN; + } + scoutfs_btree_put_iref(&iref); + } else if (ret == -ENOENT) { + ret = 0; + } + if (ret < 0) + goto out; + } + + winf->roots_read_seq++; + winf->roots_expire = ktime_add_ms(ktime_get_raw(), WKIC_CACHE_LIFETIME_MS); +out: + kfree(super); + return ret; +} + static int get_roots(struct super_block *sb, struct wkic_info *winf, - struct scoutfs_net_roots *roots_ret, u64 *read_seq, bool force_new) + struct scoutfs_net_roots *roots_ret, u64 *merge_input_seq, + u64 *read_seq, bool force_new) { - struct scoutfs_net_roots roots; int ret; mutex_lock(&winf->roots_mutex); if (force_new || ktime_before(winf->roots_expire, ktime_get_raw())) { - ret = scoutfs_client_get_roots(sb, &roots); + ret = refresh_roots(sb, winf); if (ret < 0) goto out; - - winf->roots = roots; - winf->roots_read_seq++; - winf->roots_expire = ktime_add_ms(ktime_get_raw(), WKIC_CACHE_LIFETIME_MS); } *roots_ret = winf->roots; + *merge_input_seq = winf->merge_input_seq; *read_seq = winf->roots_read_seq; ret = 0; out: @@ -870,20 +921,22 @@ static int insert_read_pages(struct super_block *sb, struct wkic_info *winf, struct scoutfs_key end; struct wkic_page *wpage; LIST_HEAD(pages); + u64 merge_input_seq; u64 read_seq; int ret; ret = 0; retry_stale: - ret = get_roots(sb, winf, &roots, &read_seq, ret == -ESTALE); + ret = get_roots(sb, winf, &roots, &merge_input_seq, &read_seq, ret == -ESTALE); if (ret < 0) - goto out; + goto check_stale; start = *range_start; end = *range_end; - ret = scoutfs_forest_read_items_roots(sb, &roots, key, range_start, &start, &end, - read_items_cb, &root); + ret = scoutfs_forest_read_items_roots(sb, &roots, merge_input_seq, key, range_start, + &start, &end, read_items_cb, &root); trace_scoutfs_wkic_read_items(sb, key, &start, &end); +check_stale: ret = scoutfs_block_check_stale(sb, ret, &saved, &roots.fs_root.ref, &roots.logs_root.ref); if (ret < 0) { if (ret == -ESTALE) {