Libevent 源码阅读笔记(二)、从一个简单例子开始(下)

阅读本文之前请确保你看过上一篇文章:Libevent 源码阅读笔记(一)、从一个简单例子开始(上)

在上一篇文章中,我们提到了关于 Libevent 使用的一个基本逻辑:

  1. 调用event_init()创建 event_base 对象,一个 event_base 对象相当于一个 Reactor 实例
  2. 创建具体的事件处理器,其中 evsignal_new()创建的是信号事件处理器和evtimer_new()创建的是定时事件处理器
  3. 调用event_add()将事件处理器插入到注册事件队列中,并将其对应的事件插入到事件多路分发器中,相当于 Reactor 中的register_handler方法
  4. 调用event_base_dispatch()执行事件循环
  5. 循环结束后,调用*_free()释放系统资源

在这篇文章中,我们将继续分析剩余的后 3 个步骤

将事件处理器添加到注册事件队列中

要了解如何将事件处理器插入到注册事件队列中,就需要先知道 Libevent 是如何将句柄和注册事件之间的联系定义出来。其相关的数据结构主要定义在 event-internal.h 和 evmap.c 文件中

//event-internal.h
//若定义了 EVMAP_USE_HT 则将 event_io_map 定义为哈希表。该哈希表存储了 event_map_entry 对象和 I/O 事件队列之间的映射关系
#ifdef EVMAP_USE_HT
#define HT_NO_CACHE_HASH_VALUES
#include "ht-internal.h"
struct event_map_entry;
HT_HEAD(event_io_map, event_map_entry);
#else
#define event_io_map event_signal_map
#endif

/* Used to map signal numbers to a list of events.  If EVMAP_USE_HT is not
   defined, this structure is also used as event_io_map, which maps fds to a
   list of events.
*/
struct event_signal_map {
    void **entries; //用于存放 evmap_io 或 evmap_signal 数组
    /* The number of entries available in entries */
    int nentries;   //entries 的大小
};

// evmap.c 文件
/** An entry for an evmap_io list: notes all the events that want to read or
    write on a given fd, and the number of each.
  */
struct evmap_io {
    struct event_dlist events;  //I/O 事件队列
    ev_uint16_t nread;      //记录事件队列中的读事件数量
    ev_uint16_t nwrite;     //记录事件队列中的写事件数量
    ev_uint16_t nclose;     //记录事件队列中的关闭事件数量
};

/* An entry for an evmap_signal list: notes all the events that want to know
   when a signal triggers. */
struct evmap_signal {
    struct event_dlist events;  //信号事件队列
};

/* On some platforms, fds start at 0 and increment by 1 as they are
   allocated, and old numbers get used.  For these platforms, we
   implement io maps just like signal maps: as an array of pointers to
   struct evmap_io.  But on other platforms (windows), sockets are not
   0-indexed, not necessarily consecutive, and not necessarily reused.
   There, we use a hashtable to implement evmap_io.
*/
#ifdef EVMAP_USE_HT
//哈希表中的节点类型:以 fd 为键, ent 为值
struct event_map_entry {
    HT_ENTRY(event_map_entry) map_node;
    evutil_socket_t fd;
    union { /* This is a union in case we need to make more things that can
               be in the hashtable. */
        struct evmap_io evmap_io;
    } ent;
};

创建好的 event 事件可以利用 event_add()插入相应的注册事件队列中

//向注册事件队列中添加事件处理器
int event_add(struct event *ev, const struct timeval *tv)
{
    int res;
    if (EVUTIL_FAILURE_CHECK(!ev->ev_base)) {
        event_warnx("%s: event has no event_base set.", __func__);
        return -1;
    }
    EVBASE_ACQUIRE_LOCK(ev->ev_base, th_base_lock);
    res = event_add_nolock_(ev, tv, 0);
    EVBASE_RELEASE_LOCK(ev->ev_base, th_base_lock);
    return (res);
}
int event_add_nolock_(struct event *ev, const struct timeval *tv,int tv_is_absolute)
{
    struct event_base *base = ev->ev_base;
    int res = 0;
    int notify = 0;

    EVENT_BASE_ASSERT_LOCKED(base);
    event_debug_assert_is_setup_(ev);

    event_debug((
         "event_add: event: %p (fd "EV_SOCK_FMT"), %s%s%s%scall %p",
         ev,
         EV_SOCK_ARG(ev->ev_fd),
         ev->ev_events & EV_READ ? "EV_READ " : " ",
         ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
         ev->ev_events & EV_CLOSED ? "EV_CLOSED " : " ",
         tv ? "EV_TIMEOUT " : " ",
         ev->ev_callback));

    EVUTIL_ASSERT(!(ev->ev_flags & ~EVLIST_ALL));

    if (ev->ev_flags & EVLIST_FINALIZING) {
        /* XXXX debug */
        return (-1);
    }

    /* 如果添加的事件处理器是一个尚未被插入到通用定时器队列或时间堆内的定时器,则为该定时器在时间堆上预留一个位置*/
    if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
        if (min_heap_reserve_(&base->timeheap,
            1 + min_heap_size_(&base->timeheap)) == -1)
            return (-1);  /* ENOMEM == errno */
    }

    /* 如果当前调用者不是主线程,且被添加的是信号事件处理器,而主线程也正在调用该信号事件的回调函数,则当前调用者必须等待主线程完成调用,以避免导致竞态的发生 */
