[RFC] net: soft lockup in netif_close_many() during namespace teardown
From: Hariom Dixit <hidden>
Date: 2026-05-28 04:47:10
Subsystem:
networking [general], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Hi,
We are hitting a reproducible soft lockup in netif_close_many()
(formerly dev_close_many(), renamed in commit 88d3cec28274) during
network namespace teardown on systems with ~1700 network devices
and 30K-65K fib6_nodes.
The NETDEV_DOWN notification loop iterates all devices without
yielding the CPU. IPv6 notifier callbacks perform full FIB6 tree
walks for each device, resulting in O(N_devices * M_fib6_nodes)
work without any scheduling point.
The bug exists in current mainline (v7.1-rc5) at net/core/dev.c
line 1803 in netif_close_many().
Crash signature (from 6.6.84, function was still dev_close_many):
watchdog: BUG: soft lockup - CPU#13 stuck for 26s!
[kworker/u32:1:6737]
Workqueue: netns cleanup_net
Call Trace:
#8 asm_sysvec_apic_timer_interrupt
[exception RIP: fib6_age+20]
#11 fib6_walk
#12 fib6_clean_tree
#14 fib6_walk_continue
#15 fib6_walk
#16 fib6_clean_tree
#17 __fib6_clean_all
#18 fib6_run_gc
#19 ndisc_netdev_event
#20 notifier_call_chain
#21 raw_notifier_call_chain
#22 call_netdevice_notifiers_info
#23 dev_close_many
#24 unregister_netdevice_many_notify
#25 default_device_exit_batch
#26 ops_exit_list
#27 cleanup_net
#28 process_one_work
#29 worker_thread
CPU utilization during lockup (every 4s): 100% system, 0% idle
Analysis:
netif_close_many() dispatches NETDEV_DOWN for each device in a
tight loop. Two IPv6 notifier callbacks trigger full fib6 tree
walks per device:
1. ndisc_netdev_event(NETDEV_DOWN) -> fib6_run_gc
-> fib6_clean_all(fib6_age): walks entire fib6 tree per
device. During teardown, fib6_age finds nothing to expire
(no RTF_EXPIRES routes), so the tree stays full-size across
all iterations.
2. addrconf_notify(NETDEV_DOWN) -> addrconf_ifdown
-> rt6_disable_ip -> fib6_clean_all(fib6_ifdown): walks
entire fib6 tree per device.
With 1700 devices and 60K+ nodes, the total uninterrupted
work is ~102M node visits, taking >26 seconds -- well over the
10-second soft lockup threshold.
Proposed fix (against mainline v7.1-rc5):
Adding cond_resched() after each device's notification dispatch.
At this point only rtnl_lock (a mutex) is held -- all spinlocks
and RCU read-side critical sections within the notifier callbacks
are acquired and released per-device invocation, so sleeping is
safe.
diff --git a/net/core/dev.c b/net/core/dev.c
--- a/net/core/dev.c
+++ b/net/core/dev.c@@ -1803,6 +1803,7 @@ void netif_close_many(struct list_head *head, bool unlink) list_for_each_entry_safe(dev, tmp, head, close_list) { rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP|IFF_RUNNING, GFP_KERNEL, 0, NULL); call_netdevice_notifiers(NETDEV_DOWN, dev); + cond_resched(); if (unlink) list_del_init(&dev->close_list); }
Testing (on 6.6.84 with equivalent change in dev_close_many): - Without fix: soft lockup panic with 1700 devices + 65K routes - With fix: no crash with 4000 devices + 65K routes Note: The IPv4 path (fib_netdev_event -> fib_disable_ip -> fib_table_flush) also fires from the same NETDEV_DOWN loop, so the single cond_resched() covers both IPv4 and IPv6 scenarios. Question: 1. Is cond_resched() the right approach here? I can send a formal patch if the approach looks reasonable. Thanks, Hariom