Skip to content

Commit

Permalink
bridge: move to workqueue gc
Browse files Browse the repository at this point in the history
Move the fdb garbage collector to a workqueue which fires at least 10
milliseconds apart and cleans chain by chain allowing for other tasks
to run in the meantime. When having thousands of fdbs the system is much
more responsive. Most importantly remove the need to check if the
matched entry has expired in __br_fdb_get that causes false-sharing and
is completely unnecessary if we cleanup entries, at worst we'll get 10ms
of traffic for that entry before it gets deleted.

Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Nikolay Aleksandrov authored and David S. Miller committed Feb 7, 2017
1 parent 1f90c7f commit f7cdee8
Show file tree
Hide file tree
Showing 10 changed files with 29 additions and 23 deletions.
1 change: 1 addition & 0 deletions net/bridge/br_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -411,4 +411,5 @@ void br_dev_setup(struct net_device *dev)
br_netfilter_rtable_init(br);
br_stp_timer_init(br);
br_multicast_init(br);
INIT_DELAYED_WORK(&br->gc_work, br_fdb_cleanup);
}
31 changes: 19 additions & 12 deletions net/bridge/br_fdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f)
if (f->added_by_external_learn)
fdb_del_external_learn(f);

hlist_del_rcu(&f->hlist);
hlist_del_init_rcu(&f->hlist);
fdb_notify(br, f, RTM_DELNEIGH);
call_rcu(&f->rcu, fdb_rcu_free);
}
Expand Down Expand Up @@ -290,34 +290,43 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
spin_unlock_bh(&br->hash_lock);
}

void br_fdb_cleanup(unsigned long _data)
void br_fdb_cleanup(struct work_struct *work)
{
struct net_bridge *br = (struct net_bridge *)_data;
struct net_bridge *br = container_of(work, struct net_bridge,
gc_work.work);
unsigned long delay = hold_time(br);
unsigned long next_timer = jiffies + br->ageing_time;
unsigned long work_delay = delay;
unsigned long now = jiffies;
int i;

spin_lock(&br->hash_lock);
for (i = 0; i < BR_HASH_SIZE; i++) {
struct net_bridge_fdb_entry *f;
struct hlist_node *n;

if (!br->hash[i].first)
continue;

spin_lock_bh(&br->hash_lock);
hlist_for_each_entry_safe(f, n, &br->hash[i], hlist) {
unsigned long this_timer;

if (f->is_static)
continue;
if (f->added_by_external_learn)
continue;
this_timer = f->updated + delay;
if (time_before_eq(this_timer, jiffies))
if (time_after(this_timer, now))
work_delay = min(work_delay, this_timer - now);
else
fdb_delete(br, f);
else if (time_before(this_timer, next_timer))
next_timer = this_timer;
}
spin_unlock_bh(&br->hash_lock);
cond_resched();
}
spin_unlock(&br->hash_lock);

mod_timer(&br->gc_timer, round_jiffies_up(next_timer));
/* Cleanup minimum 10 milliseconds apart */
work_delay = max_t(unsigned long, work_delay, msecs_to_jiffies(10));
mod_delayed_work(system_long_wq, &br->gc_work, work_delay);
}

/* Completely flush all dynamic entries in forwarding database.*/
Expand Down Expand Up @@ -382,8 +391,6 @@ struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br,
&br->hash[br_mac_hash(addr, vid)], hlist) {
if (ether_addr_equal(fdb->addr.addr, addr) &&
fdb->vlan_id == vid) {
if (unlikely(has_expired(br, fdb)))
break;
return fdb;
}
}
Expand Down
2 changes: 1 addition & 1 deletion net/bridge/br_if.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ void br_dev_delete(struct net_device *dev, struct list_head *head)

br_vlan_flush(br);
br_multicast_dev_del(br);
del_timer_sync(&br->gc_timer);
cancel_delayed_work_sync(&br->gc_work);

br_sysfs_delbr(br->dev);
unregister_netdevice_queue(br->dev, head);
Expand Down
2 changes: 1 addition & 1 deletion net/bridge/br_ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ static int old_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
b.hello_timer_value = br_timer_value(&br->hello_timer);
b.tcn_timer_value = br_timer_value(&br->tcn_timer);
b.topology_change_timer_value = br_timer_value(&br->topology_change_timer);
b.gc_timer_value = br_timer_value(&br->gc_timer);
b.gc_timer_value = br_timer_value(&br->gc_work.timer);
rcu_read_unlock();

if (copy_to_user((void __user *)args[1], &b, sizeof(b)))
Expand Down
2 changes: 1 addition & 1 deletion net/bridge/br_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -1250,7 +1250,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
if (nla_put_u64_64bit(skb, IFLA_BR_TOPOLOGY_CHANGE_TIMER, clockval,
IFLA_BR_PAD))
return -EMSGSIZE;
clockval = br_timer_value(&br->gc_timer);
clockval = br_timer_value(&br->gc_work.timer);
if (nla_put_u64_64bit(skb, IFLA_BR_GC_TIMER, clockval, IFLA_BR_PAD))
return -EMSGSIZE;

Expand Down
4 changes: 2 additions & 2 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ struct net_bridge {
struct timer_list hello_timer;
struct timer_list tcn_timer;
struct timer_list topology_change_timer;
struct timer_list gc_timer;
struct delayed_work gc_work;
struct kobject *ifobj;
u32 auto_cnt;

Expand Down Expand Up @@ -502,7 +502,7 @@ void br_fdb_find_delete_local(struct net_bridge *br,
const unsigned char *addr, u16 vid);
void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr);
void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr);
void br_fdb_cleanup(unsigned long arg);
void br_fdb_cleanup(struct work_struct *work);
void br_fdb_delete_by_port(struct net_bridge *br,
const struct net_bridge_port *p, u16 vid, int do_all);
struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge *br,
Expand Down
2 changes: 1 addition & 1 deletion net/bridge/br_stp.c
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ int br_set_ageing_time(struct net_bridge *br, clock_t ageing_time)
br->ageing_time = t;
spin_unlock_bh(&br->lock);

mod_timer(&br->gc_timer, jiffies);
mod_delayed_work(system_long_wq, &br->gc_work, 0);

return 0;
}
Expand Down
4 changes: 2 additions & 2 deletions net/bridge/br_stp_if.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ void br_stp_enable_bridge(struct net_bridge *br)
spin_lock_bh(&br->lock);
if (br->stp_enabled == BR_KERNEL_STP)
mod_timer(&br->hello_timer, jiffies + br->hello_time);
mod_timer(&br->gc_timer, jiffies + HZ/10);
mod_delayed_work(system_long_wq, &br->gc_work, HZ / 10);

br_config_bpdu_generation(br);

Expand Down Expand Up @@ -88,7 +88,7 @@ void br_stp_disable_bridge(struct net_bridge *br)
del_timer_sync(&br->hello_timer);
del_timer_sync(&br->topology_change_timer);
del_timer_sync(&br->tcn_timer);
del_timer_sync(&br->gc_timer);
cancel_delayed_work_sync(&br->gc_work);
}

/* called under bridge lock */
Expand Down
2 changes: 0 additions & 2 deletions net/bridge/br_stp_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ void br_stp_timer_init(struct net_bridge *br)
setup_timer(&br->topology_change_timer,
br_topology_change_timer_expired,
(unsigned long) br);

setup_timer(&br->gc_timer, br_fdb_cleanup, (unsigned long) br);
}

void br_stp_port_timer_init(struct net_bridge_port *p)
Expand Down
2 changes: 1 addition & 1 deletion net/bridge/br_sysfs_br.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ static ssize_t gc_timer_show(struct device *d, struct device_attribute *attr,
char *buf)
{
struct net_bridge *br = to_bridge(d);
return sprintf(buf, "%ld\n", br_timer_value(&br->gc_timer));
return sprintf(buf, "%ld\n", br_timer_value(&br->gc_work.timer));
}
static DEVICE_ATTR_RO(gc_timer);

Expand Down

0 comments on commit f7cdee8

Please sign in to comment.