Re: [PATCH v5 5/5] sunrpc: derive the pool count instead of caching it in sv_nrpools

Jeff Layton <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.nfs
Message-ID <[email protected]>
On Tue, 2026-07-07 at 08:50 +1000, NeilBrown wrote:
> On Mon, 06 Jul 2026, Jeff Layton wrote:
> > Now that the pool mode is always pernode, svc_serv.sv_nrpools is
> > redundant with sv_is_pooled: an unpooled service always has a single
> > pool, and a pooled service has svc_pool_map.npools pools (which is one on
> > a single-node host). sv_nrpools cannot distinguish an unpooled service
> > from a pooled service that happens to have one pool, so it is sv_nrpools,
> > not sv_is_pooled, that carries no unique information.
> > 
> > Replace the cached field with a svc_serv_nrpools() helper that derives
> > the count from sv_is_pooled and the pool map, and convert all readers to
> > it. svc_pool_map is file-local to svc.c, so export the helper for the
> > svc_xprt.c and nfsd callers.
> > 
> > Reading svc_pool_map.npools without svc_pool_map_mutex is safe: the
> > mutex protects only svc_pool_map.count, and npools is already read
> > locklessly in svc_pool_for_cpu().
> > 
> > A pooled service holds a map reference for its whole lifetime, so npools
> > is stable while any reader could observe it.  The hot path
> > (svc_pool_for_cpu()) already dereferences svc_pool_map for to_pool, and
> > npools shares that cacheline, so there is no new locking or coherence
> > cost.
> > 
> > __svc_create() keeps using its local npools argument for the sv_pools[]
> > allocation, since sv_is_pooled is not set until svc_create_pooled() has
> > returned from it.
> > 
> > Doing this also removes a modulus operation from svc_pool_for_cpu(),
> > which should make for more efficient RPC queueing.
> > 
> > Assisted-by: Claude:claude-opus-4-8
> > Suggested-by: NeilBrown <[email protected]>
> > Signed-off-by: Jeff Layton <[email protected]>
> > ---
> >  fs/nfsd/nfsctl.c           |  2 +-
> >  fs/nfsd/nfssvc.c           | 10 ++++-----
> >  include/linux/sunrpc/svc.h |  2 +-
> >  net/sunrpc/svc.c           | 52 ++++++++++++++++++++++++++++++++--------------
> >  net/sunrpc/svc_xprt.c      |  6 +++---
> >  5 files changed, 46 insertions(+), 26 deletions(-)
> > 
> > diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c
> > index bc16fc7ca24f..0543e5bb842f 100644
> > --- a/fs/nfsd/nfsctl.c
> > +++ b/fs/nfsd/nfsctl.c
> > @@ -1526,7 +1526,7 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
> >  
> >  	rcu_read_lock();
> >  
> > -	for (i = 0; i < nn->nfsd_serv->sv_nrpools; i++) {
> > +	for (i = 0; i < svc_serv_nrpools(nn->nfsd_serv); i++) {
> >  		struct svc_rqst *rqstp;
> >  		long thread_skip = 0;
> >  
> > diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c
> > index a8ea4dbfa56b..2edf716ea022 100644
> > --- a/fs/nfsd/nfssvc.c
> > +++ b/fs/nfsd/nfssvc.c
> > @@ -655,7 +655,7 @@ int nfsd_nrpools(struct net *net)
> >  	if (nn->nfsd_serv == NULL)
> >  		return 0;
> >  	else
> > -		return nn->nfsd_serv->sv_nrpools;
> > +		return svc_serv_nrpools(nn->nfsd_serv);
> >  }
> >  
> >  int nfsd_get_nrthreads(int n, int *nthreads, struct net *net)
> > @@ -665,7 +665,7 @@ int nfsd_get_nrthreads(int n, int *nthreads, struct net *net)
> >  	int i;
> >  
> >  	if (serv)
> > -		for (i = 0; i < serv->sv_nrpools && i < n; i++)
> > +		for (i = 0; i < svc_serv_nrpools(serv) && i < n; i++)
> >  			nthreads[i] = serv->sv_pools[i].sp_nrthrmax;
> >  	return 0;
> >  }
> > @@ -699,8 +699,8 @@ int nfsd_set_nrthreads(int n, int *nthreads, struct net *net)
> >  	if (n == 1)
> >  		return svc_set_num_threads(nn->nfsd_serv, nn->min_threads, nthreads[0]);
> >  
> > -	if (n > nn->nfsd_serv->sv_nrpools)
> > -		n = nn->nfsd_serv->sv_nrpools;
> > +	if (n > svc_serv_nrpools(nn->nfsd_serv))
> > +		n = svc_serv_nrpools(nn->nfsd_serv);
> >  
> >  	/* enforce a global maximum number of threads */
> >  	tot = 0;
> > @@ -731,7 +731,7 @@ int nfsd_set_nrthreads(int n, int *nthreads, struct net *net)
> >  	}
> >  
> >  	/* Anything undefined in array is considered to be 0 */
> > -	for (i = n; i < nn->nfsd_serv->sv_nrpools; ++i) {
> > +	for (i = n; i < svc_serv_nrpools(nn->nfsd_serv); ++i) {
> >  		err = svc_set_pool_threads(nn->nfsd_serv,
> >  					   &nn->nfsd_serv->sv_pools[i],
> >  					   0, 0);
> > diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h
> > index 3a0152d926fb..3c885ab6ad41 100644
> > --- a/include/linux/sunrpc/svc.h
> > +++ b/include/linux/sunrpc/svc.h
> > @@ -85,7 +85,6 @@ struct svc_serv {
> >  
> >  	char *			sv_name;	/* service name */
> >  
> > -	unsigned int		sv_nrpools;	/* number of thread pools */
> >  	bool			sv_is_pooled;	/* is this a pooled service? */
> >  	struct svc_pool *	sv_pools;	/* array of thread pools */
> >  	int			(*sv_threadfn)(void *data);
> > @@ -480,6 +479,7 @@ void		   svc_wake_up(struct svc_serv *);
> >  void		   svc_reserve(struct svc_rqst *rqstp, int space);
> >  void		   svc_pool_wake_idle_thread(struct svc_pool *pool);
> >  struct svc_pool   *svc_pool_for_cpu(struct svc_serv *serv);
> > +unsigned int	   svc_serv_nrpools(const struct svc_serv *serv);
> >  char *		   svc_print_addr(struct svc_rqst *, char *, size_t);
> >  const char *	   svc_proc_name(const struct svc_rqst *rqstp);
> >  int		   svc_encode_result_payload(struct svc_rqst *rqstp,
> > diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> > index ece69cb0138a..800514a14f17 100644
> > --- a/net/sunrpc/svc.c
> > +++ b/net/sunrpc/svc.c
> > @@ -224,7 +224,7 @@ svc_pool_map_set_cpumask(struct task_struct *task, unsigned int pidx)
> >  	unsigned int node = m->pool_to[pidx];
> >  
> >  	/*
> > -	 * The caller checks for sv_nrpools > 1, which
> > +	 * The caller checks for more than one pool, which
> >  	 * implies that we've been initialized.
> >  	 */
> >  	WARN_ON_ONCE(m->count == 0);
> > @@ -234,6 +234,24 @@ svc_pool_map_set_cpumask(struct task_struct *task, unsigned int pidx)
> >  	set_cpus_allowed_ptr(task, cpumask_of_node(node));
> >  }
> >  
> > +/**
> > + * svc_serv_nrpools - number of thread pools backing a service
> > + * @serv: An RPC service
> > + *
> > + * Pooled services all share the global svc_pool_map, so their pool count
> > + * is svc_pool_map.npools. Unpooled services have a single pool. Reading
> > + * npools without svc_pool_map_mutex is safe: a pooled service holds a map
> > + * reference for its whole lifetime, so npools is stable once set.
> > + *
> > + * Return value:
> > + *   The number of pools in @serv
> > + */
> > +unsigned int svc_serv_nrpools(const struct svc_serv *serv)
> > +{
> > +	return serv->sv_is_pooled ? svc_pool_map.npools : 1;
> > +}
> > +EXPORT_SYMBOL_GPL(svc_serv_nrpools);
> 
> I would make this a static-inline.
> 

That would mean that we would have to export svc_pool_map, which is
currently private to svc.c.

> > +
> >  /**
> >   * svc_pool_for_cpu - Select pool to run a thread on this cpu
> >   * @serv: An RPC service
> > @@ -247,12 +265,15 @@ svc_pool_map_set_cpumask(struct task_struct *task, unsigned int pidx)
> >  struct svc_pool *svc_pool_for_cpu(struct svc_serv *serv)
> >  {
> >  	struct svc_pool_map *m = &svc_pool_map;
> > +	unsigned int nrpools = svc_serv_nrpools(serv);
> >  	unsigned int pidx, i;
> >  
> > -	if (serv->sv_nrpools <= 1)
> > +	if (nrpools <= 1)
> >  		return serv->sv_pools;
> >  
> > -	pidx = m->to_pool[cpu_to_node(raw_smp_processor_id())] % serv->sv_nrpools;
> > +	pidx = m->to_pool[cpu_to_node(raw_smp_processor_id())];
> > +	if (pidx >= nrpools)
> > +		pidx = 0;
> 
> The values stored in svc_pool_map.to_pool are all less than
> svc_pool_map.npools.
> So that if() condition cannot be true. 
> 
> But those two things don't reduce the correctness of the patch.
> 
> Reviewed-by: NeilBrown <[email protected]>
> 
> Thanks for doing this.
> NeilBrown
> 

Ahh good point. I guess we can remove that if statement. I can send
Chuck a follow-on patch. Thanks for the R-b!

> 
> >  
> >  	/*
> >  	 * It's possible to have a pool with no threads. Userland can just set
> > @@ -265,7 +286,7 @@ struct svc_pool *svc_pool_for_cpu(struct svc_serv *serv)
> >  	 * populated pool, trading NUMA locality for a guarantee that the
> >  	 * transport is serviced.
> >  	 */
> > -	for (i = 0; i < serv->sv_nrpools; i++) {
> > +	for (i = 0; i < nrpools; i++) {
> >  		struct svc_pool *pool = &serv->sv_pools[pidx];
> >  
> >  		/* This is set under the sp_mutex and rarely ever changes. A
> > @@ -274,7 +295,7 @@ struct svc_pool *svc_pool_for_cpu(struct svc_serv *serv)
> >  		if (data_race(pool->sp_nrthreads))
> >  			return pool;
> >  
> > -		if (++pidx >= serv->sv_nrpools)
> > +		if (++pidx >= nrpools)
> >  			pidx = 0;
> >  	}
> >  
> > @@ -414,15 +435,13 @@ __svc_create(struct svc_program *prog, int nprogs, struct svc_stat *stats,
> >  
> >  	__svc_init_bc(serv);
> >  
> > -	serv->sv_nrpools = npools;
> > -	serv->sv_pools =
> > -		kzalloc_objs(struct svc_pool, serv->sv_nrpools);
> > +	serv->sv_pools = kzalloc_objs(struct svc_pool, npools);
> >  	if (!serv->sv_pools) {
> >  		kfree(serv);
> >  		return NULL;
> >  	}
> >  
> > -	for (i = 0; i < serv->sv_nrpools; i++) {
> > +	for (i = 0; i < npools; i++) {
> >  		struct svc_pool *pool = &serv->sv_pools[i];
> >  
> >  		dprintk("svc: initialising pool %u for %s\n",
> > @@ -520,7 +539,7 @@ svc_destroy(struct svc_serv **servp)
> >  
> >  	cache_clean_deferred(serv);
> >  
> > -	for (i = 0; i < serv->sv_nrpools; i++) {
> > +	for (i = 0; i < svc_serv_nrpools(serv); i++) {
> >  		struct svc_pool *pool = &serv->sv_pools[i];
> >  
> >  		svc_pool_destroy_counters(pool);
> > @@ -732,7 +751,7 @@ int svc_new_thread(struct svc_serv *serv, struct svc_pool *pool)
> >  	}
> >  
> >  	rqstp->rq_task = task;
> > -	if (serv->sv_nrpools > 1)
> > +	if (svc_serv_nrpools(serv) > 1)
> >  		svc_pool_map_set_cpumask(task, pool->sp_id);
> >  
> >  	svc_sock_update_bufs(serv);
> > @@ -858,8 +877,9 @@ int
> >  svc_set_num_threads(struct svc_serv *serv, unsigned int min_threads,
> >  		    unsigned int nrservs)
> >  {
> > -	unsigned int base = nrservs / serv->sv_nrpools;
> > -	unsigned int remain = nrservs % serv->sv_nrpools;
> > +	unsigned int nrpools = svc_serv_nrpools(serv);
> > +	unsigned int base = nrservs / nrpools;
> > +	unsigned int remain = nrservs % nrpools;
> >  	int i, err = 0;
> >  
> >  	/*
> > @@ -870,9 +890,9 @@ svc_set_num_threads(struct svc_serv *serv, unsigned int min_threads,
> >  	 * @nrservs.
> >  	 */
> >  	if (base == 0 && nrservs != 0)
> > -		remain = serv->sv_nrpools;
> > +		remain = nrpools;
> >  
> > -	for (i = 0; i < serv->sv_nrpools; ++i) {
> > +	for (i = 0; i < nrpools; ++i) {
> >  		struct svc_pool *pool = &serv->sv_pools[i];
> >  		int threads = base;
> >  
> > @@ -906,7 +926,7 @@ unsigned int svc_serv_maxthreads(const struct svc_serv *serv)
> >  {
> >  	unsigned int i, max = 0;
> >  
> > -	for (i = 0; i < serv->sv_nrpools; i++)
> > +	for (i = 0; i < svc_serv_nrpools(serv); i++)
> >  		max += data_race(serv->sv_pools[i].sp_nrthrmax);
> >  	return max;
> >  }
> > diff --git a/net/sunrpc/svc_xprt.c b/net/sunrpc/svc_xprt.c
> > index 63d1002e63e7..40040af588fb 100644
> > --- a/net/sunrpc/svc_xprt.c
> > +++ b/net/sunrpc/svc_xprt.c
> > @@ -1188,7 +1188,7 @@ static void svc_clean_up_xprts(struct svc_serv *serv, struct net *net)
> >  	struct svc_xprt *xprt;
> >  	int i;
> >  
> > -	for (i = 0; i < serv->sv_nrpools; i++) {
> > +	for (i = 0; i < svc_serv_nrpools(serv); i++) {
> >  		struct svc_pool *pool = &serv->sv_pools[i];
> >  		struct llist_node *q, **t1, *t2;
> >  
> > @@ -1517,7 +1517,7 @@ static void *svc_pool_stats_start(struct seq_file *m, loff_t *pos)
> >  		return SEQ_START_TOKEN;
> >  	if (!si->serv)
> >  		return NULL;
> > -	return pidx > si->serv->sv_nrpools ? NULL
> > +	return pidx > svc_serv_nrpools(si->serv) ? NULL
> >  		: &si->serv->sv_pools[pidx - 1];
> >  }
> >  
> > @@ -1535,7 +1535,7 @@ static void *svc_pool_stats_next(struct seq_file *m, void *p, loff_t *pos)
> >  		pool = &serv->sv_pools[0];
> >  	} else {
> >  		unsigned int pidx = (pool - &serv->sv_pools[0]);
> > -		if (pidx < serv->sv_nrpools-1)
> > +		if (pidx < svc_serv_nrpools(serv) - 1)
> >  			pool = &serv->sv_pools[pidx+1];
> >  		else
> >  			pool = NULL;
> > 
> > -- 
> > 2.55.0
> > 
> > 

-- 
Jeff Layton <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.