[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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help