There is a hidden call loop which can trigger itself:

btrfs_reserve_extent()             <--|
|- do_chunk_alloc()                   |
   |- btrfs_alloc_chunk()             |
      |- btrfs_insert_item()          |
         |- btrfs_reserve_extent() <--|

Currently, we're using root->ref_cows to determine whether we should do
chunk prealloc to avoid such loop.

But that's still a hidden trap. Instead of solving it using some hidden
tricks, this patch will make chunk/block group allocation exclusive.

Now if do_chunk_alloc() determines to alloc chunk, it will set a special
flag in transaction handler so it new do_chunk_alloc() will refuse to
allocate new chunk until current chunk allocation finishes.

Signed-off-by: Qu Wenruo <[email protected]>
---
 check/main.c  |  2 +-
 extent-tree.c | 12 ++++++++++++
 transaction.c |  3 ++-
 transaction.h |  3 ++-
 4 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/check/main.c b/check/main.c
index 683c322eba6f..121be4b73c4f 100644
--- a/check/main.c
+++ b/check/main.c
@@ -10185,7 +10185,7 @@ int cmd_check(int argc, char **argv)
                        goto close_out;
                }
 
-               trans->reinit_extent_tree = true;
+               trans->reinit_extent_tree = 1;
                if (init_extent_tree) {
                        printf("Creating a new extent tree\n");
                        ret = reinit_extent_tree(trans, info,
diff --git a/extent-tree.c b/extent-tree.c
index 8c9cdeff3b02..e25ddf02e5cc 100644
--- a/extent-tree.c
+++ b/extent-tree.c
@@ -1873,10 +1873,21 @@ static int do_chunk_alloc(struct btrfs_trans_handle 
*trans,
            (flags & BTRFS_BLOCK_GROUP_SYSTEM))
                return 0;
 
+       /*
+        * We're going to allocate new chunk, during the process, we will
+        * allocate new tree blocks, which can trigger new chunk allocation
+        * again.
+        * Avoid such loop call
+        */
+       if (trans->allocating_chunk)
+               return 0;
+       trans->allocating_chunk = 1;
+
        ret = btrfs_alloc_chunk(trans, fs_info, &start, &num_bytes,
                                space_info->flags);
        if (ret == -ENOSPC) {
                space_info->full = 1;
+               trans->allocating_chunk = 0;
                return 0;
        }
 
@@ -1885,6 +1896,7 @@ static int do_chunk_alloc(struct btrfs_trans_handle 
*trans,
        ret = btrfs_make_block_group(trans, fs_info, 0, space_info->flags,
                                     start, num_bytes);
        BUG_ON(ret);
+       trans->allocating_chunk = 0;
        return 0;
 }
 
diff --git a/transaction.c b/transaction.c
index 3a63988b0969..21377282f806 100644
--- a/transaction.c
+++ b/transaction.c
@@ -46,7 +46,8 @@ struct btrfs_trans_handle* btrfs_start_transaction(struct 
btrfs_root *root,
        fs_info->generation++;
        h->transid = fs_info->generation;
        h->blocks_reserved = num_blocks;
-       h->reinit_extent_tree = false;
+       h->reinit_extent_tree = 0;
+       h->allocating_chunk = 0;
        root->last_trans = h->transid;
        root->commit_root = root->node;
        extent_buffer_get(root->node);
diff --git a/transaction.h b/transaction.h
index 34060252dd5c..b426cd112447 100644
--- a/transaction.h
+++ b/transaction.h
@@ -28,7 +28,8 @@ struct btrfs_trans_handle {
        u64 transid;
        u64 alloc_exclude_start;
        u64 alloc_exclude_nr;
-       bool reinit_extent_tree;
+       unsigned int reinit_extent_tree:1;
+       unsigned int allocating_chunk:1;
        u64 delayed_ref_updates;
        unsigned long blocks_reserved;
        unsigned long blocks_used;
-- 
2.21.0

Reply via email to