#ifndef EVENT__DISABLE_THREAD_SUPPORT
    if (base->current_event == event_to_event_callback(ev) &&
        (ev->ev_events & EV_SIGNAL)
        && !EVBASE_IN_THREAD(base)) {
        ++base->current_event_waiters;
        EVTHREAD_COND_WAIT(base->current_event_cond, base->th_base_lock);
    }
#endif
    
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE|EVLIST_ACTIVE_LATER))) {
        if (ev->ev_events & (EV_READ|EV_WRITE|EV_CLOSED))
            //添加 IO 事件和 IO 事件处理器的映射关系
            res = evmap_io_add_(base, ev->ev_fd, ev);
        else if (ev->ev_events & EV_SIGNAL)
            //添加信号事件和信号事件处理器的映射关系
            res = evmap_signal_add_(base, (int)ev->ev_fd, ev);
        if (res != -1)
            //将事件处理器插入到注册事件队列
            event_queue_insert_inserted(base, ev);
        if (res == 1) {
            //通知主线程,事件多路分发器中添加了新的事件
            notify = 1;
            res = 0;
        }
    }

    /* 在前一个事件添加成功,且该事件是一个定时器的话,将其添加至时间堆或通用定时器队列当中。*/
    if (res != -1 && tv != NULL) {
        struct timeval now;
        int common_timeout;
#ifdef USE_REINSERT_TIMEOUT
        int was_common;
        int old_timeout_idx;
#endif

        /* 对于永久性事件处理器,如果其超时时间不是绝对时间,则将该事件的超时时间保存到 ev->ev_io_timeout 中*/
        if (ev->ev_closure == EV_CLOSURE_EVENT_PERSIST && !tv_is_absolute)
            ev->ev_io_timeout = *tv;

#ifndef USE_REINSERT_TIMEOUT
        //若该事件处理器已经被插入到通用定时器队列或时间堆当中,则先对其进行删除
        if (ev->ev_flags & EVLIST_TIMEOUT) {
            event_queue_remove_timeout(base, ev);
        }
#endif

        /* Check if it is active due to a timeout.  Rescheduling
         * this timeout before the callback can be executed
         * removes it from the active list. */
        //如果待添加的事件处理器因超时而被激活,则要从活动事件队列当中删除它以便重新调度
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
            (ev->ev_res & EV_TIMEOUT)) {
            if (ev->ev_events & EV_SIGNAL) {
                //若事件处理器的回调函数正在执行当中,则设置 ev_ncalls 的值为 0,就可以退出回调函数的执行循环
                if (ev->ev_ncalls && ev->ev_pncalls) {
                    /* Abort loop */
                    *ev->ev_pncalls = 0;
                }
            }

            event_queue_remove_active(base, event_to_event_callback(ev));
        }

        gettime(base, &now);

        common_timeout = is_common_timeout(tv, base);   //用于判断应该将定时器插入到通用定时器队列还是时间堆当中
#ifdef USE_REINSERT_TIMEOUT
        was_common = is_common_timeout(&ev->ev_timeout, base);
        old_timeout_idx = COMMON_TIMEOUT_IDX(&ev->ev_timeout);
