]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: release open stripe runs when a mount fails or an aborted filesy...
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 10:19:44 +0000 (06:19 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:07 +0000 (17:40 -0400)
A block group with open stripe runs holds a reference for as long as it
is on fs_info->open_stripe_bgs, and only the commit-time retirement,
having released the group's last run, drops it.  Two paths never commit
again: a mount whose log replay fails after it has already allocated
tree blocks (each allocation opened a metadata run), and the unmount of
a filesystem whose transaction aborted.  btrfs_free_block_groups() then
finds the leftover references and asserts:

  BTRFS: error (device loop0 state EAO) in btrfs_replay_log:2072:
  errno=-5 IO failure (Failed to recover log tree)
  assertion failed: refcount_read(&block_group->refs) == 1 :: 0, in
  fs/btrfs/block-group.c:7479
  kernel BUG at fs/btrfs/block-group.c:7479!
  RIP: btrfs_free_block_groups.cold
  open_ctree
  btrfs_get_tree.cold

Hit by the raid56-metadata fsync-window characterization: a degraded
mount whose log replay got as far as allocating before it met the torn
log block, where an earlier run of the same case had failed while
walking the log and never allocated.  The mount task died holding the
superblock lock and the next mount of the same devices hung on it.

Release every open run and the references before the block groups are
freed, on both paths.  Nothing is in flight there: workers are stopped
and the roots dropped; a run that still counts bytes in flight is
reported and released anyway.

Assisted-by: Claude:claude-fable-5-1
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/disk-io.c

index 128daaa70d80a25db94fb8cf808e16e6e065167f..a845947bf085b0a5a1bd0b790b968aaac038373e 100644 (file)
@@ -2493,6 +2493,50 @@ void btrfs_retire_block_group_stripes(struct btrfs_block_group *bg)
  * Calls are serialized by the transaction commit; @trans may be NULL for
  * the final cleanup call.
  */
+/*
+ * Teardown: release every open stripe run and the block group reference
+ * each group with runs holds.  The commit normally retires runs, and the
+ * release of a group's last run drops its membership here; a mount that
+ * fails after log replay has already allocated tree blocks, and an unmount
+ * after a transaction abort, never commit again, and
+ * btrfs_free_block_groups() then asserts on the leftover references.  By
+ * the time this runs the workers are stopped and the roots dropped, so no
+ * IO is left to report; a run still counting bytes in flight is noted and
+ * released all the same.
+ */
+void btrfs_free_open_stripe_runs(struct btrfs_fs_info *fs_info)
+{
+       LIST_HEAD(bgs);
+       struct btrfs_block_group *bg, *tmp_bg;
+       unsigned long flags;
+
+       spin_lock(&fs_info->open_stripe_lock);
+       list_splice_init(&fs_info->open_stripe_bgs, &bgs);
+       spin_unlock(&fs_info->open_stripe_lock);
+
+       list_for_each_entry_safe(bg, tmp_bg, &bgs, open_stripe_bg_list) {
+               struct btrfs_open_stripe_run *run, *tmp;
+
+               list_del_init(&bg->open_stripe_bg_list);
+               spin_lock_irqsave(&bg->stripe_run_lock, flags);
+               list_for_each_entry_safe(run, tmp, &bg->open_stripe_runs, list) {
+                       if (run->inflight_bytes)
+                               btrfs_warn(fs_info,
+"stripe run %llu-%llu in block group %llu released at teardown with %llu bytes still in flight",
+                                          run->start, run->end, bg->start,
+                                          run->inflight_bytes);
+                       list_del(&run->list);
+                       bitmap_free(run->live);
+                       kfree(run);
+               }
+               memset(bg->open_stripe, 0, sizeof(bg->open_stripe));
+               bg->stripe_open_remainder = 0;
+               spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+               stripe_open_remainder_sync(bg);
+               btrfs_put_block_group(bg);
+       }
+}
+
 /*
  * Close and drain the metadata stripe runs, once every tree block this
  * transaction allocated has reached the disk.
index 70a059a9a8a35e72ed657745320db0af74e4ac47..3c2514c5c71c51292e96c8ca7cad02985b209f81 100644 (file)
@@ -473,6 +473,7 @@ struct btrfs_open_stripe_run *btrfs_get_open_stripe_run(
                u64 *open_seq);
 void btrfs_close_bg_open_stripes(struct btrfs_block_group *bg);
 void btrfs_retire_meta_stripes(struct btrfs_fs_info *fs_info, u64 transid);
+void btrfs_free_open_stripe_runs(struct btrfs_fs_info *fs_info);
 void btrfs_retire_block_group_stripes(struct btrfs_block_group *bg);
 bool btrfs_stripe_in_open_run(struct btrfs_fs_info *fs_info, u64 logical);
 bool btrfs_defer_stripe_meta_write(struct btrfs_fs_info *fs_info,
index 7c3d0b455125123dbfa4e45d9faa873c27f12cd1..5628901d8895d31e669401fe9510966ae8feb15a 100644 (file)
@@ -3702,6 +3702,7 @@ fail_tree_roots:
 
 fail_sb_buffer:
        btrfs_stop_all_workers(fs_info);
+       btrfs_free_open_stripe_runs(fs_info);
        btrfs_free_block_groups(fs_info);
 fail_alloc:
        btrfs_mapping_tree_free(fs_info);
@@ -4450,6 +4451,7 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info)
         * accounting appear to be wrong because there's pending reserved bytes,
         * so make sure we do the block group cleanup afterwards.
         */
+       btrfs_free_open_stripe_runs(fs_info);
        btrfs_free_block_groups(fs_info);
 
        iput(fs_info->btree_inode);