From: Yunsheng Lin <hidden> Date: 2021-05-14 03:17:28
This patchset fixes the packet stuck problem mentioned in [1].
Patch 1: Add STATE_MISSED flag to fix packet stuck problem.
Patch 2: Fix a tx_action rescheduling problem after STATE_MISSED
flag is added in patch 1.
Patch 3: Fix the significantly higher CPU consumption problem when
multiple threads are competing on a saturated outgoing
device.
V8: Change function name as suggested by Jakub and fix some typo
in patch 3, adjust commit log in patch 2, and add Acked-by
from Jakub.
V7: Fix netif_tx_wake_queue() data race noted by Jakub.
V6: Some performance optimization in patch 1 suggested by Jakub
and drop NET_XMIT_DROP checking in patch 3.
V5: add patch 3 to fix the problem reported by Michal Kubecek.
V4: Change STATE_NEED_RESCHEDULE to STATE_MISSED and add patch 2.
[1]. https://lkml.org/lkml/2019/10/9/42
Yunsheng Lin (3):
net: sched: fix packet stuck problem for lockless qdisc
net: sched: fix tx action rescheduling issue during deactivation
net: sched: fix tx action reschedule issue with stopped queue
include/net/pkt_sched.h | 7 +------
include/net/sch_generic.h | 35 ++++++++++++++++++++++++++++++++-
net/core/dev.c | 29 ++++++++++++++++++++++-----
net/sched/sch_generic.c | 50 +++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 107 insertions(+), 14 deletions(-)
--
2.7.4
From: Yunsheng Lin <hidden> Date: 2021-05-14 03:17:13
Currently qdisc_run() checks the STATE_DEACTIVATED of lockless
qdisc before calling __qdisc_run(), which ultimately clear the
STATE_MISSED when all the skb is dequeued. If STATE_DEACTIVATED
is set before clearing STATE_MISSED, there may be rescheduling
of net_tx_action() at the end of qdisc_run_end(), see below:
CPU0(net_tx_atcion) CPU1(__dev_xmit_skb) CPU2(dev_deactivate)
. . .
. set STATE_MISSED .
. __netif_schedule() .
. . set STATE_DEACTIVATED
. . qdisc_reset()
. . .
.<--------------- . synchronize_net()
clear __QDISC_STATE_SCHED | . .
. | . .
. | . some_qdisc_is_busy()
. | . return *false*
. | . .
test STATE_DEACTIVATED | . .
__qdisc_run() *not* called | . .
. | . .
test STATE_MISS | . .
__netif_schedule()--------| . .
. . .
. . .
__qdisc_run() is not called by net_tx_atcion() in CPU0 because
CPU2 has set STATE_DEACTIVATED flag during dev_deactivate(), and
STATE_MISSED is only cleared in __qdisc_run(), __netif_schedule
is called at the end of qdisc_run_end(), causing tx action
rescheduling problem.
qdisc_run() called by net_tx_action() runs in the softirq context,
which should has the same semantic as the qdisc_run() called by
__dev_xmit_skb() protected by rcu_read_lock_bh(). And there is a
synchronize_net() between STATE_DEACTIVATED flag being set and
qdisc_reset()/some_qdisc_is_busy in dev_deactivate(), we can safely
bail out for the deactived lockless qdisc in net_tx_action(), and
qdisc_reset() will reset all skb not dequeued yet.
So add the rcu_read_lock() explicitly to protect the qdisc_run()
and do the STATE_DEACTIVATED checking in net_tx_action() before
calling qdisc_run_begin(). Another option is to do the checking in
the qdisc_run_end(), but it will add unnecessary overhead for
non-tx_action case, because __dev_queue_xmit() will not see qdisc
with STATE_DEACTIVATED after synchronize_net(), the qdisc with
STATE_DEACTIVATED can only be seen by net_tx_action() because of
__netif_schedule().
The STATE_DEACTIVATED checking in qdisc_run() is to avoid race
between net_tx_action() and qdisc_reset(), see:
commit d518d2ed8640 ("net/sched: fix race between deactivation
and dequeue for NOLOCK qdisc"). As the bailout added above for
deactived lockless qdisc in net_tx_action() provides better
protection for the race without calling qdisc_run() at all, so
remove the STATE_DEACTIVATED checking in qdisc_run().
After qdisc_reset(), there is no skb in qdisc to be dequeued, so
clear the STATE_MISSED in dev_reset_queue() too.
Fixes: 6b3ba9146fe6 ("net: sched: allow qdiscs to handle locking")
Acked-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Yunsheng Lin <redacted>
V8: Clearing STATE_MISSED before calling __netif_schedule() has
avoid the endless rescheduling problem, but there may still
be a unnecessary rescheduling, so adjust the commit log.
---
include/net/pkt_sched.h | 7 +------
net/core/dev.c | 26 ++++++++++++++++++++++----
net/sched/sch_generic.c | 4 +++-
3 files changed, 26 insertions(+), 11 deletions(-)
@@ -5025,25 +5025,43 @@ static __latent_entropy void net_tx_action(struct softirq_action *h)sd->output_queue_tailp=&sd->output_queue;local_irq_enable();+rcu_read_lock();+while(head){structQdisc*q=head;spinlock_t*root_lock=NULL;head=head->next_sched;-if(!(q->flags&TCQ_F_NOLOCK)){-root_lock=qdisc_lock(q);-spin_lock(root_lock);-}/* We need to make sure head->next_sched is read*beforeclearing__QDISC_STATE_SCHED*/smp_mb__before_atomic();++if(!(q->flags&TCQ_F_NOLOCK)){+root_lock=qdisc_lock(q);+spin_lock(root_lock);+}elseif(unlikely(test_bit(__QDISC_STATE_DEACTIVATED,+&q->state))){+/* There is a synchronize_net() between+*STATE_DEACTIVATEDflagbeingsetand+*qdisc_reset()/some_qdisc_is_busy()in+*dev_deactivate(),sowecansafelybailout+*earlyheretoavoiddataracebetween+*qdisc_deactivate()andsome_qdisc_is_busy()+*forlocklessqdisc.+*/+clear_bit(__QDISC_STATE_SCHED,&q->state);+continue;+}+clear_bit(__QDISC_STATE_SCHED,&q->state);qdisc_run(q);if(root_lock)spin_unlock(root_lock);}++rcu_read_unlock();}xfrm_dev_backlog(sd);
From: Yunsheng Lin <hidden> Date: 2021-05-14 03:17:22
Lockless qdisc has below concurrent problem:
cpu0 cpu1
. .
q->enqueue .
. .
qdisc_run_begin() .
. .
dequeue_skb() .
. .
sch_direct_xmit() .
. .
. q->enqueue
. qdisc_run_begin()
. return and do nothing
. .
qdisc_run_end() .
cpu1 enqueue a skb without calling __qdisc_run() because cpu0
has not released the lock yet and spin_trylock() return false
for cpu1 in qdisc_run_begin(), and cpu0 do not see the skb
enqueued by cpu1 when calling dequeue_skb() because cpu1 may
enqueue the skb after cpu0 calling dequeue_skb() and before
cpu0 calling qdisc_run_end().
Lockless qdisc has below another concurrent problem when
tx_action is involved:
cpu0(serving tx_action) cpu1 cpu2
. . .
. q->enqueue .
. qdisc_run_begin() .
. dequeue_skb() .
. . q->enqueue
. . .
. sch_direct_xmit() .
. . qdisc_run_begin()
. . return and do nothing
. . .
clear __QDISC_STATE_SCHED . .
qdisc_run_begin() . .
return and do nothing . .
. . .
. qdisc_run_end() .
This patch fixes the above data race by:
1. If the first spin_trylock() return false and STATE_MISSED is
not set, set STATE_MISSED and retry another spin_trylock() in
case other CPU may not see STATE_MISSED after it releases the
lock.
2. reschedule if STATE_MISSED is set after the lock is released
at the end of qdisc_run_end().
For tx_action case, STATE_MISSED is also set when cpu1 is at the
end if qdisc_run_end(), so tx_action will be rescheduled again
to dequeue the skb enqueued by cpu2.
Clear STATE_MISSED before retrying a dequeuing when dequeuing
returns NULL in order to reduce the overhead of the second
spin_trylock() and __netif_schedule() calling.
Also clear the STATE_MISSED before calling __netif_schedule()
at the end of qdisc_run_end() to avoid doing another round of
dequeuing in the pfifo_fast_dequeue().
The performance impact of this patch, tested using pktgen and
dummy netdev with pfifo_fast qdisc attached:
threads without+this_patch with+this_patch delta
1 2.61Mpps 2.60Mpps -0.3%
2 3.97Mpps 3.82Mpps -3.7%
4 5.62Mpps 5.59Mpps -0.5%
8 2.78Mpps 2.77Mpps -0.3%
16 2.22Mpps 2.22Mpps -0.0%
Fixes: 6b3ba9146fe6 ("net: sched: allow qdiscs to handle locking")
Acked-by: Jakub Kicinski <kuba@kernel.org>
Tested-by: Juergen Gross <jgross@suse.com>
Signed-off-by: Yunsheng Lin <redacted>
---
V7: Clear STATE_MISSED before calling __netif_schedule()
as suggested by Jakub.
V6: Check MISSED after the first trylock, and remove the
automic test and set for performance sake as suggested
by Jakub.
V4: Change STATE_NEED_RESCHEDULE to STATE_MISSED mirroring
NAPI's NAPIF_STATE_MISSED, and add Juergen's "Tested-by"
tag for there is only renaming and typo fixing between
V4 and V3.
V3: Fix a compile error and a few comment typo, remove the
__QDISC_STATE_DEACTIVATED checking, and update the
performance data.
V2: Avoid the overhead of fixing the data race as much as
possible.
---
include/net/sch_generic.h | 35 ++++++++++++++++++++++++++++++++++-
net/sched/sch_generic.c | 19 +++++++++++++++++++
2 files changed, 53 insertions(+), 1 deletion(-)
@@ -159,8 +160,33 @@ static inline bool qdisc_is_empty(const struct Qdisc *qdisc)staticinlineboolqdisc_run_begin(structQdisc*qdisc){if(qdisc->flags&TCQ_F_NOLOCK){+if(spin_trylock(&qdisc->seqlock))+gotonolock_empty;++/* If the MISSED flag is set, it means other thread has+*settheMISSEDflagbeforesecondspin_trylock(),so+*wecanreturnfalseheretoavoidmulticpusdoing+*theset_bit()andsecondspin_trylock()concurrently.+*/+if(test_bit(__QDISC_STATE_MISSED,&qdisc->state))+returnfalse;++/* Set the MISSED flag before the second spin_trylock(),+*ifthesecondspin_trylock()returnfalse,itmeans+*othercpuholdingthelockwilldodequeuingforus+*oritwillseetheMISSEDflagsetafterreleasing+*lockandreschedulethenet_tx_action()todothe+*dequeuing.+*/+set_bit(__QDISC_STATE_MISSED,&qdisc->state);++/* Retry again in case other CPU may not see the new flag+*afteritreleasesthelockattheendofqdisc_run_end().+*/if(!spin_trylock(&qdisc->seqlock))returnfalse;++nolock_empty:WRITE_ONCE(qdisc->empty,false);}elseif(qdisc_is_running(qdisc)){returnfalse;
@@ -652,6 +654,23 @@ static struct sk_buff *pfifo_fast_dequeue(struct Qdisc *qdisc)}if(likely(skb)){qdisc_update_stats_at_dequeue(qdisc,skb);+}elseif(need_retry&&+test_bit(__QDISC_STATE_MISSED,&qdisc->state)){+/* Delay clearing the STATE_MISSED here to reduce+*theoverheadofthesecondspin_trylock()in+*qdisc_run_begin()and__netif_schedule()calling+*inqdisc_run_end().+*/+clear_bit(__QDISC_STATE_MISSED,&qdisc->state);++/* Make sure dequeuing happens after clearing+*STATE_MISSED.+*/+smp_mb__after_atomic();++need_retry=false;++gotoretry;}else{WRITE_ONCE(qdisc->empty,true);}
From: Yunsheng Lin <hidden> Date: 2021-05-14 03:17:26
The netdev qeueue might be stopped when byte queue limit has
reached or tx hw ring is full, net_tx_action() may still be
rescheduled if STATE_MISSED is set, which consumes unnecessary
cpu without dequeuing and transmiting any skb because the
netdev queue is stopped, see qdisc_run_end().
This patch fixes it by checking the netdev queue state before
calling qdisc_run() and clearing STATE_MISSED if netdev queue is
stopped during qdisc_run(), the net_tx_action() is rescheduled
again when netdev qeueue is restarted, see netif_tx_wake_queue().
As there is time window between netif_xmit_frozen_or_stopped()
checking and STATE_MISSED clearing, between which STATE_MISSED
may set by net_tx_action() scheduled by netif_tx_wake_queue(),
so set the STATE_MISSED again if netdev queue is restarted.
Fixes: 6b3ba9146fe6 ("net: sched: allow qdiscs to handle locking")
Reported-by: Michal Kubecek <redacted>
Acked-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Yunsheng Lin <redacted>
---
V8: Change qdisc_maybe_stop_tx() to qdisc_maybe_clear_missed()
as suggested by Jakub.
V7: Fix the netif_tx_wake_queue() data race noted by Jakub.
V6: Drop NET_XMIT_DROP checking for it is not really relevant
to this patch, and it may cause performance performance
regression with multi pktgen threads on dummy netdev with
pfifo_fast qdisc case.
---
net/core/dev.c | 3 ++-
net/sched/sch_generic.c | 27 ++++++++++++++++++++++++++-
2 files changed, 28 insertions(+), 2 deletions(-)
@@ -35,6 +35,25 @@conststructQdisc_ops*default_qdisc_ops=&pfifo_fast_ops;EXPORT_SYMBOL(default_qdisc_ops);+staticvoidqdisc_maybe_clear_missed(structQdisc*q,+conststructnetdev_queue*txq)+{+clear_bit(__QDISC_STATE_MISSED,&q->state);++/* Make sure the below netif_xmit_frozen_or_stopped()+*checkinghappensafterclearingSTATE_MISSED.+*/+smp_mb__after_atomic();++/* Checking netif_xmit_frozen_or_stopped() again to+*makesureSTATE_MISSEDissetiftheSTATE_MISSED+*setbynetif_tx_wake_queue()'sreschedulingof+*net_tx_action()isclearedbytheaboveclear_bit().+*/+if(!netif_xmit_frozen_or_stopped(txq))+set_bit(__QDISC_STATE_MISSED,&q->state);+}+/* Main transmission queue. *//* Modifications to data participating in scheduling must be protected with
Hello:
This series was applied to netdev/net.git (refs/heads/master):
On Fri, 14 May 2021 11:16:58 +0800 you wrote:
This patchset fixes the packet stuck problem mentioned in [1].
Patch 1: Add STATE_MISSED flag to fix packet stuck problem.
Patch 2: Fix a tx_action rescheduling problem after STATE_MISSED
flag is added in patch 1.
Patch 3: Fix the significantly higher CPU consumption problem when
multiple threads are competing on a saturated outgoing
device.
[...]