libevent中event_base_loopbreak与BEV_OPT_DEFER_CALLBACKS

最近用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的源码。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值