bcachefs: Fix bch2_evict_subvolume_inodes()
authorKent Overstreet <kent.overstreet@linux.dev>
Wed, 15 Mar 2023 15:53:51 +0000 (11:53 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Sun, 22 Oct 2023 21:09:57 +0000 (17:09 -0400)
This fixes a bug in bch2_evict_subvolume_inodes(): d_mark_dontcache()
doesn't handle the case where i_count is already 0, we need to grab and
put the inode in order for it to be dropped.

Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/bcachefs.h
fs/bcachefs/darray.h
fs/bcachefs/fs.c
fs/bcachefs/fs.h
fs/bcachefs/inode.c
fs/bcachefs/super.c

index 05fc0f7434dd2530766430b44e23068e022d9720..c1f27b4910a0f64818f97df28910211a877c8050 100644 (file)
@@ -971,6 +971,10 @@ struct bch_fs {
        reflink_gc_table        reflink_gc_table;
        size_t                  reflink_gc_nr;
 
+       /* fs.c */
+       struct list_head        vfs_inodes_list;
+       struct mutex            vfs_inodes_lock;
+
        /* VFS IO PATH - fs-io.c */
        struct bio_set          writepage_bioset;
        struct bio_set          dio_write_bioset;
index 519ab9b96e67fd6e2257b3a63e3becde7711cbca..978ab7961f1b5ed557e4d2e1b76341169242b6b3 100644 (file)
@@ -19,11 +19,11 @@ struct {                                                            \
 
 typedef DARRAY(void) darray_void;
 
-static inline int __darray_make_room(darray_void *d, size_t t_size, size_t more)
+static inline int __darray_make_room(darray_void *d, size_t t_size, size_t more, gfp_t gfp)
 {
        if (d->nr + more > d->size) {
                size_t new_size = roundup_pow_of_two(d->nr + more);
-               void *data = krealloc_array(d->data, new_size, t_size, GFP_KERNEL);
+               void *data = krealloc_array(d->data, new_size, t_size, gfp);
 
                if (!data)
                        return -ENOMEM;
@@ -35,20 +35,25 @@ static inline int __darray_make_room(darray_void *d, size_t t_size, size_t more)
        return 0;
 }
 
+#define darray_make_room_gfp(_d, _more, _gfp)                          \
+       __darray_make_room((darray_void *) (_d), sizeof((_d)->data[0]), (_more), _gfp)
+
 #define darray_make_room(_d, _more)                                    \
-       __darray_make_room((darray_void *) (_d), sizeof((_d)->data[0]), (_more))
+       darray_make_room_gfp(_d, _more, GFP_KERNEL)
 
 #define darray_top(_d)         ((_d).data[(_d).nr])
 
-#define darray_push(_d, _item)                                         \
+#define darray_push_gfp(_d, _item, _gfp)                               \
 ({                                                                     \
-       int _ret = darray_make_room((_d), 1);                           \
+       int _ret = darray_make_room_gfp((_d), 1, _gfp);                 \
                                                                        \
        if (!_ret)                                                      \
                (_d)->data[(_d)->nr++] = (_item);                       \
        _ret;                                                           \
 })
 
+#define darray_push(_d, _item) darray_push_gfp(_d, _item, GFP_KERNEL)
+
 #define darray_insert_item(_d, _pos, _item)                            \
 ({                                                                     \
        size_t pos = (_pos);                                            \
index 828887abc26194a4600350f19102427a95275770..129924dfaf69b3fc2bc3ebbe437c5ca87ecfe812 100644 (file)
@@ -201,6 +201,10 @@ struct inode *bch2_vfs_inode_get(struct bch_fs *c, subvol_inum inum)
                return ERR_PTR(ret);
        }
 
+       mutex_lock(&c->vfs_inodes_lock);
+       list_add(&inode->ei_vfs_inode_list, &c->vfs_inodes_list);
+       mutex_unlock(&c->vfs_inodes_lock);
+
        unlock_new_inode(&inode->v);
 
        return &inode->v;
@@ -314,6 +318,9 @@ err_before_quota:
 
                inode = old;
        } else {
+               mutex_lock(&c->vfs_inodes_lock);
+               list_add(&inode->ei_vfs_inode_list, &c->vfs_inodes_list);
+               mutex_unlock(&c->vfs_inodes_lock);
                /*
                 * we really don't want insert_inode_locked2() to be setting
                 * I_NEW...
@@ -1370,6 +1377,7 @@ static struct inode *bch2_alloc_inode(struct super_block *sb)
        inode_init_once(&inode->v);
        mutex_init(&inode->ei_update_lock);
        two_state_lock_init(&inode->ei_pagecache_lock);
+       INIT_LIST_HEAD(&inode->ei_vfs_inode_list);
        mutex_init(&inode->ei_quota_lock);
 
        return &inode->v;
@@ -1434,53 +1442,78 @@ static void bch2_evict_inode(struct inode *vinode)
                                KEY_TYPE_QUOTA_WARN);
                bch2_inode_rm(c, inode_inum(inode));
        }
+
+       mutex_lock(&c->vfs_inodes_lock);
+       list_del_init(&inode->ei_vfs_inode_list);
+       mutex_unlock(&c->vfs_inodes_lock);
 }
 
-void bch2_evict_subvolume_inodes(struct bch_fs *c,
-                                snapshot_id_list *s)
+void bch2_evict_subvolume_inodes(struct bch_fs *c, snapshot_id_list *s)
 {
-       struct super_block *sb = c->vfs_sb;
-       struct inode *inode;
+       struct bch_inode_info *inode, **i;
+       DARRAY(struct bch_inode_info *) grabbed;
+       bool clean_pass = false, this_pass_clean;
 
-       spin_lock(&sb->s_inode_list_lock);
-       list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
-               if (!snapshot_list_has_id(s, to_bch_ei(inode)->ei_subvol) ||
-                   (inode->i_state & I_FREEING))
-                       continue;
+       /*
+        * Initially, we scan for inodes without I_DONTCACHE, then mark them to
+        * be pruned with d_mark_dontcache().
+        *
+        * Once we've had a clean pass where we didn't find any inodes without
+        * I_DONTCACHE, we wait for them to be freed:
+        */
 
-               d_mark_dontcache(inode);
-               d_prune_aliases(inode);
-       }
-       spin_unlock(&sb->s_inode_list_lock);
+       darray_init(&grabbed);
+       darray_make_room(&grabbed, 1024);
 again:
        cond_resched();
-       spin_lock(&sb->s_inode_list_lock);
-       list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
-               if (!snapshot_list_has_id(s, to_bch_ei(inode)->ei_subvol) ||
-                   (inode->i_state & I_FREEING))
+       this_pass_clean = true;
+
+       mutex_lock(&c->vfs_inodes_lock);
+       list_for_each_entry(inode, &c->vfs_inodes_list, ei_vfs_inode_list) {
+               if (!snapshot_list_has_id(s, inode->ei_subvol))
                        continue;
 
-               if (!(inode->i_state & I_DONTCACHE)) {
-                       d_mark_dontcache(inode);
-                       d_prune_aliases(inode);
-               }
+               if (!(inode->v.i_state & I_DONTCACHE) &&
+                   !(inode->v.i_state & I_FREEING)) {
+                       this_pass_clean = false;
+
+                       d_mark_dontcache(&inode->v);
+                       d_prune_aliases(&inode->v);
+
+                       /*
+                        * If i_count was zero, we have to take and release a
+                        * ref in order for I_DONTCACHE to be noticed and the
+                        * inode to be dropped;
+                        */
+
+                       if (!atomic_read(&inode->v.i_count) &&
+                           igrab(&inode->v) &&
+                           darray_push_gfp(&grabbed, inode, GFP_ATOMIC|__GFP_NOWARN))
+                               break;
+               } else if (clean_pass && this_pass_clean) {
+                       wait_queue_head_t *wq = bit_waitqueue(&inode->v.i_state, __I_NEW);
+                       DEFINE_WAIT_BIT(wait, &inode->v.i_state, __I_NEW);
 
-               spin_lock(&inode->i_lock);
-               if (snapshot_list_has_id(s, to_bch_ei(inode)->ei_subvol) &&
-                   !(inode->i_state & I_FREEING)) {
-                       wait_queue_head_t *wq = bit_waitqueue(&inode->i_state, __I_NEW);
-                       DEFINE_WAIT_BIT(wait, &inode->i_state, __I_NEW);
                        prepare_to_wait(wq, &wait.wq_entry, TASK_UNINTERRUPTIBLE);
-                       spin_unlock(&inode->i_lock);
-                       spin_unlock(&sb->s_inode_list_lock);
+                       mutex_unlock(&c->vfs_inodes_lock);
+
                        schedule();
                        finish_wait(wq, &wait.wq_entry);
                        goto again;
                }
+       }
+       mutex_unlock(&c->vfs_inodes_lock);
 
-               spin_unlock(&inode->i_lock);
+       darray_for_each(grabbed, i)
+               iput(&(*i)->v);
+       grabbed.nr = 0;
+
+       if (!clean_pass || !this_pass_clean) {
+               clean_pass = this_pass_clean;
+               goto again;
        }
-       spin_unlock(&sb->s_inode_list_lock);
+
+       darray_exit(&grabbed);
 }
 
 static int bch2_statfs(struct dentry *dentry, struct kstatfs *buf)
index e1c73a38c607c5532e47d3016d245a603783c5f1..2e63cb6603bd7f1da9eee3eb1c368fb7770fdc5b 100644 (file)
@@ -13,6 +13,7 @@
 
 struct bch_inode_info {
        struct inode            v;
+       struct list_head        ei_vfs_inode_list;
        unsigned long           ei_flags;
 
        struct mutex            ei_update_lock;
index 560545a7ea0399426b0f80cec5e1ebe977479074..7ccbc00b7156ed9f7ad9e2f16326555b475a5760 100644 (file)
@@ -803,9 +803,6 @@ retry:
 
        bch2_inode_unpack(k, &inode_u);
 
-       /* Subvolume root? */
-       BUG_ON(inode_u.bi_subvol);
-
        bkey_inode_generation_init(&delete.k_i);
        delete.k.p = iter.pos;
        delete.v.bi_generation = cpu_to_le32(inode_u.bi_generation + 1);
index 278f8f19a2307c9b9eb78a11c26280635cda1126..d6f2f453c0275feaf682c9c7a38a26503a129484 100644 (file)
@@ -709,6 +709,9 @@ static struct bch_fs *bch2_fs_alloc(struct bch_sb *sb, struct bch_opts opts)
 
        sema_init(&c->io_in_flight, 128);
 
+       INIT_LIST_HEAD(&c->vfs_inodes_list);
+       mutex_init(&c->vfs_inodes_lock);
+
        c->copy_gc_enabled              = 1;
        c->rebalance.enabled            = 1;
        c->promote_whole_extents        = true;