#endif

        if (tv_is_absolute) {
            ev->ev_timeout = *tv;
        } else if (common_timeout) {
            struct timeval tmp = *tv;
            tmp.tv_usec &= MICROSECONDS_MASK;
            evutil_timeradd(&now, &tmp, &ev->ev_timeout);
            ev->ev_timeout.tv_usec |=
                (tv->tv_usec & ~MICROSECONDS_MASK);
        } else {
            //加上当前的系统时间以获得定时器超时的绝对时间
            evutil_timeradd(&now, tv, &ev->ev_timeout);
        }

        event_debug((
             "event_add: event %p, timeout in %d seconds %d useconds, call %p",
             ev, (int)tv->tv_sec, (int)tv->tv_usec, ev->ev_callback));
//插入定时器
#ifdef USE_REINSERT_TIMEOUT
        //根据 common_timeout 决定插入的位置是通用定时器队列还是时间堆
        event_queue_reinsert_timeout(base, ev, was_common, common_timeout, old_timeout_idx);
#else
        event_queue_insert_timeout(base, ev);
#endif

        if (common_timeout) {
            struct common_timeout_list *ctl =
                get_common_timeout_list(base, &ev->ev_timeout);
            //若被插入的定时器是通用定时器队列的首元素,则调用 common_timeout_schedule 将其转移到时间堆单中,这样可以使得时间堆和定时器链表的定时器得到相同的处理。
            if (ev == TAILQ_FIRST(&ctl->events)) {
                common_timeout_schedule(ctl, &now, ev);
            }
        } else {
            struct event* top = NULL;
            /* See if the earliest timeout is now earlier than it
             * was before: if so, we will need to tell the main
             * thread to wake up earlier than it would otherwise.
             * We double check the timeout of the top element to
             * handle time distortions due to system suspension.
             */
            //判断当前的定时器是否位于最小堆的堆顶,如果是的话,通知主线程以便更新 tick 的周期
            if (min_heap_elt_is_top_(ev))
                notify = 1;
            //若定时器不位于堆顶,则取出堆顶的定时器,判断是否小于当前时间,如果是,则通知主线程
            else if ((top = min_heap_top_(&base->timeheap)) != NULL &&
                     evutil_timercmp(&top->ev_timeout, &now, <))
                notify = 1;
            //猜测:time distortions:在一般情况下,主线程每次 tick 都对应了时间堆堆顶的定时器,因此不会出现堆顶定时器的时间小于当前时间的情况。但考虑到主线程可能因为阻塞无法及时发出 tick 信号,因此如果检测到堆顶定时器的时间小于当前时间,那说明发生了 time distortions,需要通知主线程,让它尽快 tick
        }
    }

    /* 若有必要,唤醒主线程 */
    if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
        evthread_notify_base(base);

    event_debug_note_add_(ev);

    return (res);
}

可以看到,在整个 event_add_nolock_函数中,主要的工作是为不同的事件提供对应的操作,主要分为 3 类操作,

  • 调用 evmap_io_add_ 将 I/O 事件添加到事件多路分发器中,并将对应的事件处理器添加到 I/O 事件队列当中,同时建立起 I/O 事件和 I/O 事件处理器之间的映射,最后将 I/O 事件处理器插入到注册事件队列中
  • 调用 evmap_signal_add_ 将信号事件添加到事件多路分发器中,并将对应的事件处理器添加到信号事件队列当中,同时建立起信号事件和信号事件处理器之间的映射,最后将信号事件处理器插入到注册事件队列中
  • 调用 event_queue_insert_inserted修改 event_base 的事件处理器总数
  • 根据是否允许将定时器重新插入到通用定时器队列或时间堆中,调用event_queue_reinsert_timeoutevent_queue_insert_timeout来将定时器插入到通用定时器队列或时间堆中

接下来,对于注册事件,Libevent 通过evmap_io_add_evmap_signal_add_ 以及 event_queue_insert_inserted进行将它们插入到对应的事件队列当中,其中evmap_io_add_evmap_signal_add_ 被定义在 evmap.c 文件中,而event_queue_insert_inserted被定义在 event.c 文件中

// evmap.c 文件
int evmap_io_add_(struct event_base *base, evutil_socket_t fd, struct event *ev)
{
    //获得 event_base 的后端 I/O 复用机制实例
    const struct eventop *evsel = base->evsel;
    //获得 event_base 中文件描述符与 I/O 事件队列的映射表(哈希表或数组),event_io_map 由宏 HT_HEAD 定义
    struct event_io_map *io = &base->io;
    //ctx代表 fd 所对应的 I/O 事件队列
    struct evmap_io *ctx = NULL;
    int nread, nwrite, nclose, retval = 0;
    short res = 0, old = 0;
    struct event *old_ev;

    EVUTIL_ASSERT(fd == ev->ev_fd);

    if (fd < 0)
        return 0;

#ifndef EVMAP_USE_HT
    if (fd >= io->nentries) {
        //当 fd 超过了映射表 io 的最大容量时,扩充哈希表
        if (evmap_make_space(io, fd, sizeof(struct evmap_io *)) == -1)
            return (-1);
    }
#endif
    //在映射表 io 中获取 ctx 和 fd 之间的映射关系,若没有映射关系,则建立对应的映射关系
    GET_IO_SLOT_AND_CTOR(ctx, io, fd, evmap_io, evmap_io_init,
                         evsel->fdinfo_len);

    nread = ctx->nread;
    nwrite = ctx->nwrite;
    nclose = ctx->nclose;

    if (nread)
        old |= EV_READ;
    if (nwrite)
        old |= EV_WRITE;
    if (nclose)
        old |= EV_CLOSED;
    //根据 ev 的事件类型,增加 I/O 事件队列中的相应的事件数量
    if (ev->ev_events & EV_READ) {
        if (++nread == 1)
            res |= EV_READ;
    }
    if (ev->ev_events & EV_WRITE) {
        if (++nwrite == 1)
            res |= EV_WRITE;
    }
    if (ev->ev_events & EV_CLOSED) {
        if (++nclose == 1)
            res |= EV_CLOSED;
    }
    if (EVUTIL_UNLIKELY(nread > 0xffff || nwrite > 0xffff || nclose > 0xffff)) {
        event_warnx("Too many events reading or writing on fd %d",
            (int)fd);
        return -1;
    }
    if (EVENT_DEBUG_MODE_IS_ON() &&
        (old_ev = LIST_FIRST(&ctx->events)) &&
        (old_ev->ev_events&EV_ET) != (ev->ev_events&EV_ET)) {
        event_warnx("Tried to mix edge-triggered and non-edge-triggered"
            " events on fd %d", (int)fd);
        return -1;
    }

    if (res) {
        void *extra = ((char*)ctx) + sizeof(struct evmap_io);
        /* XXX(niels): we cannot mix edge-triggered and
         * level-triggered, we should probably assert on
         * this. */
        //在事件多路分发器中注册事件。add 函数是事件多路分发器的接口。
        if (evsel->add(base, ev->ev_fd,
            old, (ev->ev_events & EV_ET) | res, extra) == -1)
            return (-1);
        retval = 1;
    }

    ctx->nread = (ev_uint16_t) nread;
    ctx->nwrite = (ev_uint16_t) nwrite;
    ctx->nclose = (ev_uint16_t) nclose;
    //将 ev 插入到 I/O 事件队列 ctx 的头部
    LIST_INSERT_HEAD(&ctx->events, ev, ev_io_next);

    return (retval);
}

int evmap_signal_add_(struct event_base *base, int sig, struct event *ev)
{
    const struct eventop *evsel = base->evsigsel;
    //获得 event_base 中信号值与信号事件队列的映射表(哈希表或数组),event_signal_map 由宏 HT_HEAD 定义
    struct event_signal_map *map = &base->sigmap;
    struct evmap_signal *ctx = NULL;

    if (sig < 0 || sig >= NSIG)
        return (-1);
    //若信号值超出了 event_signal_map 的索引范围,则对 event_signal_map 进行扩容
    if (sig >= map->nentries) {
        if (evmap_make_space(
            map, sig, sizeof(struct evmap_signal *)) == -1)
            return (-1);
    }
    //在映射表 signal 中获取 ctx 和信号值之间的映射关系,若没有映射关系,则建立对应的映射关系
    GET_SIGNAL_SLOT_AND_CTOR(ctx, map, sig, evmap_signal, evmap_signal_init,
        base->evsigsel->fdinfo_len);
    //若 ctx 中的信号事件队列不为空,则调用 I/O 复用机制 evsel 中的 add 函数,对信号事件进行注册
    if (LIST_EMPTY(&ctx->events)) {
        if (evsel->add(base, ev->ev_fd, 0, EV_SIGNAL, NULL)
            == -1)
            return (-1);
    }
    //插入信号事件处理器
    LIST_INSERT_HEAD(&ctx->events, ev, ev_signal_next);

    return (1);
}

