return false;
}
+/*
+ * Read the header of the tree block at data-stripe sector @i, if there is
+ * one, from whichever copy of the sector this rbio is holding.
+ *
+ * @from_bio picks the source: the caller's pages (what this write is putting
+ * there) or the stripe pages (what an RMW read found on the disk). Returns
+ * false when the sector holds no tree block that belongs at this address --
+ * free space, or the remains of one from a previous life of the stripe.
+ */
+static bool rmw_peek_eb_generation(struct btrfs_raid_bio *rbio, u32 i,
+ bool from_bio, u64 *generation)
+{
+ struct btrfs_fs_info *fs_info = rbio->bioc->fs_info;
+ const int stripe_nr = i / rbio->stripe_nsectors;
+ const int sectornr = i % rbio->stripe_nsectors;
+ const u64 logical = rbio->bioc->full_stripe_logical +
+ (u64)stripe_nr * BTRFS_STRIPE_LEN +
+ (u64)sectornr * fs_info->sectorsize;
+ const phys_addr_t *paddrs = from_bio ? rbio->bio_paddrs
+ : rbio->stripe_paddrs;
+ struct btrfs_header *hdr;
+ phys_addr_t paddr;
+ u64 found_bytenr;
+ bool bad_fsid;
+ void *kaddr;
+
+ paddr = paddrs[i * rbio->sector_nsteps];
+ if (paddr == INVALID_PADDR)
+ return false;
+ if (!from_bio && !test_bit(i, rbio->stripe_uptodate_bitmap))
+ return false;
+
+ kaddr = kmap_local_paddr(paddr);
+ hdr = kaddr;
+ found_bytenr = btrfs_stack_header_bytenr(hdr);
+ *generation = btrfs_stack_header_generation(hdr);
+ bad_fsid = memcmp(hdr->fsid, fs_info->fs_devices->metadata_uuid,
+ BTRFS_FSID_SIZE) != 0;
+ kunmap_local(kaddr);
+
+ return found_bytenr == logical && !bad_fsid;
+}
+
+/*
+ * Decide whether an uncovered read-modify-write is a write hole or only a
+ * cost, by looking at what is actually in the stripe.
+ *
+ * An RMW has read every column this write does not cover, so the rbio is
+ * holding the stripe's on-disk contents. Any tree block found there is
+ * about to have its parity recomputed and rewritten underneath it while its
+ * own sectors stay put -- the classic torn-stripe window. What decides
+ * whether that matters is the block's generation:
+ *
+ * - equal to the generation of the blocks this write carries, and the block
+ * belongs to the transaction now in flight. A crash loses that whole
+ * transaction anyway, so a torn stripe costs nothing that was ever
+ * committed. This is the residual stripe_meta leaves behind.
+ *
+ * - older, and the stripe holds committed metadata. That is a write hole,
+ * and it means the allocator let a stripe be revisited after the
+ * transaction that filled it completed.
+ *
+ * The comparison is against this rbio's own covered blocks rather than
+ * fs_info's committed generation so that a transaction committing
+ * concurrently cannot skew the verdict: both numbers come from the same
+ * stripe at the same instant.
+ */
+static void audit_uncovered_rmw(struct btrfs_raid_bio *rbio)
+{
+ struct btrfs_fs_info *fs_info = rbio->bioc->fs_info;
+ const u32 sectors_per_tree = fs_info->nodesize >> fs_info->sectorsize_bits;
+ const u32 nsectors = rbio->nr_data * rbio->stripe_nsectors;
+ u64 write_gen = 0;
+
+ /* A mixed block group's sectors need not be tree blocks at all. */
+ if (!(rbio->bioc->map_type & BTRFS_BLOCK_GROUP_METADATA) ||
+ (rbio->bioc->map_type & BTRFS_BLOCK_GROUP_DATA))
+ return;
+
+ /*
+ * Both loops read bio_paddrs to tell what this write covers, and the
+ * paths that reach here do not all populate it (a cached rbio that
+ * failed to pad never indexes). Idempotent, so just do it.
+ */
+ index_rbio_pages(rbio);
+
+ /* The generation this write is putting into the stripe. */
+ for (u32 i = 0; i < nsectors; i += sectors_per_tree) {
+ u64 gen;
+
+ if (rmw_peek_eb_generation(rbio, i, true, &gen))
+ write_gen = max(write_gen, gen);
+ }
+ if (!write_gen)
+ return;
+
+ for (u32 i = 0; i < nsectors; i += sectors_per_tree) {
+ u64 gen;
+
+ /* Covered by this write: it is what we are putting there. */
+ if (rbio->bio_paddrs[i * rbio->sector_nsteps] != INVALID_PADDR)
+ continue;
+ if (!rmw_peek_eb_generation(rbio, i, false, &gen)) {
+ atomic64_inc(&fs_info->stripe_park_stats.meta_rmw_free);
+ continue;
+ }
+ if (gen >= write_gen) {
+ atomic64_inc(&fs_info->stripe_park_stats.meta_rmw_cur);
+ continue;
+ }
+ atomic64_inc(&fs_info->stripe_park_stats.meta_rmw_old);
+ btrfs_warn_rl(fs_info,
+"read-modify-write of stripe %llu rewrites parity over committed tree block at generation %llu while writing generation %llu: write hole",
+ rbio->bioc->full_stripe_logical, gen, write_gen);
+ }
+}
+
/*
* Report a sub-stripe write that is going out as a read-modify-write.
*
return;
atomic64_inc(&fs_info->stripe_park_stats.meta_rmw);
+ audit_uncovered_rmw(rbio);
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);
"expired %lld\n"
"padded %lld\n"
"rmw_reads %lld\n"
- "meta_rmw %lld\n",
+ "meta_rmw %lld\n"
+ "meta_rmw_cur %lld\n"
+ "meta_rmw_old %lld\n"
+ "meta_rmw_free %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.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.meta_rmw));
+ atomic64_read(&fs_info->stripe_park_stats.meta_rmw),
+ atomic64_read(&fs_info->stripe_park_stats.meta_rmw_cur),
+ atomic64_read(&fs_info->stripe_park_stats.meta_rmw_old),
+ atomic64_read(&fs_info->stripe_park_stats.meta_rmw_free));
}
BTRFS_ATTR(, stripe_park_stats, btrfs_stripe_park_stats_show);