btrfs: qgroup: iterate qgroups without memory allocation for qgroup_reserve()
authorQu Wenruo <wqu@suse.com>
Sat, 2 Sep 2023 00:13:52 +0000 (08:13 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 1 Jan 2024 12:42:24 +0000 (12:42 +0000)
[ Upstream commit 686c4a5a42635e0d2889e3eb461c554fd0b616b4 ]

Qgroup heavily relies on ulist to go through all the involved
qgroups, but since we're using ulist inside fs_info->qgroup_lock
spinlock, this means we're doing a lot of GFP_ATOMIC allocations.

This patch reduces the GFP_ATOMIC usage for qgroup_reserve() by
eliminating the memory allocation completely.

This is done by moving the needed memory to btrfs_qgroup::iterator
list_head, so that we can put all the involved qgroup into a on-stack
list, thus eliminating the need to allocate memory while holding
spinlock.

The only cost is the slightly higher memory usage, but considering the
reduce GFP_ATOMIC during a hot path, it should still be acceptable.

Function qgroup_reserve() is the perfect start point for this
conversion.

Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Stable-dep-of: b321a52cce06 ("btrfs: free qgroup pertrans reserve on transaction abort")
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/btrfs/qgroup.c
fs/btrfs/qgroup.h

index 7c92494..32e5def 100644 (file)
@@ -208,6 +208,7 @@ static struct btrfs_qgroup *add_qgroup_rb(struct btrfs_fs_info *fs_info,
        INIT_LIST_HEAD(&qgroup->groups);
        INIT_LIST_HEAD(&qgroup->members);
        INIT_LIST_HEAD(&qgroup->dirty);
+       INIT_LIST_HEAD(&qgroup->iterator);
 
        rb_link_node(&qgroup->node, parent, p);
        rb_insert_color(&qgroup->node, &fs_info->qgroup_tree);
@@ -1342,6 +1343,24 @@ static void qgroup_dirty(struct btrfs_fs_info *fs_info,
                list_add(&qgroup->dirty, &fs_info->dirty_qgroups);
 }
 
+static void qgroup_iterator_add(struct list_head *head, struct btrfs_qgroup *qgroup)
+{
+       if (!list_empty(&qgroup->iterator))
+               return;
+
+       list_add_tail(&qgroup->iterator, head);
+}
+
+static void qgroup_iterator_clean(struct list_head *head)
+{
+       while (!list_empty(head)) {
+               struct btrfs_qgroup *qgroup;
+
+               qgroup = list_first_entry(head, struct btrfs_qgroup, iterator);
+               list_del_init(&qgroup->iterator);
+       }
+}
+
 /*
  * The easy accounting, we're updating qgroup relationship whose child qgroup
  * only has exclusive extents.
@@ -3125,8 +3144,7 @@ static int qgroup_reserve(struct btrfs_root *root, u64 num_bytes, bool enforce,
        struct btrfs_fs_info *fs_info = root->fs_info;
        u64 ref_root = root->root_key.objectid;
        int ret = 0;
-       struct ulist_node *unode;
-       struct ulist_iterator uiter;
+       LIST_HEAD(qgroup_list);
 
        if (!is_fstree(ref_root))
                return 0;
@@ -3146,49 +3164,28 @@ static int qgroup_reserve(struct btrfs_root *root, u64 num_bytes, bool enforce,
        if (!qgroup)
                goto out;
 
-       /*
-        * in a first step, we check all affected qgroups if any limits would
-        * be exceeded
-        */
-       ulist_reinit(fs_info->qgroup_ulist);
-       ret = ulist_add(fs_info->qgroup_ulist, qgroup->qgroupid,
-                       qgroup_to_aux(qgroup), GFP_ATOMIC);
-       if (ret < 0)
-               goto out;
-       ULIST_ITER_INIT(&uiter);
-       while ((unode = ulist_next(fs_info->qgroup_ulist, &uiter))) {
-               struct btrfs_qgroup *qg;
+       qgroup_iterator_add(&qgroup_list, qgroup);
+       list_for_each_entry(qgroup, &qgroup_list, iterator) {
                struct btrfs_qgroup_list *glist;
 
-               qg = unode_aux_to_qgroup(unode);
-
-               if (enforce && !qgroup_check_limits(qg, num_bytes)) {
+               if (enforce && !qgroup_check_limits(qgroup, num_bytes)) {
                        ret = -EDQUOT;
                        goto out;
                }
 
-               list_for_each_entry(glist, &qg->groups, next_group) {
-                       ret = ulist_add(fs_info->qgroup_ulist,
-                                       glist->group->qgroupid,
-                                       qgroup_to_aux(glist->group), GFP_ATOMIC);
-                       if (ret < 0)
-                               goto out;
-               }
+               list_for_each_entry(glist, &qgroup->groups, next_group)
+                       qgroup_iterator_add(&qgroup_list, glist->group);
        }
+
        ret = 0;
        /*
         * no limits exceeded, now record the reservation into all qgroups
         */
-       ULIST_ITER_INIT(&uiter);
-       while ((unode = ulist_next(fs_info->qgroup_ulist, &uiter))) {
-               struct btrfs_qgroup *qg;
-
-               qg = unode_aux_to_qgroup(unode);
-
-               qgroup_rsv_add(fs_info, qg, num_bytes, type);
-       }
+       list_for_each_entry(qgroup, &qgroup_list, iterator)
+               qgroup_rsv_add(fs_info, qgroup, num_bytes, type);
 
 out:
+       qgroup_iterator_clean(&qgroup_list);
        spin_unlock(&fs_info->qgroup_lock);
        return ret;
 }
index 104c9bd..1203f06 100644 (file)
@@ -220,6 +220,15 @@ struct btrfs_qgroup {
        struct list_head groups;  /* groups this group is member of */
        struct list_head members; /* groups that are members of this group */
        struct list_head dirty;   /* dirty groups */
+
+       /*
+        * For qgroup iteration usage.
+        *
+        * The iteration list should always be empty until qgroup_iterator_add()
+        * is called.  And should be reset to empty after the iteration is
+        * finished.
+        */
+       struct list_head iterator;
        struct rb_node node;      /* tree of qgroups */
 
        /*