commit e7087f93e05d7cad42889ee055c2499ff0f1720e Author: Andrey V. Elsukov Date: Thu Oct 27 18:42:32 2016 +0300 Add network garbage collector. diff --git a/sys/net/if.c b/sys/net/if.c index fa55662..86bf820 100644 --- a/sys/net/if.c +++ b/sys/net/if.c @@ -101,6 +101,7 @@ SYSCTL_NODE(_net, PF_LINK, link, CTLFLAG_RW, 0, "Link layers"); SYSCTL_NODE(_net_link, 0, generic, CTLFLAG_RW, 0, "Generic link-management"); +SYSCTL_NODE(_net, OID_AUTO, gc, CTLFLAG_RW, 0, "Garbage collector"); SYSCTL_INT(_net_link, OID_AUTO, ifqmaxlen, CTLFLAG_RDTUN, &ifqmaxlen, 0, "max send queue size"); @@ -236,9 +237,122 @@ static if_com_alloc_t *if_com_alloc[256]; static if_com_free_t *if_com_free[256]; static MALLOC_DEFINE(M_IFNET, "ifnet", "interface internals"); +static MALLOC_DEFINE(M_NETGC, "netgc", "network garbage collector"); MALLOC_DEFINE(M_IFADDR, "ifaddr", "interface address"); MALLOC_DEFINE(M_IFMADDR, "ether_multi", "link-level multicast address"); +static void netgc_func(void *arg); +static void netgc_free(int type, void *data, int can_sleep); +static void if_free_internal(struct ifnet *ifp); +#define NETGC_CALLOUT_DELAY 1 +#define NETGC_EXPIRE_DELAY 3 + +struct netgc_entry { + time_t expire; + int type; +#define NETGC_IFNET 0 + void *data; + TAILQ_ENTRY(netgc_entry) chain; +}; + +static VNET_DEFINE(struct mtx, netgc_mtx); +static VNET_DEFINE(struct callout, netgc_callout); +static VNET_DEFINE(int, netgc_enabled); +static VNET_DEFINE(u_int, netgc_expire_delay) = NETGC_EXPIRE_DELAY; +static VNET_DEFINE(TAILQ_HEAD(, netgc_entry), netgc_queue); +static VNET_DEFINE(int, netgc_failures) = 0; +#define V_netgc_mtx VNET(netgc_mtx) +#define V_netgc_callout VNET(netgc_callout) +#define V_netgc_enabled VNET(netgc_enabled) +#define V_netgc_expire_delay VNET(netgc_expire_delay) +#define V_netgc_queue VNET(netgc_queue) +#define V_netgc_failures VNET(netgc_failures) + +SYSCTL_INT(_net_gc, OID_AUTO, enable, CTLFLAG_RWTUN | CTLFLAG_VNET, + &VNET_NAME(netgc_enabled), 1, "Enable network garbage collector"); +SYSCTL_UINT(_net_gc, OID_AUTO, expire_delay, CTLFLAG_RWTUN | CTLFLAG_VNET, + &VNET_NAME(netgc_expire_delay), 1, "Object expiration delay"); +SYSCTL_INT(_net_gc, OID_AUTO, failures, CTLFLAG_RD | CTLFLAG_VNET, + &VNET_NAME(netgc_failures), 0, + "Number of objects leaked from network garbage collector"); + +static void +netgc_func(void *arg) +{ + TAILQ_HEAD(, netgc_entry) drainq; + struct netgc_entry *entry, *tmp; + + TAILQ_INIT(&drainq); + /* Move all contents of current queue to new empty queue */ + mtx_lock(&V_netgc_mtx); + TAILQ_CONCAT(&drainq, &V_netgc_queue, chain); + mtx_unlock(&V_netgc_mtx); + + /* Dispatch as much as we can */ + TAILQ_FOREACH_SAFE(entry, &drainq, chain, tmp) { + if (entry->expire > time_uptime) + break; + + /* We can definitely delete this item */ + TAILQ_REMOVE(&drainq, entry, chain); + switch (entry->type) { + case NETGC_IFNET: + if_free_internal((struct ifnet *)entry->data); + break; + } + free(entry, M_NETGC); + } + if (TAILQ_EMPTY(&drainq)) + return; + /* + * Add remaining data back to queue. + * Note items are still sorted by time_uptime after merge. + */ + mtx_lock(&V_netgc_mtx); + /* Add new items to the end of drainq */ + TAILQ_CONCAT(&drainq, &V_netgc_queue, chain); + /* Move items back to netgc_queue */ + TAILQ_SWAP(&V_netgc_queue, &drainq, netgc_entry, chain); + mtx_unlock(&V_netgc_mtx); + + callout_reset(&V_netgc_callout, hz * NETGC_CALLOUT_DELAY, + netgc_func, NULL); +} + +static void +netgc_free(int type, void *data, int can_sleep) +{ + struct netgc_entry *entry; + + entry = malloc(sizeof(*entry), M_NETGC, + (can_sleep ? M_WAITOK : M_NOWAIT) | M_ZERO); + if (entry == NULL) { + /* + * Skip freeing. Memory leak is better than panic. + */ + V_netgc_failures++; + return; + } + entry->expire = time_uptime + V_netgc_expire_delay; + entry->type = type; + entry->data = data; + if (can_sleep == 0 && mtx_trylock(&V_netgc_mtx) == 0) { + /* Fail to acquire lock. Add another leak. */ + free(entry, M_NETGC); + V_netgc_failures++; + return; + } + if (can_sleep != 0) + mtx_lock(&V_netgc_mtx); + + TAILQ_INSERT_TAIL(&netgc_queue, entry, chain); + mtx_unlock(&V_netgc_mtx); + /* Schedule callout if not running */ + if (!callout_pending(&V_netgc_callout)) + callout_reset(&V_netgc_callout, hz * NETGC_CALLOUT_DELAY, + netgc_func, NULL); +} + struct ifnet * ifnet_byindex_locked(u_short idx) { @@ -377,6 +491,11 @@ vnet_if_init(const void *unused __unused) if_grow(); /* create initial table */ IFNET_WUNLOCK(); vnet_if_clone_init(); + + /* Init garbage collector */ + mtx_init(&V_netgc_mtx, "NETGC", NULL, MTX_DEF); + TAILQ_INIT(&V_netgc_queue); + callout_init(&V_netgc_callout, 1); } VNET_SYSINIT(vnet_if_init, SI_SUB_INIT_IF, SI_ORDER_SECOND, vnet_if_init, NULL); @@ -391,6 +510,7 @@ vnet_if_uninit(const void *unused __unused) VNET_ASSERT(TAILQ_EMPTY(&V_ifg_head), ("%s:%d tailq &V_ifg_head=%p " "not empty", __func__, __LINE__, &V_ifg_head)); + /* XXX: Cleanup garbage collector */ free((caddr_t)V_ifindex_table, M_IFNET); } VNET_SYSUNINIT(vnet_if_uninit, SI_SUB_INIT_IF, SI_ORDER_FIRST, @@ -535,8 +655,12 @@ if_free(struct ifnet *ifp) ifindex_free_locked(ifp->if_index); IFNET_WUNLOCK(); - if (refcount_release(&ifp->if_refcount)) - if_free_internal(ifp); + if (refcount_release(&ifp->if_refcount)) { + if (V_netgc_enabled != 0) + netgc_free(NETGC_IFNET, ifp, 0); + else + if_free_internal(ifp); + } CURVNET_RESTORE(); }