Re: [RFC PATCH v2] fuse: add optional workqueue to periodically invalidate expired dentries

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Miklos,

On Tue, Apr 15 2025, Luis Henriques wrote:

> This patch adds a new mount option that will allow to set a workqueue to
> periodically invalidate expired dentries.  When this parameter is set,
> every new (or revalidated) dentry will be added to a tree, sorted by
> expiry time.  The workqueue period is set when a filesystem is mounted
> using this new parameter, and can not be less than 5 seconds.
>

I wondering if you had the chance to have a look at this patch already.
Or maybe I misinterpreted your suggestion.

Cheers,
-- 
Luís

> Signed-off-by: Luis Henriques <luis@xxxxxxxxxx>
> ---
> * Changes since v1:
>
> - Add mount option to enable the workqueue and set it's period
> - 'parent' initialisation missing in fuse_dentry_tree_add_node()
>
>  Documentation/filesystems/fuse.rst |   5 +
>  fs/fuse/dir.c                      | 147 +++++++++++++++++++++++++++++
>  fs/fuse/fuse_i.h                   |  13 +++
>  fs/fuse/inode.c                    |  18 ++++
>  4 files changed, 183 insertions(+)
>
> diff --git a/Documentation/filesystems/fuse.rst b/Documentation/filesystems/fuse.rst
> index 1e31e87aee68..b0a7be54e611 100644
> --- a/Documentation/filesystems/fuse.rst
> +++ b/Documentation/filesystems/fuse.rst
> @@ -103,6 +103,11 @@ blksize=N
>    Set the block size for the filesystem.  The default is 512.  This
>    option is only valid for 'fuseblk' type mounts.
>  
> +inval_wq=N
> +  Enable a workqueue that will periodically invalidate dentries that
> +  have expired.  'N' is a value in seconds and has to be bigger than
> +  5 seconds.
> +
>  Control filesystem
>  ==================
>  
> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
> index 1fb0b15a6088..e16aafc522ef 100644
> --- a/fs/fuse/dir.c
> +++ b/fs/fuse/dir.c
> @@ -62,6 +62,151 @@ static inline u64 fuse_dentry_time(const struct dentry *entry)
>  }
>  #endif
>  
> +struct dentry_node {
> +	struct rb_node node;
> +	struct dentry *dentry;
> +};
> +
> +static void fuse_dentry_tree_add_node(struct dentry *dentry)
> +{
> +	struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb);
> +	struct dentry_node *dn, *cur;
> +	struct rb_node **p, *parent = NULL;
> +	bool start_work = false;
> +
> +	if (!fc->inval_wq)
> +		return;
> +
> +	dn = kmalloc(sizeof(*dn), GFP_KERNEL);
> +	if (!dn)
> +		return;
> +	dn->dentry = dget(dentry);
> +	spin_lock(&fc->dentry_tree_lock);
> +	start_work = RB_EMPTY_ROOT(&fc->dentry_tree);
> +	p = &fc->dentry_tree.rb_node;
> +	while (*p) {
> +		parent = *p;
> +		cur = rb_entry(*p, struct dentry_node, node);
> +		if (fuse_dentry_time(dn->dentry) >
> +		    fuse_dentry_time(cur->dentry))
> +			p = &(*p)->rb_left;
> +		else
> +			p = &(*p)->rb_right;
> +	}
> +	rb_link_node(&dn->node, parent, p);
> +	rb_insert_color(&dn->node, &fc->dentry_tree);
> +	spin_unlock(&fc->dentry_tree_lock);
> +	if (start_work)
> +		schedule_delayed_work(&fc->dentry_tree_work,
> +				      secs_to_jiffies(fc->inval_wq));
> +}
> +
> +static void fuse_dentry_tree_del_node(struct dentry *dentry)
> +{
> +	struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb);
> +	struct dentry_node *cur;
> +	struct rb_node **p;
> +
> +	if (!fc->inval_wq)
> +		return;
> +
> +	spin_lock(&fc->dentry_tree_lock);
> +	p = &fc->dentry_tree.rb_node;
> +	while (*p) {
> +		cur = rb_entry(*p, struct dentry_node, node);
> +		if (fuse_dentry_time(dentry) > fuse_dentry_time(cur->dentry))
> +			p = &(*p)->rb_left;
> +		else if (fuse_dentry_time(dentry) <
> +			 fuse_dentry_time(cur->dentry))
> +			p = &(*p)->rb_right;
> +		else {
> +			rb_erase(*p, &fc->dentry_tree);
> +			dput(cur->dentry);
> +			kfree(cur);
> +			break;
> +		}
> +	}
> +	spin_unlock(&fc->dentry_tree_lock);
> +}
> +
> +void fuse_dentry_tree_prune(struct fuse_conn *fc)
> +{
> +	struct rb_node *n;
> +	struct dentry_node *dn;
> +
> +	if (!fc->inval_wq)
> +		return;
> +
> +	fc->inval_wq = 0;
> +	cancel_delayed_work_sync(&fc->dentry_tree_work);
> +
> +	spin_lock(&fc->dentry_tree_lock);
> +	while (!RB_EMPTY_ROOT(&fc->dentry_tree)) {
> +		n = rb_first(&fc->dentry_tree);
> +		dn = rb_entry(n, struct dentry_node, node);
> +		rb_erase(n, &fc->dentry_tree);
> +		dput(dn->dentry);
> +		kfree(dn);
> +	}
> +	spin_unlock(&fc->dentry_tree_lock);
> +}
> +
> +/*
> + * Global workqueue task that will periodically check for expired dentries in
> + * the dentries tree.
> + *
> + * A dentry has expired if:
> + *   1) it has been around for too long or
> + *   2) the connection epoch has been incremented
> + * For this second case, all dentries will be expired.
> + *
> + * The task will be rescheduled as long as the dentries tree is not empty.
> + */
> +void fuse_dentry_tree_work(struct work_struct *work)
> +{
> +	struct fuse_conn *fc = container_of(work, struct fuse_conn,
> +					    dentry_tree_work.work);
> +	struct dentry_node *dn;
> +	struct rb_node *node;
> +	struct dentry *entry;
> +	u64 now;
> +	int epoch;
> +	bool expire_all = false;
> +	bool is_first = true;
> +	bool reschedule;
> +
> +	spin_lock(&fc->dentry_tree_lock);
> +	now = get_jiffies_64();
> +	epoch = atomic_read(&fc->epoch);
> +
> +	node = rb_first(&fc->dentry_tree);
> +
> +	while (node) {
> +		dn = rb_entry(node, struct dentry_node, node);
> +		node = rb_next(node);
> +		entry = dn->dentry;
> +		if (is_first) {
> +			/* expire all entries if epoch was incremented */
> +			if (entry->d_time < epoch)
> +				expire_all = true;
> +			is_first = false;
> +		}
> +		if (expire_all || (fuse_dentry_time(entry) < now)) {
> +			rb_erase(&dn->node, &fc->dentry_tree);
> +			d_invalidate(entry);
> +			dput(entry);
> +			kfree(dn);
> +		} else
> +			break;
> +	}
> +	reschedule = !RB_EMPTY_ROOT(&fc->dentry_tree);
> +	spin_unlock(&fc->dentry_tree_lock);
> +
> +	if (reschedule)
> +		schedule_delayed_work(&fc->dentry_tree_work,
> +				      secs_to_jiffies(fc->inval_wq));
> +}
> +
>  static void fuse_dentry_settime(struct dentry *dentry, u64 time)
>  {
>  	struct fuse_conn *fc = get_fuse_conn_super(dentry->d_sb);
> @@ -81,6 +226,7 @@ static void fuse_dentry_settime(struct dentry *dentry, u64 time)
>  	}
>  
>  	__fuse_dentry_settime(dentry, time);
> +	fuse_dentry_tree_add_node(dentry);
>  }
>  
>  /*
> @@ -280,6 +426,7 @@ static int fuse_dentry_revalidate(struct inode *dir, const struct qstr *name,
>  
>  invalid:
>  	ret = 0;
> +	fuse_dentry_tree_del_node(entry);
>  	goto out;
>  }
>  
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index f870d53a1bcf..60be9d982490 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -603,6 +603,7 @@ struct fuse_fs_context {
>  	enum fuse_dax_mode dax_mode;
>  	unsigned int max_read;
>  	unsigned int blksize;
> +	unsigned int inval_wq;
>  	const char *subtype;
>  
>  	/* DAX device, may be NULL */
> @@ -978,6 +979,15 @@ struct fuse_conn {
>  		/* Request timeout (in jiffies). 0 = no timeout */
>  		unsigned int req_timeout;
>  	} timeout;
> +
> +	/** Cache dentries tree */
> +	struct rb_root dentry_tree;
> +	/** Look to protect dentry_tree access */
> +	spinlock_t dentry_tree_lock;
> +	/** Periodic delayed work to invalidate expired dentries */
> +	struct delayed_work dentry_tree_work;
> +	/** Period for the invalidation workqueue */
> +	unsigned int inval_wq;
>  };
>  
>  /*
> @@ -1262,6 +1272,9 @@ void fuse_wait_aborted(struct fuse_conn *fc);
>  /* Check if any requests timed out */
>  void fuse_check_timeout(struct work_struct *work);
>  
> +void fuse_dentry_tree_prune(struct fuse_conn *fc);
> +void fuse_dentry_tree_work(struct work_struct *work);
> +
>  /**
>   * Invalidate inode attributes
>   */
> diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
> index b399784cca5f..4e9c10e34b2e 100644
> --- a/fs/fuse/inode.c
> +++ b/fs/fuse/inode.c
> @@ -769,6 +769,7 @@ enum {
>  	OPT_ALLOW_OTHER,
>  	OPT_MAX_READ,
>  	OPT_BLKSIZE,
> +	OPT_INVAL_WQ,
>  	OPT_ERR
>  };
>  
> @@ -783,6 +784,7 @@ static const struct fs_parameter_spec fuse_fs_parameters[] = {
>  	fsparam_u32	("max_read",		OPT_MAX_READ),
>  	fsparam_u32	("blksize",		OPT_BLKSIZE),
>  	fsparam_string	("subtype",		OPT_SUBTYPE),
> +	fsparam_u32	("inval_wq",		OPT_INVAL_WQ),
>  	{}
>  };
>  
> @@ -878,6 +880,12 @@ static int fuse_parse_param(struct fs_context *fsc, struct fs_parameter *param)
>  		ctx->blksize = result.uint_32;
>  		break;
>  
> +	case OPT_INVAL_WQ:
> +		if (result.uint_32 < 5)
> +			return invalfc(fsc, "Workqueue period is < 5s");
> +		ctx->inval_wq = result.uint_32;
> +		break;
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -911,6 +919,8 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root)
>  			seq_puts(m, ",allow_other");
>  		if (fc->max_read != ~0)
>  			seq_printf(m, ",max_read=%u", fc->max_read);
> +		if (fc->inval_wq != 0)
> +			seq_printf(m, ",inval_wq=%u", fc->inval_wq);
>  		if (sb->s_bdev && sb->s_blocksize != FUSE_DEFAULT_BLKSIZE)
>  			seq_printf(m, ",blksize=%lu", sb->s_blocksize);
>  	}
> @@ -959,6 +969,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
>  	memset(fc, 0, sizeof(*fc));
>  	spin_lock_init(&fc->lock);
>  	spin_lock_init(&fc->bg_lock);
> +	spin_lock_init(&fc->dentry_tree_lock);
>  	init_rwsem(&fc->killsb);
>  	refcount_set(&fc->count, 1);
>  	atomic_set(&fc->dev_count, 1);
> @@ -968,6 +979,8 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
>  	INIT_LIST_HEAD(&fc->bg_queue);
>  	INIT_LIST_HEAD(&fc->entry);
>  	INIT_LIST_HEAD(&fc->devices);
> +	fc->dentry_tree = RB_ROOT;
> +	fc->inval_wq = 0;
>  	atomic_set(&fc->num_waiting, 0);
>  	fc->max_background = FUSE_DEFAULT_MAX_BACKGROUND;
>  	fc->congestion_threshold = FUSE_DEFAULT_CONGESTION_THRESHOLD;
> @@ -1844,6 +1857,9 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
>  	fc->group_id = ctx->group_id;
>  	fc->legacy_opts_show = ctx->legacy_opts_show;
>  	fc->max_read = max_t(unsigned int, 4096, ctx->max_read);
> +	fc->inval_wq = ctx->inval_wq;
> +	if (fc->inval_wq > 0)
> +		INIT_DELAYED_WORK(&fc->dentry_tree_work, fuse_dentry_tree_work);
>  	fc->destroy = ctx->destroy;
>  	fc->no_control = ctx->no_control;
>  	fc->no_force_umount = ctx->no_force_umount;
> @@ -2009,6 +2025,7 @@ static int fuse_init_fs_context(struct fs_context *fsc)
>  		return -ENOMEM;
>  
>  	ctx->max_read = ~0;
> +	ctx->inval_wq = 0;
>  	ctx->blksize = FUSE_DEFAULT_BLKSIZE;
>  	ctx->legacy_opts_show = true;
>  
> @@ -2048,6 +2065,7 @@ void fuse_conn_destroy(struct fuse_mount *fm)
>  
>  	fuse_abort_conn(fc);
>  	fuse_wait_aborted(fc);
> +	fuse_dentry_tree_prune(fc);
>  
>  	if (!list_empty(&fc->entry)) {
>  		mutex_lock(&fuse_mutex);
>






[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux