]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ipv6: prevent fib6_run_gc() contention
authorMichal Kubeček <mkubecek@suse.cz>
Thu, 1 Aug 2013 08:04:14 +0000 (10:04 +0200)
committerZefan Li <lizefan@huawei.com>
Mon, 21 Mar 2016 01:17:57 +0000 (09:17 +0800)
commit 2ac3ac8f86f2fe065d746d9a9abaca867adec577 upstream.

On a high-traffic router with many processors and many IPv6 dst
entries, soft lockup in fib6_run_gc() can occur when number of
entries reaches gc_thresh.

This happens because fib6_run_gc() uses fib6_gc_lock to allow
only one thread to run the garbage collector but ip6_dst_gc()
doesn't update net->ipv6.ip6_rt_last_gc until fib6_run_gc()
returns. On a system with many entries, this can take some time
so that in the meantime, other threads pass the tests in
ip6_dst_gc() (ip6_rt_last_gc is still not updated) and wait for
the lock. They then have to run the garbage collector one after
another which blocks them for quite long.

Resolve this by replacing special value ~0UL of expire parameter
to fib6_run_gc() by explicit "force" parameter to choose between
spin_lock_bh() and spin_trylock_bh() and call fib6_run_gc() with
force=false if gc_thresh is reached but not max_size.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
Signed-off-by: David S. Miller <davem@davemloft.net>
[lizf: Backported to 3.4: adjust context]
Signed-off-by: Zefan Li <lizefan@huawei.com>
include/net/ip6_fib.h
net/ipv6/ip6_fib.c
net/ipv6/ndisc.c
net/ipv6/route.c

index 0ae759a6c76ef7f2b58ba28588195223443ca352..49c4cfe810d239e754e3f13336df97dbdab2c749 100644 (file)
@@ -266,7 +266,7 @@ extern void                 inet6_rt_notify(int event, struct rt6_info *rt,
                                                struct nl_info *info);
 
 extern void                    fib6_run_gc(unsigned long expires,
-                                           struct net *net);
+                                           struct net *net, bool force);
 
 extern void                    fib6_gc_cleanup(void);
 
index 2cfcfb7efa913d5bd2b1b544d308508a93d1c83c..fc5ce6e9dc6fe3b322662e30d71c0fc9310b6e73 100644 (file)
@@ -1593,19 +1593,16 @@ static int fib6_age(struct rt6_info *rt, void *arg)
 
 static DEFINE_SPINLOCK(fib6_gc_lock);
 
-void fib6_run_gc(unsigned long expires, struct net *net)
+void fib6_run_gc(unsigned long expires, struct net *net, bool force)
 {
-       if (expires != ~0UL) {
+       if (force) {
                spin_lock_bh(&fib6_gc_lock);
-               gc_args.timeout = expires ? (int)expires :
-                       net->ipv6.sysctl.ip6_rt_gc_interval;
-       } else {
-               if (!spin_trylock_bh(&fib6_gc_lock)) {
-                       mod_timer(&net->ipv6.ip6_fib_timer, jiffies + HZ);
-                       return;
-               }
-               gc_args.timeout = net->ipv6.sysctl.ip6_rt_gc_interval;
+       } else if (!spin_trylock_bh(&fib6_gc_lock)) {
+               mod_timer(&net->ipv6.ip6_fib_timer, jiffies + HZ);
+               return;
        }
+       gc_args.timeout = expires ? (int)expires :
+                         net->ipv6.sysctl.ip6_rt_gc_interval;
 
        gc_args.more = icmp6_dst_gc();
 
@@ -1622,7 +1619,7 @@ void fib6_run_gc(unsigned long expires, struct net *net)
 
 static void fib6_gc_timer_cb(unsigned long arg)
 {
-       fib6_run_gc(0, (struct net *)arg);
+       fib6_run_gc(0, (struct net *)arg, true);
 }
 
 static int __net_init fib6_net_init(struct net *net)
index 5cc78e6930b58cce61f9cd1c6eac8a392671a148..e235b4c2b1bef22a2276733401dfd843b54c8739 100644 (file)
@@ -1737,11 +1737,11 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event,
        switch (event) {
        case NETDEV_CHANGEADDR:
                neigh_changeaddr(&nd_tbl, dev);
-               fib6_run_gc(~0UL, net);
+               fib6_run_gc(0, net, false);
                break;
        case NETDEV_DOWN:
                neigh_ifdown(&nd_tbl, dev);
-               fib6_run_gc(~0UL, net);
+               fib6_run_gc(0, net, false);
                break;
        case NETDEV_NOTIFY_PEERS:
                ndisc_send_unsol_na(dev);
index c9092178e3d8a8b4d041f35b4eb6cac0f47a2262..7ab7f8a5ee4fd2ba8155e15b9f172a64fa8dcf30 100644 (file)
@@ -1245,7 +1245,7 @@ static int ip6_dst_gc(struct dst_ops *ops)
                goto out;
 
        net->ipv6.ip6_rt_gc_expire++;
-       fib6_run_gc(net->ipv6.ip6_rt_gc_expire, net);
+       fib6_run_gc(net->ipv6.ip6_rt_gc_expire, net, entries > rt_max_size);
        net->ipv6.ip6_rt_last_gc = now;
        entries = dst_entries_get_slow(ops);
        if (entries < ops->gc_thresh)
@@ -2840,7 +2840,7 @@ int ipv6_sysctl_rtcache_flush(ctl_table *ctl, int write,
        net = (struct net *)ctl->extra1;
        delay = net->ipv6.sysctl.flush_delay;
        proc_dointvec(ctl, write, buffer, lenp, ppos);
-       fib6_run_gc(delay <= 0 ? ~0UL : (unsigned long)delay, net);
+       fib6_run_gc(delay <= 0 ? 0 : (unsigned long)delay, net, delay > 0);
        return 0;
 }