The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Luis Henriques <luis@igalia.com>
To: Miklos Szeredi <miklos@szeredi.hu>
Cc: Bernd Schubert <bernd@bsbernd.com>,
	 Laura Promberger <laura.promberger@cern.ch>,
	 Dave Chinner <david@fromorbit.com>,
	 Matt Harvey <mharvey@jumptrading.com>,
	 linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	 kernel-dev@igalia.com
Subject: Re: [RFC PATCH v2] fuse: add optional workqueue to periodically invalidate expired dentries
Date: Mon, 05 May 2025 13:35:28 +0100	[thread overview]
Message-ID: <874ixzfd67.fsf@igalia.com> (raw)
In-Reply-To: <20250415133801.28923-1-luis@igalia.com> (Luis Henriques's message of "Tue, 15 Apr 2025 14:38:01 +0100")

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@igalia.com>
> ---
> * 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);
>


  parent reply	other threads:[~2025-05-05 12:35 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-15 13:38 [RFC PATCH v2] fuse: add optional workqueue to periodically invalidate expired dentries Luis Henriques
2025-04-16 15:55 ` Bernd Schubert
2025-05-05 12:35 ` Luis Henriques [this message]
2025-05-13  9:56 ` Miklos Szeredi
2025-05-13 12:34   ` Luis Henriques

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=874ixzfd67.fsf@igalia.com \
    --to=luis@igalia.com \
    --cc=bernd@bsbernd.com \
    --cc=david@fromorbit.com \
    --cc=kernel-dev@igalia.com \
    --cc=laura.promberger@cern.ch \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mharvey@jumptrading.com \
    --cc=miklos@szeredi.hu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox