netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH net v2] net: fix race between napi kthread mode and busy poll
@ 2021-02-27  0:30 Wei Wang
  2021-02-27  0:48 ` Jakub Kicinski
  0 siblings, 1 reply; 10+ messages in thread
From: Wei Wang @ 2021-02-27  0:30 UTC (permalink / raw)
  To: David S . Miller, netdev, Jakub Kicinski
  Cc: Martin Zaharinov, Alexander Duyck, Eric Dumazet, Paolo Abeni,
	Hannes Frederic Sowa

Currently, napi_thread_wait() checks for NAPI_STATE_SCHED bit to
determine if the kthread owns this napi and could call napi->poll() on
it. However, if socket busy poll is enabled, it is possible that the
busy poll thread grabs this SCHED bit (after the previous napi->poll()
invokes napi_complete_done() and clears SCHED bit) and tries to poll
on the same napi. napi_disable() could grab the SCHED bit as well.
This patch tries to fix this race by adding a new bit
NAPI_STATE_SCHED_THREADED in napi->state. This bit gets set in
____napi_schedule() if the threaded mode is enabled, and gets cleared
in napi_complete_done(), and we only poll the napi in kthread if this
bit is set. This helps distinguish the ownership of the napi between
kthread and other scenarios and fixes the race issue.

Fixes: 29863d41bb6e ("net: implement threaded-able napi poll loop support")
Reported-by: Martin Zaharinov <micron10@gmail.com>
Signed-off-by: Wei Wang <weiwan@google.com>
Cc: Alexander Duyck <alexanderduyck@fb.com>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Hannes Frederic Sowa <hannes@stressinduktion.org>
---
 include/linux/netdevice.h |  2 ++
 net/core/dev.c            | 20 +++++++++++++-------
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index ddf4cfc12615..682908707c1a 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -360,6 +360,7 @@ enum {
 	NAPI_STATE_IN_BUSY_POLL,	/* sk_busy_loop() owns this NAPI */
 	NAPI_STATE_PREFER_BUSY_POLL,	/* prefer busy-polling over softirq processing*/
 	NAPI_STATE_THREADED,		/* The poll is performed inside its own thread*/
+	NAPI_STATE_SCHED_THREADED,	/* Napi is currently scheduled in threaded mode */
 };
 
 enum {
@@ -372,6 +373,7 @@ enum {
 	NAPIF_STATE_IN_BUSY_POLL	= BIT(NAPI_STATE_IN_BUSY_POLL),
 	NAPIF_STATE_PREFER_BUSY_POLL	= BIT(NAPI_STATE_PREFER_BUSY_POLL),
 	NAPIF_STATE_THREADED		= BIT(NAPI_STATE_THREADED),
+	NAPIF_STATE_SCHED_THREADED	= BIT(NAPI_STATE_SCHED_THREADED),
 };
 
 enum gro_result {
diff --git a/net/core/dev.c b/net/core/dev.c
index 6c5967e80132..d4ce154c8df5 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1501,15 +1501,14 @@ static int napi_kthread_create(struct napi_struct *n)
 {
 	int err = 0;
 
-	/* Create and wake up the kthread once to put it in
-	 * TASK_INTERRUPTIBLE mode to avoid the blocked task
-	 * warning and work with loadavg.
+	/* Avoid waking up the kthread during creation to prevent
+	 * potential race.
 	 */
-	n->thread = kthread_run(napi_threaded_poll, n, "napi/%s-%d",
-				n->dev->name, n->napi_id);
+	n->thread = kthread_create(napi_threaded_poll, n, "napi/%s-%d",
+				   n->dev->name, n->napi_id);
 	if (IS_ERR(n->thread)) {
 		err = PTR_ERR(n->thread);
-		pr_err("kthread_run failed with err %d\n", err);
+		pr_err("kthread_create failed with err %d\n", err);
 		n->thread = NULL;
 	}
 
@@ -4294,6 +4293,7 @@ static inline void ____napi_schedule(struct softnet_data *sd,
 		 */
 		thread = READ_ONCE(napi->thread);
 		if (thread) {
+			set_bit(NAPI_STATE_SCHED_THREADED, &napi->state);
 			wake_up_process(thread);
 			return;
 		}
@@ -6486,6 +6486,7 @@ bool napi_complete_done(struct napi_struct *n, int work_done)
 		WARN_ON_ONCE(!(val & NAPIF_STATE_SCHED));
 
 		new = val & ~(NAPIF_STATE_MISSED | NAPIF_STATE_SCHED |
+			      NAPIF_STATE_SCHED_THREADED |
 			      NAPIF_STATE_PREFER_BUSY_POLL);
 
 		/* If STATE_MISSED was set, leave STATE_SCHED set,
@@ -6971,7 +6972,12 @@ static int napi_thread_wait(struct napi_struct *napi)
 	set_current_state(TASK_INTERRUPTIBLE);
 
 	while (!kthread_should_stop() && !napi_disable_pending(napi)) {
-		if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
+		/* Testing SCHED_THREADED bit here to make sure the current
+		 * kthread owns this napi and could poll on this napi.
+		 * Testing SCHED bit is not enough because SCHED bit might be
+		 * set by some other busy poll thread or by napi_disable().
+		 */
+		if (test_bit(NAPI_STATE_SCHED_THREADED, &napi->state)) {
 			WARN_ON(!list_empty(&napi->poll_list));
 			__set_current_state(TASK_RUNNING);
 			return 0;
-- 
2.30.1.766.gb4fecdf3b7-goog


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27  0:30 [PATCH net v2] net: fix race between napi kthread mode and busy poll Wei Wang
@ 2021-02-27  0:48 ` Jakub Kicinski
  2021-02-27  1:02   ` Wei Wang
  0 siblings, 1 reply; 10+ messages in thread
From: Jakub Kicinski @ 2021-02-27  0:48 UTC (permalink / raw)
  To: Wei Wang
  Cc: David S . Miller, netdev, Martin Zaharinov, Alexander Duyck,
	Eric Dumazet, Paolo Abeni, Hannes Frederic Sowa

On Fri, 26 Feb 2021 16:30:47 -0800 Wei Wang wrote:
>  		thread = READ_ONCE(napi->thread);
>  		if (thread) {
> +			set_bit(NAPI_STATE_SCHED_THREADED, &napi->state);
>  			wake_up_process(thread);

What about the version which checks RUNNING? As long as
wake_up_process() implies a barrier I _think_ it should 
work as well. Am I missing some case, or did you decide
to go with the simpler/safer approach?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27  0:48 ` Jakub Kicinski
@ 2021-02-27  1:02   ` Wei Wang
  2021-02-27  1:22     ` Jakub Kicinski
  0 siblings, 1 reply; 10+ messages in thread
From: Wei Wang @ 2021-02-27  1:02 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: David S . Miller, Linux Kernel Network Developers,
	Martin Zaharinov, Alexander Duyck, Eric Dumazet, Paolo Abeni,
	Hannes Frederic Sowa

On Fri, Feb 26, 2021 at 4:48 PM Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Fri, 26 Feb 2021 16:30:47 -0800 Wei Wang wrote:
> >               thread = READ_ONCE(napi->thread);
> >               if (thread) {
> > +                     set_bit(NAPI_STATE_SCHED_THREADED, &napi->state);
> >                       wake_up_process(thread);
>
> What about the version which checks RUNNING? As long as
> wake_up_process() implies a barrier I _think_ it should
> work as well. Am I missing some case, or did you decide
> to go with the simpler/safer approach?


I assume you are referring to the following proposed patch in your
previous email right?
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -4294,6 +4294,8 @@ static inline void ____napi_schedule(struct
softnet_data *sd,
                 */
                thread = READ_ONCE(napi->thread);
                if (thread) {
+                       if (thread->state == TASK_RUNNING)
+                               set_bit(NAPIF_STATE_SCHED_THREAD, &napi->state);
                        wake_up_process(thread);
                        return;
                }
@@ -6486,7 +6488,8 @@ bool napi_complete_done(struct napi_struct *n,
int work_done)
                WARN_ON_ONCE(!(val & NAPIF_STATE_SCHED));

                new = val & ~(NAPIF_STATE_MISSED | NAPIF_STATE_SCHED |
-                             NAPIF_STATE_PREFER_BUSY_POLL);
+                             NAPIF_STATE_PREFER_BUSY_POLL |
+                             NAPIF_STATE_SCHED_THREAD);

                /* If STATE_MISSED was set, leave STATE_SCHED set,
                 * because we will call napi->poll() one more time.
@@ -6968,16 +6971,24 @@ static int napi_poll(struct napi_struct *n,
struct list_head *repoll)

 static int napi_thread_wait(struct napi_struct *napi)
 {
+       bool woken = false;
+
        set_current_state(TASK_INTERRUPTIBLE);

        while (!kthread_should_stop() && !napi_disable_pending(napi)) {
-               if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
+               unsigned long state = READ_ONCE(napi->state);
+
+               if ((state & NAPIF_STATE_SCHED) &&
+                   ((state & NAPIF_STATE_SCHED_THREAD) || woken)) {
                        WARN_ON(!list_empty(&napi->poll_list));
                        __set_current_state(TASK_RUNNING);
                        return 0;
+               } else {
+                       WARN_ON(woken);
                }

                schedule();
+               woken = true;
                set_current_state(TASK_INTERRUPTIBLE);
        }
        __set_current_state(TASK_RUNNING);

I don't think it is sufficient to only set SCHED_THREADED bit when the
thread is in RUNNING state.
In fact, the thread is most likely NOT in RUNNING mode before we call
wake_up_process() in ____napi_schedule(), because it has finished the
previous round of napi->poll() and SCHED bit was cleared, so
napi_thread_wait() sets the state to INTERRUPTIBLE and schedule() call
should already put it in sleep.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27  1:02   ` Wei Wang
@ 2021-02-27  1:22     ` Jakub Kicinski
  2021-02-27  1:35       ` Wei Wang
  0 siblings, 1 reply; 10+ messages in thread
From: Jakub Kicinski @ 2021-02-27  1:22 UTC (permalink / raw)
  To: Wei Wang
  Cc: David S . Miller, Linux Kernel Network Developers,
	Martin Zaharinov, Alexander Duyck, Eric Dumazet, Paolo Abeni,
	Hannes Frederic Sowa

On Fri, 26 Feb 2021 17:02:17 -0800 Wei Wang wrote:
>  static int napi_thread_wait(struct napi_struct *napi)
>  {
> +       bool woken = false;
> +
>         set_current_state(TASK_INTERRUPTIBLE);
> 
>         while (!kthread_should_stop() && !napi_disable_pending(napi)) {
> -               if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
> +               unsigned long state = READ_ONCE(napi->state);
> +
> +               if ((state & NAPIF_STATE_SCHED) &&
> +                   ((state & NAPIF_STATE_SCHED_THREAD) || woken)) {
>                         WARN_ON(!list_empty(&napi->poll_list));
>                         __set_current_state(TASK_RUNNING);
>                         return 0;
> +               } else {
> +                       WARN_ON(woken);
>                 }
> 
>                 schedule();
> +               woken = true;
>                 set_current_state(TASK_INTERRUPTIBLE);
>         }
>         __set_current_state(TASK_RUNNING);
> 
> I don't think it is sufficient to only set SCHED_THREADED bit when the
> thread is in RUNNING state.
> In fact, the thread is most likely NOT in RUNNING mode before we call
> wake_up_process() in ____napi_schedule(), because it has finished the
> previous round of napi->poll() and SCHED bit was cleared, so
> napi_thread_wait() sets the state to INTERRUPTIBLE and schedule() call
> should already put it in sleep.

That's why the check says "|| woken":

	((state & NAPIF_STATE_SCHED_THREAD) ||	woken))

thread knows it owns the NAPI if:

  (a) the NAPI has the explicit flag set
or
  (b) it was just worken up and !kthread_should_stop(), since only
      someone who just claimed the normal SCHED on thread's behalf 
      will wake it up

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27  1:22     ` Jakub Kicinski
@ 2021-02-27  1:35       ` Wei Wang
  2021-02-27  2:08         ` Jakub Kicinski
  0 siblings, 1 reply; 10+ messages in thread
From: Wei Wang @ 2021-02-27  1:35 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: David S . Miller, Linux Kernel Network Developers,
	Martin Zaharinov, Alexander Duyck, Eric Dumazet, Paolo Abeni,
	Hannes Frederic Sowa

On Fri, Feb 26, 2021 at 5:22 PM Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Fri, 26 Feb 2021 17:02:17 -0800 Wei Wang wrote:
> >  static int napi_thread_wait(struct napi_struct *napi)
> >  {
> > +       bool woken = false;
> > +
> >         set_current_state(TASK_INTERRUPTIBLE);
> >
> >         while (!kthread_should_stop() && !napi_disable_pending(napi)) {
> > -               if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
> > +               unsigned long state = READ_ONCE(napi->state);
> > +
> > +               if ((state & NAPIF_STATE_SCHED) &&
> > +                   ((state & NAPIF_STATE_SCHED_THREAD) || woken)) {
> >                         WARN_ON(!list_empty(&napi->poll_list));
> >                         __set_current_state(TASK_RUNNING);
> >                         return 0;
> > +               } else {
> > +                       WARN_ON(woken);
> >                 }
> >
> >                 schedule();
> > +               woken = true;
> >                 set_current_state(TASK_INTERRUPTIBLE);
> >         }
> >         __set_current_state(TASK_RUNNING);
> >
> > I don't think it is sufficient to only set SCHED_THREADED bit when the
> > thread is in RUNNING state.
> > In fact, the thread is most likely NOT in RUNNING mode before we call
> > wake_up_process() in ____napi_schedule(), because it has finished the
> > previous round of napi->poll() and SCHED bit was cleared, so
> > napi_thread_wait() sets the state to INTERRUPTIBLE and schedule() call
> > should already put it in sleep.
>
> That's why the check says "|| woken":
>
>         ((state & NAPIF_STATE_SCHED_THREAD) ||  woken))
>
> thread knows it owns the NAPI if:
>
>   (a) the NAPI has the explicit flag set
> or
>   (b) it was just worken up and !kthread_should_stop(), since only
>       someone who just claimed the normal SCHED on thread's behalf
>       will wake it up

The 'woken' is set after schedule(). If it is the first time
napi_threaded_wait() is called, and SCHED_THREADED is not set, and
woken is not set either, this thread will be put to sleep when it
reaches schedule(), even though there is work waiting to be done on
that napi. And I think this kthread will not be woken up again
afterwards, since the SCHED bit is already grabbed.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27  1:35       ` Wei Wang
@ 2021-02-27  2:08         ` Jakub Kicinski
  2021-02-27 19:00           ` Wei Wang
  0 siblings, 1 reply; 10+ messages in thread
From: Jakub Kicinski @ 2021-02-27  2:08 UTC (permalink / raw)
  To: Wei Wang
  Cc: David S . Miller, Linux Kernel Network Developers,
	Martin Zaharinov, Alexander Duyck, Eric Dumazet, Paolo Abeni,
	Hannes Frederic Sowa

On Fri, 26 Feb 2021 17:35:21 -0800 Wei Wang wrote:
> On Fri, Feb 26, 2021 at 5:22 PM Jakub Kicinski <kuba@kernel.org> wrote:
> >
> > On Fri, 26 Feb 2021 17:02:17 -0800 Wei Wang wrote:  
> > >  static int napi_thread_wait(struct napi_struct *napi)
> > >  {
> > > +       bool woken = false;
> > > +
> > >         set_current_state(TASK_INTERRUPTIBLE);
> > >
> > >         while (!kthread_should_stop() && !napi_disable_pending(napi)) {
> > > -               if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
> > > +               unsigned long state = READ_ONCE(napi->state);
> > > +
> > > +               if ((state & NAPIF_STATE_SCHED) &&
> > > +                   ((state & NAPIF_STATE_SCHED_THREAD) || woken)) {
> > >                         WARN_ON(!list_empty(&napi->poll_list));
> > >                         __set_current_state(TASK_RUNNING);
> > >                         return 0;
> > > +               } else {
> > > +                       WARN_ON(woken);
> > >                 }
> > >
> > >                 schedule();
> > > +               woken = true;
> > >                 set_current_state(TASK_INTERRUPTIBLE);
> > >         }
> > >         __set_current_state(TASK_RUNNING);
> > >
> > > I don't think it is sufficient to only set SCHED_THREADED bit when the
> > > thread is in RUNNING state.
> > > In fact, the thread is most likely NOT in RUNNING mode before we call
> > > wake_up_process() in ____napi_schedule(), because it has finished the
> > > previous round of napi->poll() and SCHED bit was cleared, so
> > > napi_thread_wait() sets the state to INTERRUPTIBLE and schedule() call
> > > should already put it in sleep.  
> >
> > That's why the check says "|| woken":
> >
> >         ((state & NAPIF_STATE_SCHED_THREAD) ||  woken))
> >
> > thread knows it owns the NAPI if:
> >
> >   (a) the NAPI has the explicit flag set
> > or
> >   (b) it was just worken up and !kthread_should_stop(), since only
> >       someone who just claimed the normal SCHED on thread's behalf
> >       will wake it up  
> 
> The 'woken' is set after schedule(). If it is the first time
> napi_threaded_wait() is called, and SCHED_THREADED is not set, and
> woken is not set either, this thread will be put to sleep when it
> reaches schedule(), even though there is work waiting to be done on
> that napi. And I think this kthread will not be woken up again
> afterwards, since the SCHED bit is already grabbed.

Indeed, looks like the task will be in WAKING state until it runs?
We can switch the check in ____napi_schedule() from

	if (thread->state == TASK_RUNNING)

to

	if (!(thread->state & TASK_INTERRUPTIBLE))

?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27  2:08         ` Jakub Kicinski
@ 2021-02-27 19:00           ` Wei Wang
  2021-02-27 23:23             ` Wei Wang
  0 siblings, 1 reply; 10+ messages in thread
From: Wei Wang @ 2021-02-27 19:00 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: David S . Miller, Linux Kernel Network Developers,
	Martin Zaharinov, Alexander Duyck, Eric Dumazet, Paolo Abeni,
	Hannes Frederic Sowa

On Fri, Feb 26, 2021 at 6:08 PM Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Fri, 26 Feb 2021 17:35:21 -0800 Wei Wang wrote:
> > On Fri, Feb 26, 2021 at 5:22 PM Jakub Kicinski <kuba@kernel.org> wrote:
> > >
> > > On Fri, 26 Feb 2021 17:02:17 -0800 Wei Wang wrote:
> > > >  static int napi_thread_wait(struct napi_struct *napi)
> > > >  {
> > > > +       bool woken = false;
> > > > +
> > > >         set_current_state(TASK_INTERRUPTIBLE);
> > > >
> > > >         while (!kthread_should_stop() && !napi_disable_pending(napi)) {
> > > > -               if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
> > > > +               unsigned long state = READ_ONCE(napi->state);
> > > > +
> > > > +               if ((state & NAPIF_STATE_SCHED) &&
> > > > +                   ((state & NAPIF_STATE_SCHED_THREAD) || woken)) {
> > > >                         WARN_ON(!list_empty(&napi->poll_list));
> > > >                         __set_current_state(TASK_RUNNING);
> > > >                         return 0;
> > > > +               } else {
> > > > +                       WARN_ON(woken);
> > > >                 }
> > > >
> > > >                 schedule();
> > > > +               woken = true;
> > > >                 set_current_state(TASK_INTERRUPTIBLE);
> > > >         }
> > > >         __set_current_state(TASK_RUNNING);
> > > >
> > > > I don't think it is sufficient to only set SCHED_THREADED bit when the
> > > > thread is in RUNNING state.
> > > > In fact, the thread is most likely NOT in RUNNING mode before we call
> > > > wake_up_process() in ____napi_schedule(), because it has finished the
> > > > previous round of napi->poll() and SCHED bit was cleared, so
> > > > napi_thread_wait() sets the state to INTERRUPTIBLE and schedule() call
> > > > should already put it in sleep.
> > >
> > > That's why the check says "|| woken":
> > >
> > >         ((state & NAPIF_STATE_SCHED_THREAD) ||  woken))
> > >
> > > thread knows it owns the NAPI if:
> > >
> > >   (a) the NAPI has the explicit flag set
> > > or
> > >   (b) it was just worken up and !kthread_should_stop(), since only
> > >       someone who just claimed the normal SCHED on thread's behalf
> > >       will wake it up
> >
> > The 'woken' is set after schedule(). If it is the first time
> > napi_threaded_wait() is called, and SCHED_THREADED is not set, and
> > woken is not set either, this thread will be put to sleep when it
> > reaches schedule(), even though there is work waiting to be done on
> > that napi. And I think this kthread will not be woken up again
> > afterwards, since the SCHED bit is already grabbed.
>
> Indeed, looks like the task will be in WAKING state until it runs?
> We can switch the check in ____napi_schedule() from
>
>         if (thread->state == TASK_RUNNING)
>
> to
>
>         if (!(thread->state & TASK_INTERRUPTIBLE))
>
> ?

Hmm... I am not very sure what state the thread will be put in after
kthread_create(). Could it be in TASK_INTERRUPTIBLE?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27 19:00           ` Wei Wang
@ 2021-02-27 23:23             ` Wei Wang
  2021-02-28 19:17               ` Jakub Kicinski
  0 siblings, 1 reply; 10+ messages in thread
From: Wei Wang @ 2021-02-27 23:23 UTC (permalink / raw)
  To: Jakub Kicinski, Alexander Duyck, Eric Dumazet
  Cc: David S . Miller, Linux Kernel Network Developers,
	Martin Zaharinov, Paolo Abeni, Hannes Frederic Sowa

On Sat, Feb 27, 2021 at 11:00 AM Wei Wang <weiwan@google.com> wrote:
>
> On Fri, Feb 26, 2021 at 6:08 PM Jakub Kicinski <kuba@kernel.org> wrote:
> >
> > On Fri, 26 Feb 2021 17:35:21 -0800 Wei Wang wrote:
> > > On Fri, Feb 26, 2021 at 5:22 PM Jakub Kicinski <kuba@kernel.org> wrote:
> > > >
> > > > On Fri, 26 Feb 2021 17:02:17 -0800 Wei Wang wrote:
> > > > >  static int napi_thread_wait(struct napi_struct *napi)
> > > > >  {
> > > > > +       bool woken = false;
> > > > > +
> > > > >         set_current_state(TASK_INTERRUPTIBLE);
> > > > >
> > > > >         while (!kthread_should_stop() && !napi_disable_pending(napi)) {
> > > > > -               if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
> > > > > +               unsigned long state = READ_ONCE(napi->state);
> > > > > +
> > > > > +               if ((state & NAPIF_STATE_SCHED) &&
> > > > > +                   ((state & NAPIF_STATE_SCHED_THREAD) || woken)) {
> > > > >                         WARN_ON(!list_empty(&napi->poll_list));
> > > > >                         __set_current_state(TASK_RUNNING);
> > > > >                         return 0;
> > > > > +               } else {
> > > > > +                       WARN_ON(woken);
> > > > >                 }
> > > > >
> > > > >                 schedule();
> > > > > +               woken = true;
> > > > >                 set_current_state(TASK_INTERRUPTIBLE);
> > > > >         }
> > > > >         __set_current_state(TASK_RUNNING);
> > > > >
> > > > > I don't think it is sufficient to only set SCHED_THREADED bit when the
> > > > > thread is in RUNNING state.
> > > > > In fact, the thread is most likely NOT in RUNNING mode before we call
> > > > > wake_up_process() in ____napi_schedule(), because it has finished the
> > > > > previous round of napi->poll() and SCHED bit was cleared, so
> > > > > napi_thread_wait() sets the state to INTERRUPTIBLE and schedule() call
> > > > > should already put it in sleep.
> > > >
> > > > That's why the check says "|| woken":
> > > >
> > > >         ((state & NAPIF_STATE_SCHED_THREAD) ||  woken))
> > > >
> > > > thread knows it owns the NAPI if:
> > > >
> > > >   (a) the NAPI has the explicit flag set
> > > > or
> > > >   (b) it was just worken up and !kthread_should_stop(), since only
> > > >       someone who just claimed the normal SCHED on thread's behalf
> > > >       will wake it up
> > >
> > > The 'woken' is set after schedule(). If it is the first time
> > > napi_threaded_wait() is called, and SCHED_THREADED is not set, and
> > > woken is not set either, this thread will be put to sleep when it
> > > reaches schedule(), even though there is work waiting to be done on
> > > that napi. And I think this kthread will not be woken up again
> > > afterwards, since the SCHED bit is already grabbed.
> >
> > Indeed, looks like the task will be in WAKING state until it runs?
> > We can switch the check in ____napi_schedule() from
> >
> >         if (thread->state == TASK_RUNNING)
> >
> > to
> >
> >         if (!(thread->state & TASK_INTERRUPTIBLE))
> >
> > ?
>
> Hmm... I am not very sure what state the thread will be put in after
> kthread_create(). Could it be in TASK_INTERRUPTIBLE?

I did a printk and confirmed that the thread->state is
TASK_UNINTERRUPTIBLE after kthread_create() is called.
So I think if we change the above state to:
          if (thread->state != TASK_INTERRUPTIBLE)
                  set_bit(NAPI_STATE_SCHED_THREADED, &napi->state);
It should work.

I tested the following patch on my setup and saw no issues:
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index ddf4cfc12615..682908707c1a 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -360,6 +360,7 @@ enum {
        NAPI_STATE_IN_BUSY_POLL,        /* sk_busy_loop() owns this NAPI */
        NAPI_STATE_PREFER_BUSY_POLL,    /* prefer busy-polling over
softirq processing*/
        NAPI_STATE_THREADED,            /* The poll is performed
inside its own thread*/
+       NAPI_STATE_SCHED_THREADED,      /* Napi is currently scheduled
in threaded mode */
 };

 enum {
@@ -372,6 +373,7 @@ enum {
        NAPIF_STATE_IN_BUSY_POLL        = BIT(NAPI_STATE_IN_BUSY_POLL),
        NAPIF_STATE_PREFER_BUSY_POLL    = BIT(NAPI_STATE_PREFER_BUSY_POLL),
        NAPIF_STATE_THREADED            = BIT(NAPI_STATE_THREADED),
+       NAPIF_STATE_SCHED_THREADED      = BIT(NAPI_STATE_SCHED_THREADED),
 };

 enum gro_result {
diff --git a/net/core/dev.c b/net/core/dev.c
index 6c5967e80132..43607523ee99 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1501,17 +1501,18 @@ static int napi_kthread_create(struct napi_struct *n)
 {
        int err = 0;

-       /* Create and wake up the kthread once to put it in
-        * TASK_INTERRUPTIBLE mode to avoid the blocked task
-        * warning and work with loadavg.
+       /* Avoid waking up the kthread during creation to prevent
+        * potential race.
         */
-       n->thread = kthread_run(napi_threaded_poll, n, "napi/%s-%d",
-                               n->dev->name, n->napi_id);
+       n->thread = kthread_create(napi_threaded_poll, n, "napi/%s-%d",
+                                  n->dev->name, n->napi_id);
        if (IS_ERR(n->thread)) {
                err = PTR_ERR(n->thread);
-               pr_err("kthread_run failed with err %d\n", err);
+               pr_err("kthread_create failed with err %d\n", err);
                n->thread = NULL;
        }
@@ -4294,6 +4295,8 @@ static inline void ____napi_schedule(struct
softnet_data *sd,
                 */
                thread = READ_ONCE(napi->thread);
                if (thread) {
+                       if (thread->state != TASK_INTERRUPTIBLE)
+                               set_bit(NAPI_STATE_SCHED_THREADED,
&napi->state);
                        wake_up_process(thread);
                        return;
                }
@@ -6486,6 +6489,7 @@ bool napi_complete_done(struct napi_struct *n,
int work_done)
                WARN_ON_ONCE(!(val & NAPIF_STATE_SCHED));

                new = val & ~(NAPIF_STATE_MISSED | NAPIF_STATE_SCHED |
+                             NAPIF_STATE_SCHED_THREADED |
                              NAPIF_STATE_PREFER_BUSY_POLL);

                /* If STATE_MISSED was set, leave STATE_SCHED set,
@@ -6968,16 +6972,24 @@ static int napi_poll(struct napi_struct *n,
struct list_head *repoll)

 static int napi_thread_wait(struct napi_struct *napi)
 {
+       bool woken = false;
+
        set_current_state(TASK_INTERRUPTIBLE);

        while (!kthread_should_stop() && !napi_disable_pending(napi)) {
-               if (test_bit(NAPI_STATE_SCHED, &napi->state)) {
+               /* Testing SCHED_THREADED bit here to make sure the current
+                * kthread owns this napi and could poll on this napi.
+                * Testing SCHED bit is not enough because SCHED bit might be
+                * set by some other busy poll thread or by napi_disable().
+                */
+               if (test_bit(NAPI_STATE_SCHED_THREADED, &napi->state)
|| woken) {
                        WARN_ON(!list_empty(&napi->poll_list));
                        __set_current_state(TASK_RUNNING);
                        return 0;
                }

                schedule();
+                /* woken being true indicates this thread owns this napi. */
+               woken = true;
                set_current_state(TASK_INTERRUPTIBLE);
        }
        __set_current_state(TASK_RUNNING);

Jakub, Eric and Alexander,
What do you think of the above patch?
To me, the logic here seems more complicated than the original v2
patch, but it helps save quite some set_bit() in ____napi_schedule().
So it may be worthwhile?

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-27 23:23             ` Wei Wang
@ 2021-02-28 19:17               ` Jakub Kicinski
  2021-03-01 18:16                 ` Wei Wang
  0 siblings, 1 reply; 10+ messages in thread
From: Jakub Kicinski @ 2021-02-28 19:17 UTC (permalink / raw)
  To: Wei Wang
  Cc: Alexander Duyck, Eric Dumazet, David S . Miller,
	Linux Kernel Network Developers, Martin Zaharinov, Paolo Abeni,
	Hannes Frederic Sowa

On Sat, 27 Feb 2021 15:23:56 -0800 Wei Wang wrote:
> > > Indeed, looks like the task will be in WAKING state until it runs?
> > > We can switch the check in ____napi_schedule() from
> > >
> > >         if (thread->state == TASK_RUNNING)
> > >
> > > to
> > >
> > >         if (!(thread->state & TASK_INTERRUPTIBLE))
> > >
> > > ?  
> >
> > Hmm... I am not very sure what state the thread will be put in after
> > kthread_create(). Could it be in TASK_INTERRUPTIBLE?  
> 
> I did a printk and confirmed that the thread->state is
> TASK_UNINTERRUPTIBLE after kthread_create() is called.
> So I think if we change the above state to:
>           if (thread->state != TASK_INTERRUPTIBLE)
>                   set_bit(NAPI_STATE_SCHED_THREADED, &napi->state);
> It should work.

> diff --git a/net/core/dev.c b/net/core/dev.c
> index 6c5967e80132..43607523ee99 100644
> --- a/net/core/dev.c
> +++ b/net/core/dev.c
> @@ -1501,17 +1501,18 @@ static int napi_kthread_create(struct napi_struct *n)
>  {
>         int err = 0;
> 
> -       /* Create and wake up the kthread once to put it in
> -        * TASK_INTERRUPTIBLE mode to avoid the blocked task
> -        * warning and work with loadavg.
> +       /* Avoid waking up the kthread during creation to prevent
> +        * potential race.
>          */
> -       n->thread = kthread_run(napi_threaded_poll, n, "napi/%s-%d",
> -                               n->dev->name, n->napi_id);
> +       n->thread = kthread_create(napi_threaded_poll, n, "napi/%s-%d",
> +                                  n->dev->name, n->napi_id);

Does kthread_run() make the thread go into TASK_INTERRUPTIBLE ?
It just calls wake_up_process(), which according to a comment in the
kdoc..

 * Conceptually does:
 *
 *   If (@state & @p->state) @p->state = TASK_RUNNING.

So I think we could safely stick to kthread_run() if the condition in
at the NAPI wake point checks for INTERRUPTIBLE?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH net v2] net: fix race between napi kthread mode and busy poll
  2021-02-28 19:17               ` Jakub Kicinski
@ 2021-03-01 18:16                 ` Wei Wang
  0 siblings, 0 replies; 10+ messages in thread
From: Wei Wang @ 2021-03-01 18:16 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: Alexander Duyck, Eric Dumazet, David S . Miller,
	Linux Kernel Network Developers, Martin Zaharinov, Paolo Abeni,
	Hannes Frederic Sowa

On Sun, Feb 28, 2021 at 11:17 AM Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Sat, 27 Feb 2021 15:23:56 -0800 Wei Wang wrote:
> > > > Indeed, looks like the task will be in WAKING state until it runs?
> > > > We can switch the check in ____napi_schedule() from
> > > >
> > > >         if (thread->state == TASK_RUNNING)
> > > >
> > > > to
> > > >
> > > >         if (!(thread->state & TASK_INTERRUPTIBLE))
> > > >
> > > > ?
> > >
> > > Hmm... I am not very sure what state the thread will be put in after
> > > kthread_create(). Could it be in TASK_INTERRUPTIBLE?
> >
> > I did a printk and confirmed that the thread->state is
> > TASK_UNINTERRUPTIBLE after kthread_create() is called.
> > So I think if we change the above state to:
> >           if (thread->state != TASK_INTERRUPTIBLE)
> >                   set_bit(NAPI_STATE_SCHED_THREADED, &napi->state);
> > It should work.
>
> > diff --git a/net/core/dev.c b/net/core/dev.c
> > index 6c5967e80132..43607523ee99 100644
> > --- a/net/core/dev.c
> > +++ b/net/core/dev.c
> > @@ -1501,17 +1501,18 @@ static int napi_kthread_create(struct napi_struct *n)
> >  {
> >         int err = 0;
> >
> > -       /* Create and wake up the kthread once to put it in
> > -        * TASK_INTERRUPTIBLE mode to avoid the blocked task
> > -        * warning and work with loadavg.
> > +       /* Avoid waking up the kthread during creation to prevent
> > +        * potential race.
> >          */
> > -       n->thread = kthread_run(napi_threaded_poll, n, "napi/%s-%d",
> > -                               n->dev->name, n->napi_id);
> > +       n->thread = kthread_create(napi_threaded_poll, n, "napi/%s-%d",
> > +                                  n->dev->name, n->napi_id);
>
> Does kthread_run() make the thread go into TASK_INTERRUPTIBLE ?
> It just calls wake_up_process(), which according to a comment in the
> kdoc..
>
>  * Conceptually does:
>  *
>  *   If (@state & @p->state) @p->state = TASK_RUNNING.
>
> So I think we could safely stick to kthread_run() if the condition in
> at the NAPI wake point checks for INTERRUPTIBLE?

I think so. kthread_run() wakes up the kthread and kthread_wait_poll()
should put it to INTERRUPTIBLE mode and schedule() will make it go to
sleep, and wait for the next napi_schedule().
I've also tested on my setup and saw no issues.

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2021-03-01 18:20 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-27  0:30 [PATCH net v2] net: fix race between napi kthread mode and busy poll Wei Wang
2021-02-27  0:48 ` Jakub Kicinski
2021-02-27  1:02   ` Wei Wang
2021-02-27  1:22     ` Jakub Kicinski
2021-02-27  1:35       ` Wei Wang
2021-02-27  2:08         ` Jakub Kicinski
2021-02-27 19:00           ` Wei Wang
2021-02-27 23:23             ` Wei Wang
2021-02-28 19:17               ` Jakub Kicinski
2021-03-01 18:16                 ` Wei Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).