]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
btrfs: handle discarding fully-remapped block groups
authorMark Harmstone <mark@harmstone.com>
Wed, 7 Jan 2026 14:09:16 +0000 (14:09 +0000)
committerDavid Sterba <dsterba@suse.com>
Tue, 3 Feb 2026 06:54:36 +0000 (07:54 +0100)
Discard normally works by iterating over the free-space entries of a
block group. This doesn't work for fully-remapped block groups, as we
removed their free-space entries when we started relocation.

For sync discard, call btrfs_discard_extent() when we commit the
transaction in which the last identity remap was removed.

For async discard, add a new function btrfs_trim_fully_remapped_block_group()
to be called by the discard worker, which iterates over the block
group's range using the normal async discard rules. Once we reach the
end, remove the chunk's stripes and device extents to get back its free
space.

Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Mark Harmstone <mark@harmstone.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/discard.c
fs/btrfs/extent-tree.c
fs/btrfs/free-space-cache.c
fs/btrfs/free-space-cache.h

index dc80f147e98de040d47c5600c848ab56413cf513..cfc1e363e3ec60e49c34df5a7653177788d30327 100644 (file)
@@ -4792,18 +4792,22 @@ void btrfs_mark_bg_fully_remapped(struct btrfs_block_group *bg,
 {
        struct btrfs_fs_info *fs_info = trans->fs_info;
 
-       spin_lock(&fs_info->unused_bgs_lock);
-       /*
-        * The block group might already be on the unused_bgs list, remove it
-        * if it is. It'll get readded after the async discard worker finishes,
-        * or in btrfs_handle_fully_remapped_bgs() if we're not using async
-        * discard.
-        */
-       if (!list_empty(&bg->bg_list))
-               list_del(&bg->bg_list);
-       else
-               btrfs_get_block_group(bg);
 
-       list_add_tail(&bg->bg_list, &fs_info->fully_remapped_bgs);
-       spin_unlock(&fs_info->unused_bgs_lock);
+       if (btrfs_test_opt(fs_info, DISCARD_ASYNC)) {
+               btrfs_discard_queue_work(&fs_info->discard_ctl, bg);
+       } else {
+               spin_lock(&fs_info->unused_bgs_lock);
+               /*
+                * The block group might already be on the unused_bgs list,
+                * remove it if it is. It'll get readded after
+                * btrfs_handle_fully_remapped_bgs() finishes.
+                */
+               if (!list_empty(&bg->bg_list))
+                       list_del(&bg->bg_list);
+               else
+                       btrfs_get_block_group(bg);
+
+               list_add_tail(&bg->bg_list, &fs_info->fully_remapped_bgs);
+               spin_unlock(&fs_info->unused_bgs_lock);
+       }
 }
index a775c0bc40c3646b7223f6d555ddcde2f61ec695..29d6e682accdea3505161642d390840504557a0e 100644 (file)
@@ -49,6 +49,7 @@ enum btrfs_discard_state {
        BTRFS_DISCARD_EXTENTS,
        BTRFS_DISCARD_BITMAPS,
        BTRFS_DISCARD_RESET_CURSOR,
+       BTRFS_DISCARD_FULLY_REMAPPED,
 };
 
 /*
index ee5f5b2788e189f4bd8186d09920b268783703bb..1c304bf473e55e7fdb2b05ebc830b372ded874e9 100644 (file)
@@ -215,6 +215,25 @@ static struct btrfs_block_group *find_next_block_group(
        return ret_block_group;
 }
 
+/*
+ * Check whether a block group is empty.
+ *
+ * "Empty" here means that there are no extents physically located within the
+ * device extents corresponding to this block group.
+ *
+ * For a remapped block group, this means that all of its identity remaps have
+ * been removed. For a non-remapped block group, this means that no extents
+ * have an address within its range, and that nothing has been remapped to be
+ * within it.
+ */
+static bool block_group_is_empty(const struct btrfs_block_group *bg)
+{
+       if (bg->flags & BTRFS_BLOCK_GROUP_REMAPPED)
+               return bg->identity_remap_count == 0;
+
+       return bg->used == 0 && bg->remap_bytes == 0;
+}
+
 /*
  * Look up next block group and set it for use.
  *
@@ -241,8 +260,10 @@ again:
        block_group = find_next_block_group(discard_ctl, now);
 
        if (block_group && now >= block_group->discard_eligible_time) {
+               const bool empty = block_group_is_empty(block_group);
+
                if (block_group->discard_index == BTRFS_DISCARD_INDEX_UNUSED &&
-                   block_group->used != 0) {
+                   !empty) {
                        if (btrfs_is_block_group_data_only(block_group)) {
                                __add_to_discard_list(discard_ctl, block_group);
                                /*
@@ -267,7 +288,12 @@ again:
                }
                if (block_group->discard_state == BTRFS_DISCARD_RESET_CURSOR) {
                        block_group->discard_cursor = block_group->start;
-                       block_group->discard_state = BTRFS_DISCARD_EXTENTS;
+
+                       if (block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED && empty) {
+                               block_group->discard_state = BTRFS_DISCARD_FULLY_REMAPPED;
+                       } else {
+                               block_group->discard_state = BTRFS_DISCARD_EXTENTS;
+                       }
                }
        }
        if (block_group) {
@@ -373,7 +399,7 @@ void btrfs_discard_queue_work(struct btrfs_discard_ctl *discard_ctl,
        if (!block_group || !btrfs_test_opt(block_group->fs_info, DISCARD_ASYNC))
                return;
 
-       if (block_group->used == 0 && block_group->remap_bytes == 0)
+       if (block_group_is_empty(block_group))
                add_to_discard_unused_list(discard_ctl, block_group);
        else
                add_to_discard_list(discard_ctl, block_group);
@@ -470,7 +496,7 @@ static void btrfs_finish_discard_pass(struct btrfs_discard_ctl *discard_ctl,
 {
        remove_from_discard_list(discard_ctl, block_group);
 
-       if (block_group->used == 0) {
+       if (block_group_is_empty(block_group)) {
                if (btrfs_is_free_space_trimmed(block_group))
                        btrfs_mark_bg_unused(block_group);
                else
@@ -524,7 +550,8 @@ static void btrfs_discard_workfn(struct work_struct *work)
        /* Perform discarding */
        minlen = discard_minlen[discard_index];
 
-       if (discard_state == BTRFS_DISCARD_BITMAPS) {
+       switch (discard_state) {
+       case BTRFS_DISCARD_BITMAPS: {
                u64 maxlen = 0;
 
                /*
@@ -541,17 +568,28 @@ static void btrfs_discard_workfn(struct work_struct *work)
                                       btrfs_block_group_end(block_group),
                                       minlen, maxlen, true);
                discard_ctl->discard_bitmap_bytes += trimmed;
-       } else {
+
+               break;
+       }
+
+       case BTRFS_DISCARD_FULLY_REMAPPED:
+               btrfs_trim_fully_remapped_block_group(block_group);
+               break;
+
+       default:
                btrfs_trim_block_group_extents(block_group, &trimmed,
                                       block_group->discard_cursor,
                                       btrfs_block_group_end(block_group),
                                       minlen, true);
                discard_ctl->discard_extent_bytes += trimmed;
+
+               break;
        }
 
        /* Determine next steps for a block_group */
        if (block_group->discard_cursor >= btrfs_block_group_end(block_group)) {
-               if (discard_state == BTRFS_DISCARD_BITMAPS) {
+               if (discard_state == BTRFS_DISCARD_BITMAPS ||
+                   discard_state == BTRFS_DISCARD_FULLY_REMAPPED) {
                        btrfs_finish_discard_pass(discard_ctl, block_group);
                } else {
                        block_group->discard_cursor = block_group->start;
index c063c5b6c433a138b5dd1305278dd5ab76e4fab1..6fab7765057e96036a33ab32dd13e0a2414150c4 100644 (file)
@@ -2905,6 +2905,8 @@ void btrfs_handle_fully_remapped_bgs(struct btrfs_fs_info *fs_info)
                list_del_init(&bg->bg_list);
                spin_unlock(&fs_info->unused_bgs_lock);
 
+               btrfs_discard_extent(fs_info, bg->start, bg->length, NULL, false);
+
                ret = btrfs_complete_bg_remapping(bg);
                if (ret) {
                        btrfs_put_block_group(bg);
index 17e79ee3e021603c50af56989dd21c55b8d4f5c7..a4a941cde866e8b1a1e401286557a9b1c0db133a 100644 (file)
@@ -29,6 +29,7 @@
 #include "file-item.h"
 #include "file.h"
 #include "super.h"
+#include "relocation.h"
 
 #define BITS_PER_BITMAP                (PAGE_SIZE * 8UL)
 #define MAX_CACHE_BYTES_PER_GIG        SZ_64K
@@ -3066,6 +3067,11 @@ bool btrfs_is_free_space_trimmed(struct btrfs_block_group *block_group)
        struct rb_node *node;
        bool ret = true;
 
+       if (block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED &&
+           block_group->identity_remap_count == 0) {
+               return true;
+       }
+
        spin_lock(&ctl->tree_lock);
        node = rb_first(&ctl->free_space_offset);
 
@@ -3834,6 +3840,33 @@ out_unlock:
        return ret;
 }
 
+void btrfs_trim_fully_remapped_block_group(struct btrfs_block_group *bg)
+{
+       struct btrfs_fs_info *fs_info = bg->fs_info;
+       struct btrfs_discard_ctl *discard_ctl = &fs_info->discard_ctl;
+       int ret = 0;
+       u64 bytes, trimmed;
+       const u64 max_discard_size = READ_ONCE(discard_ctl->max_discard_size);
+       u64 end = btrfs_block_group_end(bg);
+
+       bytes = end - bg->discard_cursor;
+
+       if (max_discard_size &&
+           bytes >= (max_discard_size + BTRFS_ASYNC_DISCARD_MIN_FILTER))
+               bytes = max_discard_size;
+
+       ret = btrfs_discard_extent(fs_info, bg->discard_cursor, bytes, &trimmed, false);
+       if (ret)
+               return;
+
+       bg->discard_cursor += trimmed;
+
+       if (bg->discard_cursor < end)
+               return;
+
+       btrfs_complete_bg_remapping(bg);
+}
+
 /*
  * If we break out of trimming a bitmap prematurely, we should reset the
  * trimming bit.  In a rather contrived case, it's possible to race here so
index 9f1dbfdee8cabfef7e6aa87928c2c3ccdcea950f..33fc3b245648515b7f0f5f143cace8e90cf1af47 100644 (file)
@@ -166,6 +166,7 @@ int btrfs_trim_block_group_extents(struct btrfs_block_group *block_group,
 int btrfs_trim_block_group_bitmaps(struct btrfs_block_group *block_group,
                                   u64 *trimmed, u64 start, u64 end, u64 minlen,
                                   u64 maxlen, bool async);
+void btrfs_trim_fully_remapped_block_group(struct btrfs_block_group *bg);
 
 bool btrfs_free_space_cache_v1_active(struct btrfs_fs_info *fs_info);
 int btrfs_set_free_space_cache_v1_active(struct btrfs_fs_info *fs_info, bool active);