最近用C++和libevent改写了一个多线程网络服务器应用,大体框架是前端一个tcp连接监听线程,接收到连接后将socket随机交给一个后台工作线程做进一步处理。所有的线程均使用event_base_loop事件循环。
其中有这样一个需求,我们可能会通过某种通知,让这个服务器程序重新初始化(不是重启),包括结束和重新创建后台线程。
结束后台线程我这里用了event_base_loopbreak,并在event_loop退出前及时的bufferevent_free了所有bufferevent,事件循环退出后也进行了event_base_free,但我发现,还是有相当一部分socket没有释放。比较诡异的是看起来完全等价的socket,有些关闭了,有一些没关闭。
我们知道,后台线程对socket进行读写前,需要创建bufferevent,我这里用了BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS参数,BEV_OPT_CLOSE_ON_FREE 意味着当bufferevent释放时会自动关闭socket,BEV_OPT_DEFER_CALLBACKS意味着延迟回调。为了弄清socket不关闭的原因,不得不查看libevent的源码。
先看几处代码:
bufferevent关于事件的处理函数
#define SCHEDULE_DEFERRED(bevp) \
do { \
bufferevent_incref(&(bevp)->bev); \
event_deferred_cb_schedule( \
event_base_get_deferred_cb_queue((bevp)->bev.ev_base), \
&(bevp)->deferred); \
} while (0)
void
_bufferevent_run_readcb(struct bufferevent *bufev)
{
/* Requires that we hold the lock and a reference */
struct bufferevent_private *p =
EVUTIL_UPCAST(bufev, struct bufferevent_private, bev);
if (bufev->readcb == NULL)
return;
if (p->options & BEV_OPT_DEFER_CALLBACKS) {
p->readcb_pending = 1;
if (!p->deferred.queued)
SCHEDULE_DEFERRED(p);
} else {
bufev->readcb(bufev, bufev->cbarg);
}
}
由于我们使用了BEV_OPT_DEFER_CALLBACKS参数,当socket发生读写事件时,libevent会使用SCHEDULE_DEFERRED将事件处理回调保存在base->defer_queue队列中等待延后处理。值得注意的是,SCHEDULE_DEFERRED会将bufferevent的reference_count加1。
event_base_loopbreak的实现:
int
event_base_loopbreak(struct event_base *event_base)
{//省略
event_base->event_break = 1;
}
很简单,就是对event_break的置位,接下来看看libevent事件循环的实现:
int event_base_loop(struct event_base *base, int flags)
//省略
while (!done) {
/* Terminate the loop if we have been asked to */
if (base->event_gotterm) {
break;
}
if (base->event_break) {
break;
}
//省略
if (N_ACTIVE_CALLBACKS(base)) {
int n = event_process_active(base);
if ((flags & EVLOOP_ONCE)
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
//省略
clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);
}
不难发现loop其实就是一个大while,event_base_loopbreak则是将base->event_break置位达到退出循环的目的。
event_process_active是处理事件回调的地方,跟进:
static int
event_process_active(struct event_base *base)
{
/* Caller must hold th_base_lock */
struct event_list *activeq = NULL;
int i, c = 0;
for (i = 0; i < base->nactivequeues; ++i) {
if (TAILQ_FIRST(&base->activequeues[i]) != NULL) {
activeq = &base->activequeues[i];
c = event_process_active_single_queue(base, activeq);
if (c < 0)
return -1;
else if (c > 0)
break; /* Processed a real event; do not
* consider lower-priority events */
/* If we get here, all of the events we processed
* were internal. Continue. */
}
}
event_process_deferred_callbacks(&base->defer_queue,&base->event_break);
return c;
}
event_process_deferred_callbacks函数正是对延迟回调进行处理的地方,不妨看一下socket读事件的延迟回调实现:
static void
bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
{//省略
error:
bufferevent_disable(bufev, EV_READ);
_bufferevent_run_eventcb(bufev, what);
done:
_bufferevent_decref_and_unlock(bufev);
}
继续观察不难发现每一个事件回调结束时,libevent都会将buffervent的引用计数减1。其实,bufferevent_free的实现也不过是将引用计数减1。
bufferevent_free(struct bufferevent *bufev)
{
BEV_LOCK(bufev);
bufferevent_setcb(bufev, NULL, NULL, NULL, NULL);
_bufferevent_cancel_all(bufev);
_bufferevent_decref_and_unlock(bufev);
}
_bufferevent_decref_and_unlock的实现:
int
_bufferevent_decref_and_unlock(struct bufferevent *bufev)
{//省略
if (--bufev_private->refcnt) {
BEV_UNLOCK(bufev);
return 0;
}
/* Clean up the shared info */
if (bufev->be_ops->destruct)
bufev->be_ops->destruct(bufev);
//省略
}
这里bufev->be_ops->destruct对应的实现就是close。
至此推测,我们程序里那部分没有被关闭的socket,在我们退其出事件循环那时,正好有延迟回调在base->defer_queue队列当中,导致对应bufferevent的引用计数大于1,即使我们显示的调用bufferevent_free,也仅仅只是将引用计数降为1,不能达到立即关闭socket的目的。之后,由于事件循环退出,类似bufferevent_readcb这样的延迟回调函数就再也没有机会被执行了,因此这些socket对应的bufferevent并不会随着事件循环的退出而释放,就这样一直留在了内存中。
找到问题所在就好办,我们在event_base_loop的实现之前加一个函数:
int event_base_remove_deffered_tasks(struct event_base *base){
int count = 0;
struct deferred_cb *cb;
struct deferred_cb_queue *queue = &base->defer_queue;
if(!queue)return 0;
while ((cb = TAILQ_FIRST(&queue->deferred_cb_list))) {
cb->queued = 0;
TAILQ_REMOVE(&queue->deferred_cb_list, cb, cb_next);
--queue->active_count;
UNLOCK_DEFERRED_QUEUE(queue);
cb->cb(cb, cb->arg);
LOCK_DEFERRED_QUEUE(queue);
++count;
}
return count;
}
然后在event_base_loop退出之前调用一次:
int
event_base_loop(struct event_base *base, int flags)
{//省略
done:
event_base_remove_deffered_tasks(base);
clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);
这样,在event_base_loop退出之前,将base->defer_queue中所有的延迟回调全部执行一遍,事实上,此时执行这些延迟回调除了将bufferevent的引用计数减1以外,什么事情也不会发生,因为bufferevent_free已经将所有的event callback置为了NULL。
问题终于得到解决,尽管还是修改了libevent的源码。