bcachefs: Fix bch2_evict_subvolume_inodes()
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>
This commit is contained in:
parent
e1e7ecafe6
commit
9edbcc72f6
|
@ -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;
|
||||
|
|
|
@ -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); \
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
spin_unlock(&inode->i_lock);
|
||||
}
|
||||
spin_unlock(&sb->s_inode_list_lock);
|
||||
mutex_unlock(&c->vfs_inodes_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;
|
||||
}
|
||||
|
||||
darray_exit(&grabbed);
|
||||
}
|
||||
|
||||
static int bch2_statfs(struct dentry *dentry, struct kstatfs *buf)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue