* [PATCH] sunrpc: fix lost wakeups when stopping service threads
@ 2026-10-30 13:06 Kal Cutter Conley
2026-10-30 21:43 ` Chuck Lever
2026-10-30 22:45 ` NeilBrown
0 siblings, 2 replies; 7+ messages in thread
From: Kal Cutter Conley @ 2026-10-30 13:06 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman
Cc: Kal Cutter Conley, linux-nfs, netdev, linux-kernel
Stopping NFS can leave rpc.nfsd 0 in an uninterruptible wait with
this stack:
svc_stop_kthreads [sunrpc]
svc_set_num_threads [sunrpc]
nfsd_svc [nfsd]
write_threads [nfsd]
nfsctl_transaction_write [nfsd]
SP_NEED_VICTIM and SP_VICTIM_REMAINS remain set while all remaining
nfsd threads sleep in svc_recv() and none has RQ_VICTIM set.
The stop request is published with unordered set_bit() operations.
svc_pool_wake_idle_thread() checks the idle list and task state before
calling wake_up_process(). If it sees an empty list or TASK_RUNNING,
it skips the wakeup and its memory barrier. The requester can therefore
miss a sleeping worker while that worker misses the stop request.
Order work publication before both lockless checks, even when no
wakeup occurs. Pair with set_current_state() and llist_add() in
svc_thread_wait_for_work(), so the worker observes the pending work or
the requester observes the idle worker and wakes it.
Fixes: 60749cbe3d8a ("sunrpc: change sp_nrthreads from atomic_t to unsigned int.")
Signed-off-by: Kal Cutter Conley <kalcutterc@nvidia.com>
---
net/sunrpc/svc.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
index af8d87c1de83..f3cc63acc5e1 100644
--- a/net/sunrpc/svc.c
+++ b/net/sunrpc/svc.c
@@ -29,6 +29,8 @@
#include <linux/sunrpc/clnt.h>
#include <linux/sunrpc/bc_xprt.h>
+#include <asm/barrier.h>
+
#include <trace/events/sunrpc.h>
#include "fail.h"
@@ -693,6 +695,13 @@ void svc_pool_wake_idle_thread(struct svc_pool *pool)
struct svc_rqst *rqstp;
struct llist_node *ln;
+ /*
+ * Order work publication before the idle-list and task-state checks,
+ * even when wake_up_process() is skipped. Pairs with the barriers in
+ * set_current_state() and llist_add() in svc_thread_wait_for_work().
+ */
+ smp_mb();
+
rcu_read_lock();
ln = READ_ONCE(pool->sp_idle_threads.first);
if (ln) {
base-commit: b803428098f6fcd584db9f0499217b80e465bb34
--
2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] sunrpc: fix lost wakeups when stopping service threads
2026-10-30 13:06 [PATCH] sunrpc: fix lost wakeups when stopping service threads Kal Cutter Conley
@ 2026-10-30 21:43 ` Chuck Lever
2026-10-30 22:45 ` NeilBrown
1 sibling, 0 replies; 7+ messages in thread
From: Chuck Lever @ 2026-10-30 21:43 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kal Cutter Conley
Cc: linux-nfs, netdev, linux-kernel
On Fri, 30 Oct 2026 14:06:28 +0100, Kal Cutter Conley wrote:
> Stopping NFS can leave rpc.nfsd 0 in an uninterruptible wait with
> this stack:
>
> svc_stop_kthreads [sunrpc]
> svc_set_num_threads [sunrpc]
> nfsd_svc [nfsd]
> write_threads [nfsd]
> nfsctl_transaction_write [nfsd]
>
> [...]
Applied to nfsd-testing, thanks!
[1/1] sunrpc: fix lost wakeups when stopping service threads
commit: 7073ba6c3383159deb5c1fd47b5b91a68bbcfd04
--
Chuck Lever
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] sunrpc: fix lost wakeups when stopping service threads
2026-10-30 13:06 [PATCH] sunrpc: fix lost wakeups when stopping service threads Kal Cutter Conley
2026-10-30 21:43 ` Chuck Lever
@ 2026-10-30 22:45 ` NeilBrown
2026-10-31 0:46 ` Hillf Danton
2026-10-31 14:01 ` Chuck Lever
1 sibling, 2 replies; 7+ messages in thread
From: NeilBrown @ 2026-10-30 22:45 UTC (permalink / raw)
To: Kal Cutter Conley
Cc: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
Olga Kornievskaia, Dai Ngo, Tom Talpey, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Kal Cutter Conley, linux-nfs, netdev, linux-kernel
On Sat, 31 Oct 2026, Kal Cutter Conley wrote:
> Stopping NFS can leave rpc.nfsd 0 in an uninterruptible wait with
> this stack:
>
> svc_stop_kthreads [sunrpc]
> svc_set_num_threads [sunrpc]
> nfsd_svc [nfsd]
> write_threads [nfsd]
> nfsctl_transaction_write [nfsd]
>
> SP_NEED_VICTIM and SP_VICTIM_REMAINS remain set while all remaining
> nfsd threads sleep in svc_recv() and none has RQ_VICTIM set.
>
> The stop request is published with unordered set_bit() operations.
> svc_pool_wake_idle_thread() checks the idle list and task state before
> calling wake_up_process(). If it sees an empty list or TASK_RUNNING,
> it skips the wakeup and its memory barrier. The requester can therefore
> miss a sleeping worker while that worker misses the stop request.
>
> Order work publication before both lockless checks, even when no
> wakeup occurs. Pair with set_current_state() and llist_add() in
> svc_thread_wait_for_work(), so the worker observes the pending work or
> the requester observes the idle worker and wakes it.
>
> Fixes: 60749cbe3d8a ("sunrpc: change sp_nrthreads from atomic_t to unsigned int.")
> Signed-off-by: Kal Cutter Conley <kalcutterc@nvidia.com>
> ---
> net/sunrpc/svc.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> index af8d87c1de83..f3cc63acc5e1 100644
> --- a/net/sunrpc/svc.c
> +++ b/net/sunrpc/svc.c
> @@ -29,6 +29,8 @@
> #include <linux/sunrpc/clnt.h>
> #include <linux/sunrpc/bc_xprt.h>
>
> +#include <asm/barrier.h>
> +
> #include <trace/events/sunrpc.h>
>
> #include "fail.h"
> @@ -693,6 +695,13 @@ void svc_pool_wake_idle_thread(struct svc_pool *pool)
> struct svc_rqst *rqstp;
> struct llist_node *ln;
>
> + /*
> + * Order work publication before the idle-list and task-state checks,
> + * even when wake_up_process() is skipped. Pairs with the barriers in
> + * set_current_state() and llist_add() in svc_thread_wait_for_work().
> + */
> + smp_mb();
> +
I don't like this. Stand-alone barriers are, I think, harder to read
than barriers combined with operations, like smp_load_acquire() or
similar. And this one seems quite detached from anything.
If I understand correctly, the possible race is between
svc_thread_wait_for_work which includes:
llist_add(&rqstp->rq_idle, &pool->sp_idle_threads);
if (likely(svc_thread_should_sleep(rqstp)))
and svc_stop_kthread which includes:
set_bit(SP_NEED_VICTIM, &pool->sp_flags);
svc_pool_wake_idle_thread(pool);
The first sets ->sp_idle_threads.first and then checks SP_NEED_VICTIM
and the second sets SP_NEED_VICTIM and then checks
->sp_idle_threads.first.
So we need barriers to ensure the values are seen is the expected order.
Have I missed anything?
llist_add() always calls try_cmpxchg() which is fully ordered, so no
extra barriers are needed there.
There is no barrier between the set_bit)( and the READ_ONCE() in
svc_pool_wake_idle_thread(), so you added one. I agree that is correct
and solves the problem.
But it is only this call to svc_pool_wake_idle_thread() that needs a
barrier, not all calls. So I would prefer the barrier went in
svc_stop_kthreads(). Do you have a reason for not including it there?
We do need a full memory barrier - a "release" on the set_bit and an
"acquire" on the test of ->sp_idle_threads.first
I would be happy with smp_load_acquire(->sp_idle_theads_first) in
svc_pool_wake_idle_thread(). I'm not sure it is needed though.
I would like set_bit_release(SP_NEED_VICTIM) in svc_stop_kthreads()
but we don't have that.
We could do
atomic_long_fetch_or_release(BIT(SP_NEED_VICTIM), (atomic_long_t*)&pool->sp_flags)
but that is ugly.
So I think the best solution is
set_bit(SP_NEED_VICTIM, &pool->sp_flags);
/* Order SP_NEED_VICTIM before testing sp_idle_threads,
* matching svc_thread_wait_for_work() which orders
* setting sp_idle_threads before testing the bit
*/
+ smp_mb__after_atomic();
svc_pool_wake_idle_thread(pool);
We probably need the same in svc_wake_up() after the set_bit().
Does this make the barriers easier to understand?
Thoughts?
Thanks,
NeilBrown
> rcu_read_lock();
> ln = READ_ONCE(pool->sp_idle_threads.first);
> if (ln) {
>
> base-commit: b803428098f6fcd584db9f0499217b80e465bb34
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] sunrpc: fix lost wakeups when stopping service threads
2026-10-30 22:45 ` NeilBrown
@ 2026-10-31 0:46 ` Hillf Danton
2026-10-31 21:49 ` NeilBrown
2026-10-31 14:01 ` Chuck Lever
1 sibling, 1 reply; 7+ messages in thread
From: Hillf Danton @ 2026-10-31 0:46 UTC (permalink / raw)
To: NeilBrown; +Cc: Kal Cutter Conley, Chuck Lever, linux-nfs, linux-kernel
On Sat, 31 Oct 2026 09:45:39 +1100 NeilBrown wrote:
> On Sat, 31 Oct 2026, Kal Cutter Conley wrote:
> > Stopping NFS can leave rpc.nfsd 0 in an uninterruptible wait with
> > this stack:
> >
> > svc_stop_kthreads [sunrpc]
> > svc_set_num_threads [sunrpc]
> > nfsd_svc [nfsd]
> > write_threads [nfsd]
> > nfsctl_transaction_write [nfsd]
> >
> > SP_NEED_VICTIM and SP_VICTIM_REMAINS remain set while all remaining
> > nfsd threads sleep in svc_recv() and none has RQ_VICTIM set.
> >
> > The stop request is published with unordered set_bit() operations.
> > svc_pool_wake_idle_thread() checks the idle list and task state before
> > calling wake_up_process(). If it sees an empty list or TASK_RUNNING,
> > it skips the wakeup and its memory barrier. The requester can therefore
> > miss a sleeping worker while that worker misses the stop request.
> >
> > Order work publication before both lockless checks, even when no
> > wakeup occurs. Pair with set_current_state() and llist_add() in
> > svc_thread_wait_for_work(), so the worker observes the pending work or
> > the requester observes the idle worker and wakes it.
> >
> > Fixes: 60749cbe3d8a ("sunrpc: change sp_nrthreads from atomic_t to unsigned int.")
> > Signed-off-by: Kal Cutter Conley <kalcutterc@nvidia.com>
> > ---
> > net/sunrpc/svc.c | 9 +++++++++
> > 1 file changed, 9 insertions(+)
> >
> > diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> > index af8d87c1de83..f3cc63acc5e1 100644
> > --- a/net/sunrpc/svc.c
> > +++ b/net/sunrpc/svc.c
> > @@ -29,6 +29,8 @@
> > #include <linux/sunrpc/clnt.h>
> > #include <linux/sunrpc/bc_xprt.h>
> >
> > +#include <asm/barrier.h>
> > +
> > #include <trace/events/sunrpc.h>
> >
> > #include "fail.h"
> > @@ -693,6 +695,13 @@ void svc_pool_wake_idle_thread(struct svc_pool *pool)
> > struct svc_rqst *rqstp;
> > struct llist_node *ln;
> >
> > + /*
> > + * Order work publication before the idle-list and task-state checks,
> > + * even when wake_up_process() is skipped. Pairs with the barriers in
> > + * set_current_state() and llist_add() in svc_thread_wait_for_work().
> > + */
> > + smp_mb();
> > +
>
> I don't like this. Stand-alone barriers are, I think, harder to read
> than barriers combined with operations, like smp_load_acquire() or
> similar. And this one seems quite detached from anything.
>
> If I understand correctly, the possible race is between
> svc_thread_wait_for_work which includes:
>
> llist_add(&rqstp->rq_idle, &pool->sp_idle_threads);
> if (likely(svc_thread_should_sleep(rqstp)))
>
> and svc_stop_kthread which includes:
>
> set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> svc_pool_wake_idle_thread(pool);
>
> The first sets ->sp_idle_threads.first and then checks SP_NEED_VICTIM
> and the second sets SP_NEED_VICTIM and then checks
> ->sp_idle_threads.first.
>
> So we need barriers to ensure the values are seen is the expected order.
> Have I missed anything?
>
> llist_add() always calls try_cmpxchg() which is fully ordered, so no
> extra barriers are needed there.
>
> There is no barrier between the set_bit)( and the READ_ONCE() in
> svc_pool_wake_idle_thread(), so you added one. I agree that is correct
> and solves the problem.
> But it is only this call to svc_pool_wake_idle_thread() that needs a
> barrier, not all calls. So I would prefer the barrier went in
> svc_stop_kthreads(). Do you have a reason for not including it there?
>
> We do need a full memory barrier - a "release" on the set_bit and an
> "acquire" on the test of ->sp_idle_threads.first
>
> I would be happy with smp_load_acquire(->sp_idle_theads_first) in
> svc_pool_wake_idle_thread(). I'm not sure it is needed though.
>
> I would like set_bit_release(SP_NEED_VICTIM) in svc_stop_kthreads()
> but we don't have that.
It is on the way [11].
[11] Subject: [PATCH 1/2] asm-generic/bitops: Add set_bit_release()
https://lore.kernel.org/lkml/20261021095512.1345191-3-phasta@kernel.org/
> We could do
> atomic_long_fetch_or_release(BIT(SP_NEED_VICTIM), (atomic_long_t*)&pool->sp_flags)
> but that is ugly.
>
> So I think the best solution is
>
> set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> /* Order SP_NEED_VICTIM before testing sp_idle_threads,
> * matching svc_thread_wait_for_work() which orders
> * setting sp_idle_threads before testing the bit
> */
> + smp_mb__after_atomic();
> svc_pool_wake_idle_thread(pool);
>
> We probably need the same in svc_wake_up() after the set_bit().
>
> Does this make the barriers easier to understand?
>
> Thoughts?
>
> Thanks,
> NeilBrown
>
>
> > rcu_read_lock();
> > ln = READ_ONCE(pool->sp_idle_threads.first);
> > if (ln) {
> >
> > base-commit: b803428098f6fcd584db9f0499217b80e465bb34
> > --
> > 2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] sunrpc: fix lost wakeups when stopping service threads
2026-10-30 22:45 ` NeilBrown
2026-10-31 0:46 ` Hillf Danton
@ 2026-10-31 14:01 ` Chuck Lever
1 sibling, 0 replies; 7+ messages in thread
From: Chuck Lever @ 2026-10-31 14:01 UTC (permalink / raw)
To: NeilBrown, Kal Cutter Conley
Cc: Trond Myklebust, Anna Schumaker, Jeff Layton, Olga Kornievskaia,
Dai Ngo, Tom Talpey, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-nfs, netdev,
linux-kernel
On Fri, Oct 30, 2026, at 6:45 PM, NeilBrown wrote:
> On Sat, 31 Oct 2026, Kal Cutter Conley wrote:
>> Stopping NFS can leave rpc.nfsd 0 in an uninterruptible wait with
>> this stack:
>>
>> svc_stop_kthreads [sunrpc]
>> svc_set_num_threads [sunrpc]
>> nfsd_svc [nfsd]
>> write_threads [nfsd]
>> nfsctl_transaction_write [nfsd]
>>
>> SP_NEED_VICTIM and SP_VICTIM_REMAINS remain set while all remaining
>> nfsd threads sleep in svc_recv() and none has RQ_VICTIM set.
>>
>> The stop request is published with unordered set_bit() operations.
>> svc_pool_wake_idle_thread() checks the idle list and task state before
>> calling wake_up_process(). If it sees an empty list or TASK_RUNNING,
>> it skips the wakeup and its memory barrier. The requester can therefore
>> miss a sleeping worker while that worker misses the stop request.
>>
>> Order work publication before both lockless checks, even when no
>> wakeup occurs. Pair with set_current_state() and llist_add() in
>> svc_thread_wait_for_work(), so the worker observes the pending work or
>> the requester observes the idle worker and wakes it.
>>
>> Fixes: 60749cbe3d8a ("sunrpc: change sp_nrthreads from atomic_t to unsigned int.")
>> Signed-off-by: Kal Cutter Conley <kalcutterc@nvidia.com>
>> ---
>> net/sunrpc/svc.c | 9 +++++++++
>> 1 file changed, 9 insertions(+)
>>
>> diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
>> index af8d87c1de83..f3cc63acc5e1 100644
>> --- a/net/sunrpc/svc.c
>> +++ b/net/sunrpc/svc.c
>> @@ -29,6 +29,8 @@
>> #include <linux/sunrpc/clnt.h>
>> #include <linux/sunrpc/bc_xprt.h>
>>
>> +#include <asm/barrier.h>
>> +
>> #include <trace/events/sunrpc.h>
>>
>> #include "fail.h"
>> @@ -693,6 +695,13 @@ void svc_pool_wake_idle_thread(struct svc_pool *pool)
>> struct svc_rqst *rqstp;
>> struct llist_node *ln;
>>
>> + /*
>> + * Order work publication before the idle-list and task-state checks,
>> + * even when wake_up_process() is skipped. Pairs with the barriers in
>> + * set_current_state() and llist_add() in svc_thread_wait_for_work().
>> + */
>> + smp_mb();
>> +
>
> I don't like this. Stand-alone barriers are, I think, harder to read
> than barriers combined with operations, like smp_load_acquire() or
> similar. And this one seems quite detached from anything.
>
> If I understand correctly, the possible race is between
> svc_thread_wait_for_work which includes:
>
> llist_add(&rqstp->rq_idle, &pool->sp_idle_threads);
> if (likely(svc_thread_should_sleep(rqstp)))
>
> and svc_stop_kthread which includes:
>
> set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> svc_pool_wake_idle_thread(pool);
>
> The first sets ->sp_idle_threads.first and then checks SP_NEED_VICTIM
> and the second sets SP_NEED_VICTIM and then checks
> ->sp_idle_threads.first.
>
> So we need barriers to ensure the values are seen is the expected order.
> Have I missed anything?
>
> llist_add() always calls try_cmpxchg() which is fully ordered, so no
> extra barriers are needed there.
>
> There is no barrier between the set_bit)( and the READ_ONCE() in
> svc_pool_wake_idle_thread(), so you added one. I agree that is correct
> and solves the problem.
> But it is only this call to svc_pool_wake_idle_thread() that needs a
> barrier, not all calls. So I would prefer the barrier went in
> svc_stop_kthreads(). Do you have a reason for not including it there?
>
> We do need a full memory barrier - a "release" on the set_bit and an
> "acquire" on the test of ->sp_idle_threads.first
>
> I would be happy with smp_load_acquire(->sp_idle_theads_first) in
> svc_pool_wake_idle_thread(). I'm not sure it is needed though.
>
> I would like set_bit_release(SP_NEED_VICTIM) in svc_stop_kthreads()
> but we don't have that.
> We could do
> atomic_long_fetch_or_release(BIT(SP_NEED_VICTIM),
> (atomic_long_t*)&pool->sp_flags)
> but that is ugly.
>
> So I think the best solution is
>
> set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> /* Order SP_NEED_VICTIM before testing sp_idle_threads,
> * matching svc_thread_wait_for_work() which orders
> * setting sp_idle_threads before testing the bit
> */
> + smp_mb__after_atomic();
> svc_pool_wake_idle_thread(pool);
>
> We probably need the same in svc_wake_up() after the set_bit().
Kal's patch as written adds an mfence-class instruction to every transport
enqueue (a common case) for no ordering benefit. So let's go with Neil's
proposed solution. Kal, can you send a v2?
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] sunrpc: fix lost wakeups when stopping service threads
2026-10-31 0:46 ` Hillf Danton
@ 2026-10-31 21:49 ` NeilBrown
2026-10-31 22:57 ` Hillf Danton
0 siblings, 1 reply; 7+ messages in thread
From: NeilBrown @ 2026-10-31 21:49 UTC (permalink / raw)
To: Hillf Danton; +Cc: Kal Cutter Conley, Chuck Lever, linux-nfs, linux-kernel
On Sat, 31 Oct 2026, Hillf Danton wrote:
> On Sat, 31 Oct 2026 09:45:39 +1100 NeilBrown wrote:
> > On Sat, 31 Oct 2026, Kal Cutter Conley wrote:
> > > Stopping NFS can leave rpc.nfsd 0 in an uninterruptible wait with
> > > this stack:
> > >
> > > svc_stop_kthreads [sunrpc]
> > > svc_set_num_threads [sunrpc]
> > > nfsd_svc [nfsd]
> > > write_threads [nfsd]
> > > nfsctl_transaction_write [nfsd]
> > >
> > > SP_NEED_VICTIM and SP_VICTIM_REMAINS remain set while all remaining
> > > nfsd threads sleep in svc_recv() and none has RQ_VICTIM set.
> > >
> > > The stop request is published with unordered set_bit() operations.
> > > svc_pool_wake_idle_thread() checks the idle list and task state before
> > > calling wake_up_process(). If it sees an empty list or TASK_RUNNING,
> > > it skips the wakeup and its memory barrier. The requester can therefore
> > > miss a sleeping worker while that worker misses the stop request.
> > >
> > > Order work publication before both lockless checks, even when no
> > > wakeup occurs. Pair with set_current_state() and llist_add() in
> > > svc_thread_wait_for_work(), so the worker observes the pending work or
> > > the requester observes the idle worker and wakes it.
> > >
> > > Fixes: 60749cbe3d8a ("sunrpc: change sp_nrthreads from atomic_t to unsigned int.")
> > > Signed-off-by: Kal Cutter Conley <kalcutterc@nvidia.com>
> > > ---
> > > net/sunrpc/svc.c | 9 +++++++++
> > > 1 file changed, 9 insertions(+)
> > >
> > > diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> > > index af8d87c1de83..f3cc63acc5e1 100644
> > > --- a/net/sunrpc/svc.c
> > > +++ b/net/sunrpc/svc.c
> > > @@ -29,6 +29,8 @@
> > > #include <linux/sunrpc/clnt.h>
> > > #include <linux/sunrpc/bc_xprt.h>
> > >
> > > +#include <asm/barrier.h>
> > > +
> > > #include <trace/events/sunrpc.h>
> > >
> > > #include "fail.h"
> > > @@ -693,6 +695,13 @@ void svc_pool_wake_idle_thread(struct svc_pool *pool)
> > > struct svc_rqst *rqstp;
> > > struct llist_node *ln;
> > >
> > > + /*
> > > + * Order work publication before the idle-list and task-state checks,
> > > + * even when wake_up_process() is skipped. Pairs with the barriers in
> > > + * set_current_state() and llist_add() in svc_thread_wait_for_work().
> > > + */
> > > + smp_mb();
> > > +
> >
> > I don't like this. Stand-alone barriers are, I think, harder to read
> > than barriers combined with operations, like smp_load_acquire() or
> > similar. And this one seems quite detached from anything.
> >
> > If I understand correctly, the possible race is between
> > svc_thread_wait_for_work which includes:
> >
> > llist_add(&rqstp->rq_idle, &pool->sp_idle_threads);
> > if (likely(svc_thread_should_sleep(rqstp)))
> >
> > and svc_stop_kthread which includes:
> >
> > set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> > svc_pool_wake_idle_thread(pool);
> >
> > The first sets ->sp_idle_threads.first and then checks SP_NEED_VICTIM
> > and the second sets SP_NEED_VICTIM and then checks
> > ->sp_idle_threads.first.
> >
> > So we need barriers to ensure the values are seen is the expected order.
> > Have I missed anything?
> >
> > llist_add() always calls try_cmpxchg() which is fully ordered, so no
> > extra barriers are needed there.
> >
> > There is no barrier between the set_bit)( and the READ_ONCE() in
> > svc_pool_wake_idle_thread(), so you added one. I agree that is correct
> > and solves the problem.
> > But it is only this call to svc_pool_wake_idle_thread() that needs a
> > barrier, not all calls. So I would prefer the barrier went in
> > svc_stop_kthreads(). Do you have a reason for not including it there?
> >
> > We do need a full memory barrier - a "release" on the set_bit and an
> > "acquire" on the test of ->sp_idle_threads.first
> >
> > I would be happy with smp_load_acquire(->sp_idle_threads_first) in
> > svc_pool_wake_idle_thread(). I'm not sure it is needed though.
> >
> > I would like set_bit_release(SP_NEED_VICTIM) in svc_stop_kthreads()
> > but we don't have that.
>
> It is on the way [11].
>
> [11] Subject: [PATCH 1/2] asm-generic/bitops: Add set_bit_release()
> https://lore.kernel.org/lkml/20261021095512.1345191-3-phasta@kernel.org/
Thanks for that pointer. It helped me realise that I was wrong about
the sort of barriers needed. A set_bit_release() would not help.
And an acquire on the subsequent read of sp_idle_threads also wouldn't
help.
A release is between two writes:
T1:
foo = 1
smp_store_release(&bar, 2)
puts a write barrier between the two.
An acquire is between two reads.
T2:
y = smp_load_acquire(&bar);
x = foo
These ensure that if T2 sees '2' in bar, it will definitely see '1' in foo.
But we don't have two reads or two writes, we have a write-then-read,
and that requires a full memory barrier.
So we would want set_bit_mb() (corresponding to smp_store_mb()).
As we don't have that,
set_bit()
smp_mb__after_atomic()
is the right way to fix this - acquire/release barriers would not be
effective.
As I noted, this is needed in both svc_wake_up() and
svc_stop_kthreads().
Thanks,
NeilBrown
> > We could do
> > atomic_long_fetch_or_release(BIT(SP_NEED_VICTIM), (atomic_long_t*)&pool->sp_flags)
> > but that is ugly.
> >
> > So I think the best solution is
> >
> > set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> > /* Order SP_NEED_VICTIM before testing sp_idle_threads,
> > * matching svc_thread_wait_for_work() which orders
> > * setting sp_idle_threads before testing the bit
> > */
> > + smp_mb__after_atomic();
> > svc_pool_wake_idle_thread(pool);
> >
> > We probably need the same in svc_wake_up() after the set_bit().
> >
> > Does this make the barriers easier to understand?
> >
> > Thoughts?
> >
> > Thanks,
> > NeilBrown
> >
> >
> > > rcu_read_lock();
> > > ln = READ_ONCE(pool->sp_idle_threads.first);
> > > if (ln) {
> > >
> > > base-commit: b803428098f6fcd584db9f0499217b80e465bb34
> > > --
> > > 2.53.0
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] sunrpc: fix lost wakeups when stopping service threads
2026-10-31 21:49 ` NeilBrown
@ 2026-10-31 22:57 ` Hillf Danton
0 siblings, 0 replies; 7+ messages in thread
From: Hillf Danton @ 2026-10-31 22:57 UTC (permalink / raw)
To: NeilBrown; +Cc: Kal Cutter Conley, Chuck Lever, linux-nfs, linux-kernel
On Sun, 01 Nov 2026 08:49:42 +1100 NeilBrown wrote:
> On Sat, 31 Oct 2026, Hillf Danton wrote:
> > On Sat, 31 Oct 2026 09:45:39 +1100 NeilBrown wrote:
> > > On Sat, 31 Oct 2026, Kal Cutter Conley wrote:
> > > > Stopping NFS can leave rpc.nfsd 0 in an uninterruptible wait with
> > > > this stack:
> > > >
> > > > svc_stop_kthreads [sunrpc]
> > > > svc_set_num_threads [sunrpc]
> > > > nfsd_svc [nfsd]
> > > > write_threads [nfsd]
> > > > nfsctl_transaction_write [nfsd]
> > > >
> > > > SP_NEED_VICTIM and SP_VICTIM_REMAINS remain set while all remaining
> > > > nfsd threads sleep in svc_recv() and none has RQ_VICTIM set.
> > > >
> > > > The stop request is published with unordered set_bit() operations.
> > > > svc_pool_wake_idle_thread() checks the idle list and task state before
> > > > calling wake_up_process(). If it sees an empty list or TASK_RUNNING,
> > > > it skips the wakeup and its memory barrier. The requester can therefore
> > > > miss a sleeping worker while that worker misses the stop request.
> > > >
> > > > Order work publication before both lockless checks, even when no
> > > > wakeup occurs. Pair with set_current_state() and llist_add() in
> > > > svc_thread_wait_for_work(), so the worker observes the pending work or
> > > > the requester observes the idle worker and wakes it.
> > > >
> > > > Fixes: 60749cbe3d8a ("sunrpc: change sp_nrthreads from atomic_t to unsigned int.")
> > > > Signed-off-by: Kal Cutter Conley <kalcutterc@nvidia.com>
> > > > ---
> > > > net/sunrpc/svc.c | 9 +++++++++
> > > > 1 file changed, 9 insertions(+)
> > > >
> > > > diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c
> > > > index af8d87c1de83..f3cc63acc5e1 100644
> > > > --- a/net/sunrpc/svc.c
> > > > +++ b/net/sunrpc/svc.c
> > > > @@ -29,6 +29,8 @@
> > > > #include <linux/sunrpc/clnt.h>
> > > > #include <linux/sunrpc/bc_xprt.h>
> > > >
> > > > +#include <asm/barrier.h>
> > > > +
> > > > #include <trace/events/sunrpc.h>
> > > >
> > > > #include "fail.h"
> > > > @@ -693,6 +695,13 @@ void svc_pool_wake_idle_thread(struct svc_pool *pool)
> > > > struct svc_rqst *rqstp;
> > > > struct llist_node *ln;
> > > >
> > > > + /*
> > > > + * Order work publication before the idle-list and task-state checks,
> > > > + * even when wake_up_process() is skipped. Pairs with the barriers in
> > > > + * set_current_state() and llist_add() in svc_thread_wait_for_work().
> > > > + */
> > > > + smp_mb();
> > > > +
> > >
> > > I don't like this. Stand-alone barriers are, I think, harder to read
> > > than barriers combined with operations, like smp_load_acquire() or
> > > similar. And this one seems quite detached from anything.
> > >
> > > If I understand correctly, the possible race is between
> > > svc_thread_wait_for_work which includes:
> > >
> > > llist_add(&rqstp->rq_idle, &pool->sp_idle_threads);
> > > if (likely(svc_thread_should_sleep(rqstp)))
> > >
> > > and svc_stop_kthread which includes:
> > >
> > > set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> > > svc_pool_wake_idle_thread(pool);
> > >
> > > The first sets ->sp_idle_threads.first and then checks SP_NEED_VICTIM
> > > and the second sets SP_NEED_VICTIM and then checks
> > > ->sp_idle_threads.first.
> > >
> > > So we need barriers to ensure the values are seen is the expected order.
> > > Have I missed anything?
> > >
> > > llist_add() always calls try_cmpxchg() which is fully ordered, so no
> > > extra barriers are needed there.
> > >
> > > There is no barrier between the set_bit)( and the READ_ONCE() in
> > > svc_pool_wake_idle_thread(), so you added one. I agree that is correct
> > > and solves the problem.
> > > But it is only this call to svc_pool_wake_idle_thread() that needs a
> > > barrier, not all calls. So I would prefer the barrier went in
> > > svc_stop_kthreads(). Do you have a reason for not including it there?
> > >
> > > We do need a full memory barrier - a "release" on the set_bit and an
> > > "acquire" on the test of ->sp_idle_threads.first
> > >
> > > I would be happy with smp_load_acquire(->sp_idle_threads_first) in
> > > svc_pool_wake_idle_thread(). I'm not sure it is needed though.
> > >
> > > I would like set_bit_release(SP_NEED_VICTIM) in svc_stop_kthreads()
> > > but we don't have that.
> >
> > It is on the way [11].
> >
> > [11] Subject: [PATCH 1/2] asm-generic/bitops: Add set_bit_release()
> > https://lore.kernel.org/lkml/20261021095512.1345191-3-phasta@kernel.org/
>
> Thanks for that pointer. It helped me realise that I was wrong about
> the sort of barriers needed. A set_bit_release() would not help.
> And an acquire on the subsequent read of sp_idle_threads also wouldn't
> help.
>
> A release is between two writes:
> T1:
> foo = 1
> smp_store_release(&bar, 2)
> puts a write barrier between the two.
>
> An acquire is between two reads.
> T2:
> y = smp_load_acquire(&bar);
> x = foo
>
> These ensure that if T2 sees '2' in bar, it will definitely see '1' in foo.
>
> But we don't have two reads or two writes, we have a write-then-read,
> and that requires a full memory barrier.
>
> So we would want set_bit_mb() (corresponding to smp_store_mb()).
> As we don't have that,
> set_bit()
> smp_mb__after_atomic()
>
> is the right way to fix this - acquire/release barriers would not be
> effective.
>
Thanks, a good lesson to me.
Hillf
> As I noted, this is needed in both svc_wake_up() and
> svc_stop_kthreads().
>
> Thanks,
> NeilBrown
>
>
> > > We could do
> > > atomic_long_fetch_or_release(BIT(SP_NEED_VICTIM), (atomic_long_t*)&pool->sp_flags)
> > > but that is ugly.
> > >
> > > So I think the best solution is
> > >
> > > set_bit(SP_NEED_VICTIM, &pool->sp_flags);
> > > /* Order SP_NEED_VICTIM before testing sp_idle_threads,
> > > * matching svc_thread_wait_for_work() which orders
> > > * setting sp_idle_threads before testing the bit
> > > */
> > > + smp_mb__after_atomic();
> > > svc_pool_wake_idle_thread(pool);
> > >
> > > We probably need the same in svc_wake_up() after the set_bit().
> > >
> > > Does this make the barriers easier to understand?
> > >
> > > Thoughts?
> > >
> > > Thanks,
> > > NeilBrown
> > >
> > >
> > > > rcu_read_lock();
> > > > ln = READ_ONCE(pool->sp_idle_threads.first);
> > > > if (ln) {
> > > >
> > > > base-commit: b803428098f6fcd584db9f0499217b80e465bb34
> > > > --
> > > > 2.53.0
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-31 22:57 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 13:06 [PATCH] sunrpc: fix lost wakeups when stopping service threads Kal Cutter Conley
2026-10-30 21:43 ` Chuck Lever
2026-10-30 22:45 ` NeilBrown
2026-10-31 0:46 ` Hillf Danton
2026-10-31 21:49 ` NeilBrown
2026-10-31 22:57 ` Hillf Danton
2026-10-31 14:01 ` Chuck Lever
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®