//event.c
static void event_queue_insert_inserted(struct event_base *base, struct event *ev)
{
    EVENT_BASE_ASSERT_LOCKED(base);

    if (EVUTIL_FAILURE_CHECK(ev->ev_flags & EVLIST_INSERTED)) {
        event_errx(1, "%s: %p(fd "EV_SOCK_FMT") already inserted", __func__,
            ev, EV_SOCK_ARG(ev->ev_fd));
        return;
    }
    //更新 base 当中 ev->ev_flags 类型的计数器
    INCR_EVENT_COUNT(base, ev->ev_flags);
    //标记事件 ev 已经被插入到注册事件队列中
    ev->ev_flags |= EVLIST_INSERTED;
}

对于超时事件,Libevent 则调用了event_queue_insert_timeout来将定时器插入到通用定时器队列或时间堆。

static void event_queue_insert_timeout(struct event_base *base, struct event *ev)
{
    EVENT_BASE_ASSERT_LOCKED(base);

    if (EVUTIL_FAILURE_CHECK(ev->ev_flags & EVLIST_TIMEOUT)) {
        event_errx(1, "%s: %p(fd "EV_SOCK_FMT") already on timeout", __func__,
            ev, EV_SOCK_ARG(ev->ev_fd));
        return;
    }
    //更新 base 中的超时事件计数器
    INCR_EVENT_COUNT(base, ev->ev_flags);
    //标记以将这个超时事件添加到通用定时器链表或时间堆当中
    ev->ev_flags |= EVLIST_TIMEOUT;
    //判断该定时器的类型是否为通用定时器,若是则插入到通用定时器链表中,否则插入到时间堆当中
    if (is_common_timeout(&ev->ev_timeout, base)) {
        struct common_timeout_list *ctl =
            get_common_timeout_list(base, &ev->ev_timeout);
        insert_common_timeout_inorder(ctl, ev);
    } else {
        min_heap_push_(&base->timeheap, ev);
    }
}

这里需要指出,早期的 Libevent 使用了时间堆来设计定时器。由于在某些场合下,升序定时器链表的效率会更高,因此新版本的 Libevent 同时支持通用定时器链表和时间堆的操作。

Libevent 的核心动力 —— 事件循环

正如前面提到,Libevent 是基于事件驱动的,整个框架库的运转核心在于事件驱动:Reactor 在接收到相应事件后,通过 event_base_dispatch() 的方法,将事件分派给对应的事件处理器去处理。event_base_dispatch()定义在 event.c 文件中

int event_base_dispatch(struct event_base *event_base)
{
    return (event_base_loop(event_base, 0));
}
int event_base_loop(struct event_base *base, int flags)
{
    const struct eventop *evsel = base->evsel;
    struct timeval tv;
    struct timeval *tv_p;
    int res, done, retval = 0;

    /* Grab the lock.  We will release it inside evsel.dispatch, and again
     * as we invoke user callbacks. */
    EVBASE_ACQUIRE_LOCK(base, th_base_lock);
    //一个 event_base 仅允许执行一个事件循环
    if (base->running_loop) {
        event_warnx("%s: reentrant invocation.  Only one event_base_loop"
            " can run on each event_base at once.", __func__);
        EVBASE_RELEASE_LOCK(base, th_base_lock);
        return -1;
    }

    base->running_loop = 1;

    clear_time_cache(base);
    //设置信号事件的 event_base 实例
    if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
        evsig_set_base_(base);

    done = 0;

#ifndef EVENT__DISABLE_THREAD_SUPPORT
    //获取当前线程 ID
    base->th_owner_id = EVTHREAD_GET_ID();
#endif

    base->event_gotterm = base->event_break = 0;

    while (!done) {
        base->event_continue = 0;
        base->n_deferreds_queued = 0;

        /* Terminate the loop if we have been asked to */
        //这几个值代表了事件执行后应当对其执行什么操作,具体可看上篇文章中 event_base 的注释代码
        if (base->event_gotterm) {
            break;
        }

        if (base->event_break) {
            break;
        }

        tv_p = &tv;
        if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
            //获取时间堆的堆顶元素的超时值,并设为此次事件的超时值
            timeout_next(base, &tv_p);
        } else {
            //若有就绪事件尚未处理,则将I/O复用系统调用的超时时间设置为0,这样系统调用会直接返回,程序也就可以立即处理就绪事件了
            evutil_timerclear(&tv);
        }

        //如果 event_base 中没有注册任何事件,则直接退出事件循环
        if (0==(flags&EVLOOP_NO_EXIT_ON_EMPTY) &&
            !event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
            event_debug(("%s: no events registered.", __func__));
            retval = 1;
            goto done;
        }

        event_queue_make_later_events_active(base);

        clear_time_cache(base);
        //调用多路事件分发器的 dispatch 方法分发事件,将就绪事件插入到活动事件队列中
        res = evsel->dispatch(base, tv_p);

        if (res == -1) {
            event_debug(("%s: dispatch returned unsuccessfully.",
                __func__));
            retval = -1;
            goto done;
        }
        //更新时间缓冲为当前的系统时间
        update_time_cache(base);
        //检查时间堆上的到期事件并处理
        timeout_process(base);

        if (N_ACTIVE_CALLBACKS(base)) {
            //调用 event_process_active 处理活动事件
            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;
    }
    event_debug(("%s: asked to terminate loop.", __func__));

done:
    //循环结束时的工作:清空时间缓存,并设置停止循环标志位
    clear_time_cache(base);
    base->running_loop = 0;

    EVBASE_RELEASE_LOCK(base, th_base_lock);

    return (retval);
}

