]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: report read-modify-write of uncovered stripes
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Tue, 4 Aug 2026 19:51:54 +0000 (15:51 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:02 +0000 (17:40 -0400)
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
fs/btrfs/fs.h
fs/btrfs/raid56.c
fs/btrfs/sysfs.c

index 8166f5eaff1ce6534775494d45b49dddee06f2f4..cde856f5da6bf320bb8ca029345a95ebb6fa6959 100644 (file)
@@ -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;
index 5972f76281629e68df39bd0180546a3ee3d3f180..d7495c6a391d71125425d50cd8d7e3ef6f8660c8 100644 (file)
@@ -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
index 8460311f4b82005b240a933b2b86d9921a26cb76..8529b57a8f556b3e3797125192c33a650762da6c 100644 (file)
@@ -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);