From: Zygo Blaxell Date: Tue, 4 Aug 2026 19:51:54 +0000 (-0400) Subject: btrfs: stripe_alloc: report read-modify-write of uncovered stripes X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fac1ef7207bab04f4160ae0aa4c64f29aece7bf5;p=linux btrfs: stripe_alloc: report read-modify-write of uncovered stripes The mount-time warning says which block groups stripe_alloc does not cover. It cannot say whether anything is actually landing there, and on a filesystem with raid56 metadata that is the interesting question: every sub-stripe write to an uncovered stripe is a write hole window, where parity and data reach the disk separately and a crash in between leaves the stripe unreconstructible. Report it from the one place that knows the write is going out as a read-modify-write rather than as a full or padded stripe. Rate limited, because a raid56-metadata filesystem does this continuously and the point is to make the exposure visible rather than to fill the log, and paired with a meta_rmw counter in the existing stripe_park_stats sysfs file so the rate can be read off without grepping dmesg. Silent when stripe_alloc is off: there the whole filesystem works this way and the user has asked for nothing else. Assisted-by: Claude:claude-fable-5 --- diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 8166f5eaff1ce..cde856f5da6bf 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -771,6 +771,7 @@ struct btrfs_fs_info { atomic64_t kicked; atomic64_t rmw_reads; atomic64_t padded; + atomic64_t meta_rmw; } stripe_park_stats; /* BTRFS_STRIPE_RMW_* masks; see btrfs_stripe_allow_rmw(). */ u32 stripe_rmw_opt; diff --git a/fs/btrfs/raid56.c b/fs/btrfs/raid56.c index 5972f76281629..d7495c6a391d7 100644 --- a/fs/btrfs/raid56.c +++ b/fs/btrfs/raid56.c @@ -2684,6 +2684,43 @@ static bool need_read_stripe_sectors(struct btrfs_raid_bio *rbio) return false; } +/* + * Report a sub-stripe write that is going out as a read-modify-write. + * + * With stripe_alloc on, data never gets here: it is either a full stripe or + * padded up to one. Metadata, system chunks and anything in a mixed block + * group are not covered by the allocator, so those still update stripes in + * place -- and every such write is a write hole window, where parity and + * data reach the disk separately and a crash between them leaves the stripe + * unreconstructible. The mount-time warning says the exposure exists; this + * says it is actually happening, and how often. + * + * Rate limited: a raid56-metadata filesystem does this continuously, and + * the point is to make the exposure visible, not to fill the log. Silent + * when stripe_alloc is off, where the entire filesystem works this way and + * the user has asked for nothing else. + */ +static void report_uncovered_rmw(struct btrfs_raid_bio *rbio) +{ + struct btrfs_fs_info *fs_info = rbio->bioc->fs_info; + + if (!btrfs_test_opt(fs_info, STRIPE_ALLOC)) + return; + /* + * The exact complement of btrfs_is_stripe_alloc_bg(): everything + * that is not data-without-metadata. Note this includes system + * chunks, which carry no METADATA bit but are the chunk tree. + */ + if ((rbio->bioc->map_type & BTRFS_BLOCK_GROUP_DATA) && + !(rbio->bioc->map_type & BTRFS_BLOCK_GROUP_METADATA)) + return; + + atomic64_inc(&fs_info->stripe_park_stats.meta_rmw); + btrfs_warn_rl(fs_info, +"read-modify-write of full stripe %llu: this block group is not covered by stripe_alloc, the raid56 write hole applies to it", + rbio->bioc->full_stripe_logical); +} + static void rmw_rbio(struct btrfs_raid_bio *rbio) { struct bio_list bio_list; @@ -2746,6 +2783,10 @@ static void rmw_rbio(struct btrfs_raid_bio *rbio) goto out; } + /* Still a sub-stripe write: this stripe is being modified in place. */ + if (!rbio_is_full(rbio) && !test_bit(RBIO_PADDED_BIT, &rbio->flags)) + report_uncovered_rmw(rbio); + /* * At this stage we're not allowed to add any new bios to the * bio list any more, anyone else that wants to change this stripe diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index 8460311f4b820..8529b57a8f556 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -1235,14 +1235,16 @@ static ssize_t btrfs_stripe_park_stats_show(struct kobject *kobj, "kicked %lld\n" "expired %lld\n" "padded %lld\n" - "rmw_reads %lld\n", + "rmw_reads %lld\n" + "meta_rmw %lld\n", atomic64_read(&fs_info->stripe_park_stats.parked), atomic64_read(&fs_info->stripe_park_stats.merged_bytes), atomic64_read(&fs_info->stripe_park_stats.filled), atomic64_read(&fs_info->stripe_park_stats.kicked), atomic64_read(&fs_info->stripe_park_stats.expired), atomic64_read(&fs_info->stripe_park_stats.padded), - atomic64_read(&fs_info->stripe_park_stats.rmw_reads)); + atomic64_read(&fs_info->stripe_park_stats.rmw_reads), + atomic64_read(&fs_info->stripe_park_stats.meta_rmw)); } BTRFS_ATTR(, stripe_park_stats, btrfs_stripe_park_stats_show);