从代码中可以看出,event_base_dispatch()调用了event_base_loop(),而event_base_loop()的关键是

调用了 event_base 中的evsel->dispatch(),该方法封装了 I/O 复用系统调用。至于如何根据编程环境的不同,自动选择合适的 I/O 复用技术,可以看上一篇文章中关于 eventop 的介绍

释放资源

static int event_base_free_queues_(struct event_base *base, int run_finalizers)
{
    int deleted = 0, i;
    //释放掉活动事件队列中的剩余事件
    for (i = 0; i < base->nactivequeues; ++i) {
        struct event_callback *evcb, *next;
        for (evcb = TAILQ_FIRST(&base->activequeues[i]); evcb; ) {
            next = TAILQ_NEXT(evcb, evcb_active_next);
            deleted += event_base_cancel_single_callback_(base, evcb, run_finalizers);
            evcb = next;
        }
    }
    //释放延后活动事件队列中的事件
    {
        struct event_callback *evcb;
        while ((evcb = TAILQ_FIRST(&base->active_later_queue))) {
            deleted += event_base_cancel_single_callback_(base, evcb, run_finalizers);
        }
    }

    return deleted;
}
static int event_base_cancel_single_callback_(struct event_base *base,
    struct event_callback *evcb,
    int run_finalizers)
{
    int result = 0;
    //将已初始化的事件处理器释放掉
    if (evcb->evcb_flags & EVLIST_INIT) {
        struct event *ev = event_callback_to_event(evcb);
        if (!(ev->ev_flags & EVLIST_INTERNAL)) {
            event_del_(ev, EVENT_DEL_EVEN_IF_FINALIZING);
            result = 1;
        }
    } else {
        EVBASE_ACQUIRE_LOCK(base, th_base_lock);
        event_callback_cancel_nolock_(base, evcb, 1);
        EVBASE_RELEASE_LOCK(base, th_base_lock);
        result = 1;
    }

    if (run_finalizers && (evcb->evcb_flags & EVLIST_FINALIZING)) {
        switch (evcb->evcb_closure) {
        //对于类型为 EV_CLOSURE_EVENT_FINALIZE 和 EV_CLOSURE_EVENT_FINALIZE_FREE 的事件,调用它们相应的回调函数,结束后释放掉它们的资源
        case EV_CLOSURE_EVENT_FINALIZE:
        case EV_CLOSURE_EVENT_FINALIZE_FREE: {
            struct event *ev = event_callback_to_event(evcb);
            ev->ev_evcallback.evcb_cb_union.evcb_evfinalize(ev, ev->ev_arg);
            if (evcb->evcb_closure == EV_CLOSURE_EVENT_FINALIZE_FREE)
                mm_free(ev);
            break;
        }
        case EV_CLOSURE_CB_FINALIZE:
            //执行 EV_CLOSURE_CB_FINALIZE 类型事件的回调函数,它们的回调函数当中包含了事件终止时所应当执行的操作
            evcb->evcb_cb_union.evcb_cbfinalize(evcb, evcb->evcb_arg);
            break;
        default:
            break;
        }
    }
    return result;
}

参考资料

《Linux 高性能服务器编程》 —— 游双著
《libevent-book》 —— Libevent 上的官方文档

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350