[PATCH net-next 2/2] net/sched: Don't wait for RCU when unregistering the qdisc lockdep key

From: Vlad Poenaru

Date: Mon Oct 19 2026 - 15:43:55 EST


Since commit af0cb3fa3f9e ("net/sched: fix false lockdep warning on
qdisc root lock") every qdisc except ingress, clsact and noqueue
registers a dynamic lockdep key, and destroying it calls
lockdep_unregister_key(), which waits for an expedited RCU grace period.
Replacing an mq root destroys one qdisc per TX queue under RTNL, so a
single "tc qdisc replace dev eth0 root mq" waits for that many grace
periods, each of which IPIs every CPU that is not idle.

The wait is not needed here: __qdisc_destroy() and qdisc_create() free
the qdisc with qdisc_free_rcu() after unregistering, which already
keeps the key's memory valid for a grace period. Use
lockdep_unregister_key_nosync(), and free with kfree_rcu() in the one
qdisc_alloc() error path that used kfree().

On an x86_64 KASAN + PROVE_LOCKING kernel (64 vCPU KVM guest, 64-queue
netdevsim device, 214K lock chains) this removes 5.3 ms of grace-period
wait from each of the 65 qdisc destroys in "tc qdisc replace dev eth0
root handle 1: mq".

Suggested-by: Eric Dumazet <edumazet@xxxxxxxxxx>
Link: https://lore.kernel.org/all/CANn89iLvu_ijSV+FnrZ6qei_Ty2c9V-RjwLMk24+oOS91bNjaQ@xxxxxxxxxxxxxx/
Signed-off-by: Vlad Poenaru <vlad.wing@xxxxxxxxx>
---
include/net/pkt_sched.h | 3 ++-
net/sched/sch_generic.c | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/include/net/pkt_sched.h b/include/net/pkt_sched.h
index 90d3e7943b19a..1f4195dabaa57 100644
--- a/include/net/pkt_sched.h
+++ b/include/net/pkt_sched.h
@@ -323,6 +323,7 @@ static inline void qdisc_lock_init(struct Qdisc *sch,
lockdep_set_class(&sch->q.lock, &sch->root_lock_key);
}

+/* Free @sch only after an RCU grace period, e.g. with qdisc_free_rcu(). */
static inline void qdisc_lock_uninit(struct Qdisc *sch,
const struct Qdisc_ops *ops)
{
@@ -330,7 +331,7 @@ static inline void qdisc_lock_uninit(struct Qdisc *sch,
ops == &noqueue_qdisc_ops)
return;

- lockdep_unregister_key(&sch->root_lock_key);
+ lockdep_unregister_key_nosync(&sch->root_lock_key);
}

#endif
diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c
index 6f6a6f0d5eb0d..28791d4fe35b1 100644
--- a/net/sched/sch_generic.c
+++ b/net/sched/sch_generic.c
@@ -1033,7 +1033,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue,
return sch;
errout1:
qdisc_lock_uninit(sch, ops);
- kfree(sch);
+ kfree_rcu(sch, rcu);
errout:
return ERR_PTR(err);
}
--
2.53.0-Meta