[PATCH 6/7] nfsd: filecache: change garbage collection to a timer.

NeilBrown
2025-01-27T01:24:03Z

garbage collection never sleeps and no longer walks a list so it runs
quickly only requiring a spinlock.

This means we don't need to use a workqueue, we can use a simple timer
instead.

Rather than taking the lock in the timer callback, which would require
using _bh locking, simply test a flag and wake an nfsd thread. That
thread checks the flag and ages the lists when needed.

Signed-off-by: NeilBrown <neilb@suse.de>
---
fs/nfsd/filecache.c | 43 ++++++++++++++++++++++++-------------------
1 file changed, 24 insertions(+), 19 deletions(-)

diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 7264faa57280..eb95a53f806f 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -67,10 +67,12 @@ struct nfsd_fcache_disposal {
struct list_head older; /* haven't been used in last 0-2 seconds */
struct list_head freeme; /* ready to be discarded */
unsigned long num_gc; /* Approximate size of recent plus older */
- struct delayed_work filecache_laundrette;
+ struct timer_list timer;
struct shrinker *file_shrinker;
struct nfsd_net *nn;
+ unsigned long flags;
};
+#define NFSD_FCACHE_DO_AGE BIT(0) /* time to age the lists */

static struct kmem_cache *nfsd_file_slab;
static struct kmem_cache *nfsd_file_mark_slab;
@@ -115,8 +117,8 @@ static const struct rhashtable_params nfsd_file_rhash_params = {
static void
nfsd_file_schedule_laundrette(struct nfsd_fcache_disposal *l)
{
- queue_delayed_work(system_unbound_wq, &l->filecache_laundrette,
- NFSD_LAUNDRETTE_DELAY);
+ if (!timer_pending(&l->timer))
+ mod_timer(&l->timer, jiffies + NFSD_LAUNDRETTE_DELAY);
}

static void
@@ -521,6 +523,19 @@ void nfsd_file_net_dispose(struct nfsd_net *nn)
{
struct nfsd_fcache_disposal *l = nn->fcache_disposal;

+ if (test_and_clear_bit(NFSD_FCACHE_DO_AGE, &l->flags)) {
+ spin_lock(&l->lock);
+ list_splice_init(&l->older, &l->freeme);
+ list_splice_init(&l->recent, &l->older);
+ /* We don't know how many were moved to 'freeme' and don't want
+ * to waste time counting - guess a half. This is only used
+ * for the shrinker which doesn't need complete precision.
+ */
+ l->num_gc /= 2;
+ if (!list_empty(&l->older) || !list_empty(&l->recent))
+ mod_timer(&l->timer, jiffies + NFSD_LAUNDRETTE_DELAY);
+ spin_unlock(&l->lock);
+ }
if (!list_empty(&l->freeme)) {
LIST_HEAD(dispose);
int i;
@@ -557,23 +572,13 @@ void nfsd_file_net_dispose(struct nfsd_net *nn)
}

static void
-nfsd_file_gc_worker(struct work_struct *work)
+nfsd_file_gc_worker(struct timer_list *t)
{
struct nfsd_fcache_disposal *l = container_of(
- work, struct nfsd_fcache_disposal, filecache_laundrette.work);
+ t, struct nfsd_fcache_disposal, timer);

- spin_lock(&l->lock);
- list_splice_init(&l->older, &l->freeme);
- list_splice_init(&l->recent, &l->older);
- /* We don't know how many were moved to 'freeme' and don't want
- * to waste time counting - guess a half.
- */
- l->num_gc /= 2;
- if (!list_empty(&l->freeme))
- svc_wake_up(l->nn->nfsd_serv);
- if (!list_empty(&l->older) || !list_empty(&l->recent))
- nfsd_file_schedule_laundrette(l);
- spin_unlock(&l->lock);
+ set_bit(NFSD_FCACHE_DO_AGE, &l->flags);
+ svc_wake_up(l->nn->nfsd_serv);
}

static unsigned long
@@ -868,7 +873,7 @@ nfsd_alloc_fcache_disposal(void)
if (!l)
return NULL;
spin_lock_init(&l->lock);
- INIT_DELAYED_WORK(&l->filecache_laundrette, nfsd_file_gc_worker);
+ timer_setup(&l->timer, nfsd_file_gc_worker, 0);
INIT_LIST_HEAD(&l->recent);
INIT_LIST_HEAD(&l->older);
INIT_LIST_HEAD(&l->freeme);
@@ -891,7 +896,7 @@ nfsd_alloc_fcache_disposal(void)
static void
nfsd_free_fcache_disposal(struct nfsd_fcache_disposal *l)
{
- cancel_delayed_work_sync(&l->filecache_laundrette);
+ del_timer_sync(&l->timer);
shrinker_free(l->file_shrinker);
nfsd_file_release_list(&l->recent);
WARN_ON_ONCE(!list_empty(&l->recent));
--
2.47.1