Erik Lundgren and Breno Leitao reported [1] a case where
lockdep_unregister_key() can be called from time critical code pathes
where rntl_lock() may be held. And the synchronize_rcu() in it can slow
down operations such as using tc to replace a qdisc in a network device.
In fact the synchronize_rcu() in lockdep_unregister_key() is to wait for
all is_dynamic_key() callers to finish so that removing a key from the
key hashlist, and we can use shazptr to protect the hashlist as well.
Compared to the proposed solution which replaces synchronize_rcu() with
synchronize_rcu_expedited(), using shazptr here can achieve the
same/better synchronization time without the need to send IPI. Hence use
shazptr here.
Reported-by: Erik Lundgren <elundgren@meta.com>
Reported-by: Breno Leitao <leitao@debian.org>
Link: https://lore.kernel.org/all/20250321-lockdep-v1-1-78b732d195fb@debian.org/ [1]
Signed-off-by: Boqun Feng <boqun.feng@gmail.com>
---
kernel/locking/lockdep.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index dd2bbf73718b..5c205dd425f8 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -58,6 +58,7 @@
#include <linux/context_tracking.h>
#include <linux/console.h>
#include <linux/kasan.h>
+#include <linux/shazptr.h>
#include <asm/sections.h>
@@ -1267,14 +1268,18 @@ static bool is_dynamic_key(const struct lock_class_key *key)
hash_head = keyhashentry(key);
- rcu_read_lock();
+ /* Need preemption disable for using shazptr. */
+ guard(preempt)();
+
+ /* Protect the list search with shazptr. */
+ guard(shazptr)(hash_head);
+
hlist_for_each_entry_rcu(k, hash_head, hash_entry) {
if (k == key) {
found = true;
break;
}
}
- rcu_read_unlock();
return found;
}
@@ -6620,7 +6625,7 @@ void lockdep_unregister_key(struct lock_class_key *key)
call_rcu(&delayed_free.rcu_head, free_zapped_rcu);
/* Wait until is_dynamic_key() has finished accessing k->hash_entry. */
- synchronize_rcu();
+ synchronize_shazptr(keyhashentry(key));
}
EXPORT_SYMBOL_GPL(lockdep_unregister_key);
--
2.39.5 (Apple Git-154)
Hello Boqun, On Tue, Jun 24, 2025 at 08:11:01PM -0700, Boqun Feng wrote: > Erik Lundgren and Breno Leitao reported [1] a case where > lockdep_unregister_key() can be called from time critical code pathes > where rntl_lock() may be held. And the synchronize_rcu() in it can slow > down operations such as using tc to replace a qdisc in a network device. > > In fact the synchronize_rcu() in lockdep_unregister_key() is to wait for > all is_dynamic_key() callers to finish so that removing a key from the > key hashlist, and we can use shazptr to protect the hashlist as well. > > Compared to the proposed solution which replaces synchronize_rcu() with > synchronize_rcu_expedited(), using shazptr here can achieve the > same/better synchronization time without the need to send IPI. Hence use > shazptr here. > > Reported-by: Erik Lundgren <elundgren@meta.com> > Reported-by: Breno Leitao <leitao@debian.org> > Link: https://lore.kernel.org/all/20250321-lockdep-v1-1-78b732d195fb@debian.org/ [1] > Signed-off-by: Boqun Feng <boqun.feng@gmail.com> First of all, thanks for working to fix the origianl issue. I've been able to test this in my host, and the gain is impressive. Before: # time /usr/sbin/tc qdisc replace dev eth0 root handle 0x1234: mq real 0m13.195s user 0m0.001s sys 0m2.746s With your patch: # time /usr/sbin/tc qdisc replace dev eth0 root handle 0x1234: mq real 0m0.135s user 0m0.002s sys 0m0.116s # time /usr/sbin/tc qdisc replace dev eth0 root handle 0x1: mq real 0m0.127s user 0m0.001s sys 0m0.112s Please add the following to the series: Tested-by: Breno Leitao <leitao@debian.org>
On Thu, Jul 10, 2025 at 07:06:09AM -0700, Breno Leitao wrote: > Hello Boqun, > > On Tue, Jun 24, 2025 at 08:11:01PM -0700, Boqun Feng wrote: > > Erik Lundgren and Breno Leitao reported [1] a case where > > lockdep_unregister_key() can be called from time critical code pathes > > where rntl_lock() may be held. And the synchronize_rcu() in it can slow > > down operations such as using tc to replace a qdisc in a network device. > > > > In fact the synchronize_rcu() in lockdep_unregister_key() is to wait for > > all is_dynamic_key() callers to finish so that removing a key from the > > key hashlist, and we can use shazptr to protect the hashlist as well. > > > > Compared to the proposed solution which replaces synchronize_rcu() with > > synchronize_rcu_expedited(), using shazptr here can achieve the > > same/better synchronization time without the need to send IPI. Hence use > > shazptr here. > > > > Reported-by: Erik Lundgren <elundgren@meta.com> > > Reported-by: Breno Leitao <leitao@debian.org> > > Link: https://lore.kernel.org/all/20250321-lockdep-v1-1-78b732d195fb@debian.org/ [1] > > Signed-off-by: Boqun Feng <boqun.feng@gmail.com> > > First of all, thanks for working to fix the origianl issue. I've been > able to test this in my host, and the gain is impressive. > > Before: > > # time /usr/sbin/tc qdisc replace dev eth0 root handle 0x1234: mq > real 0m13.195s > user 0m0.001s > sys 0m2.746s > > With your patch: > > # time /usr/sbin/tc qdisc replace dev eth0 root handle 0x1234: mq > real 0m0.135s > user 0m0.002s > sys 0m0.116s > > # time /usr/sbin/tc qdisc replace dev eth0 root handle 0x1: mq > real 0m0.127s > user 0m0.001s > sys 0m0.112s > > Please add the following to the series: > > Tested-by: Breno Leitao <leitao@debian.org> Thanks! Will apply ;-) Regards, Boqun
On Tue, Jun 24, 2025 at 08:11:01PM -0700, Boqun Feng wrote: > + /* Need preemption disable for using shazptr. */ > + guard(preempt)(); > + > + /* Protect the list search with shazptr. */ > + guard(shazptr)(hash_head); OK, this is the end of the series, and so far every single user is doing both a preempt and a shazptr guard. Why can't we simplify this and have the shazptr guard imply preempt-disable?
On Wed, Jun 25, 2025 at 01:59:29PM +0200, Peter Zijlstra wrote: > On Tue, Jun 24, 2025 at 08:11:01PM -0700, Boqun Feng wrote: > > > + /* Need preemption disable for using shazptr. */ > > + guard(preempt)(); > > + > > + /* Protect the list search with shazptr. */ > > + guard(shazptr)(hash_head); > > OK, this is the end of the series, and so far every single user is doing > both a preempt and a shazptr guard. Why can't we simplify this and have > the shazptr guard imply preempt-disable? You're right. The background story is that in the beginning, the hazard pointer protection was placed at the callsites of is_dynamic_key(): one in register_lock_class() and one in lockdep_init_map_type(), and in register_lock_class() I could use the fact that it's called with irq disabled to save the preempt-disable. So given the current users, it makes sense to fold the preempt disabling into shazptr guard. Regards, Boqun
© 2016 - 2025 Red Hat, Inc.