libevent中event_base_loopbreak与BEV_OPT_DEFER_CALLBACKS

2023-11-01

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

libevent中event_base_loopbreak与BEV_OPT_DEFER_CALLBACKS 的相关文章

  • Linux 中 m 标志和 o 标志将存储在哪里

    我想知道最近收到的路由器通告的 m 标志和 o 标志的值 从内核源代码中我知道存储了 m 标志和 o 标志 Remember the managed otherconf flags from most recently received R
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String
  • 适用于 Linux 的轻量级 IDE [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • linux下无法创建僵尸进程

    嗯 我有一个奇怪的问题 我无法在我的项目中创建僵尸进程 但我可以在其他文件中创建僵尸进程 有简单的说明 int main if fork 0 printf Some instructions n else sleep 10 wait 0 r
  • python获取上传/下载速度

    我想在我的计算机上监控上传和下载速度 一个名为 conky 的程序已经在 conky conf 中执行了以下操作 Connection quality alignr wireless link qual perc wlan0 downspe
  • 通过 Visual Studio 2017 使用远程调试时 Linux 控制台输出在哪里?

    我的Visual Studio 2017 VS2017 成功连接Linux系统 代码如下 include
  • 在centos中安装sqlite3 dev和其他包

    我正在尝试使用 cpanel 在 centos 机器上安装 sqlite dev 和其他库 以便能够编译应用程序 我对 debian 比 centos 更熟悉 我知道我需要的库是 libsqlite3 dev libkrb5 dev lib
  • 如何在linux中以编程方式获取dir的大小?

    我想通过 C 程序获取 linux 中特定目录的确切大小 我尝试使用 statfs path struct statfs 但它没有给出确切的大小 我也尝试过 stat 但它返回任何目录的大小为 4096 请建议我如何获取 dir 的确切大小
  • GMail 421 4.7.0 稍后重试,关闭连接

    我试图找出为什么它无法使用 GMail 从我的服务器发送邮件 为此 我使用 SwiftMailer 但我可以将问题包含在以下独立代码中
  • 如何根据标签将单个 XML 文件拆分为多个

    我有一个带有标签的 XML 文件 我想像这样分割文件
  • 使用 MAX_ORDER / 包含 mmzone.h

    根据https www kernel org doc Documentation networking packet mmap txt https www kernel org doc Documentation networking pa
  • 如何在 Linux 中使用 C 语言使用共享内存

    我的一个项目有点问题 我一直在试图找到一个有据可查的使用共享内存的例子fork 但没有成功 基本上情况是 当用户启动程序时 我需要在共享内存中存储两个值 当前路径这是一个char and a 文件名这也是char 根据命令参数 启动一个新进
  • Intel 上的 gcc 中的 _mm_pause 用法

    我参考过这个网页 https software intel com en us articles benefitting power and performance sleep loops https software intel com
  • Mac OS X 上的 /proc/self/cmdline / GetCommandLine 等效项是什么?

    如何在不使用 argc argv 的情况下访问 Mac OS X 上的命令行 在 Linux 上 我会简单地阅读 proc self cmdline or use GetCommandLine在 Windows 上 但我找不到 Mac OS
  • 如何在 *nix 中登录时运行脚本?

    我知道我曾经知道如何做到这一点 但是 如何在 unix 中登录时运行脚本 bash 可以 From 维基百科 Bash http en wikipedia org wiki Bash 28Unix shell 29 当 Bash 启动时 它
  • linux下如何从文本文件中获取值

    我有一些文本格式的文件 xxx conf 我在这个文件中有一些文本 disablelog 1 当我使用 grep r disablelog oscam conf 输出是 disablelog 1 但我只需要值1 请问你有什么想法吗 一种方法
  • 使用 gdb 调试 Linux 内核模块

    我想知道 API 在内核模块 中返回什么 从几种形式可以知道 这并不是那么简单 我们需要加载符号表来调试内核模块 所以我所做的就是 1 尝试找到内核模块的 text bss和 data段地址 2 在 gdb 中使用 add symbol f
  • Apache 访问 Linux 中的 NTFS 链接文件夹

    在 Debian jessie 中使用 Apache2 PHP 当我想在 Apache 的文档文件夹 var www 中创建一个新的小节时 我只需创建一个指向我的 php 文件所在的外部文件夹的链接 然后只需更改该文件夹的所有者和权限文件夹
  • 为什么同一个curl命令在windows和linux下输出不同的东西?

    为什么同样的curl o file https www link com 命令输出不同的东西 例如 如果我运行命令curl o source txt https www youtube com playlist list PLIx6Fwnp
  • 复制目录内容

    我想将目录 tmp1 的内容复制到另一个目录 tmp2 tmp1 可能包含文件和其他目录 我想使用C C 复制tmp1的内容 包括模式 如果 tmp1 包含目录树 我想递归复制它们 最简单的解决方案是什么 我找到了一个解决方案来打开目录并读

随机推荐

  • 特别篇 :从 0 开始创作云原生应用 (殷达)

    本节内容的分享主要围绕以下两方面 介绍云原生应用是什么 介绍 Helm 和如何创作一个 Helm 应用 一 云原生应用是什么 首先我们来思考一个问题 云原生应用是什么 在生活中我们会和各种各样的应用打交道 有时候会在移动端上使用淘宝购物 使
  • 关于windows下gcc+cmake指定elf文件到特定section中

    1 map 文件中 glue 7 glue 7t是什么 Those stub sections are generated by the linker not by gcc itself so any documentation would
  • Spring Boot +JWT +MybatisPlus,使用Token登录详细教程,附源码!

    一 新建Spring Boot项目 1 File New Module 2 点击下一步 3 写完这些 点击下一步 4 选择插件 5 选择项目地址 选择完成后点击Finish 二 1 创建完成后 修改pom xml文件 添加以下依赖
  • Linux服务——nginx重写功能与反向代理

    目录 一 nginx重写功能 if指令 return指令 set指令 break指令 rewrite指令 防盗链 二 反向代理 反向代理参数 反向代理 缓存功能 反向代理 ip穿透 反向代理 动静分离 反向代理 负载均衡 一 nginx重写
  • STM32都学什么

    一 什么是STM32 对于STM32 从字面意思上来理解 ST是意法半导体 M是Microelectronics的缩写 其中32表示的是32位 那么整合起来理解就是 STM32就是指的ST公司开发的32位微控制器 在如今的32位控制器中 S
  • ChatGLM-6B微调,P-Tuning,LoRA,Full parameter

    官方教程 ChatGLM 6B 微调 P Tuning LoRA Full parameter 哔哩哔哩 bilibili我们详细介绍了GLM的技术背景 以及ChatGLM 6B的微调方案 包括P tuning LoRA Full Para
  • innodb简单优化

    innodb flush log at trx commit sync binlog 双1标准 innodb flush log at trx commit 1 sync binlog 1 innodb flush log at trx c
  • 怎么在网页中添加一个AI机器人且点击后还会讲话

    效果如下 该页面是本人参加人工智能比赛的一个项目页面展示 页面可以悬浮一个机器人 鼠标点击他就能够智能语音播报 目前只能通过360浏览器访问 等参加完比赛再写具体实现步骤 如有需要源码可以评论联系本人
  • 量化投资学习-32:每一波调整的本质

    在每一轮牛市中 都会经历大致三波的上涨 每一波上涨中间都会经历1 2个月的时间调整 调整的深度在20 左右 为什么经历这种调整的 调整的目的是什么 常听到的观点就是 涨久了 就会跌 调制的目的是洗盘 洗掉浮筹 为了后期更好的上涨 所以散户最
  • gw在计算机网络里面_GW 是什么意思?

    GW是Gateway的英文缩写 即网关 又称网间连接器 协议转换器 网关在网络层以上实现网络互连 是最复杂的网络互连设备 仅用于两个高层协议不同的网络互连 网关既可以用于广域网互连 也可以用于局域网互连 是一种充当转换重任的计算机系统或设备
  • Tone Mapping中luma滤波(降噪)对噪声放大的定性分析

    Tone Mapping中luma滤波对噪声放大的定性分析 在tone mapping过程中 通常经过统计之后得到一条mapping曲线 记这条曲线为 f x f x f x mapping过程中 对于给定的点 假定其亮度为
  • Oracle GoldenGate 将 SQLServer 数据实时同步到 ORACLE

    Oracle GoldenGate 简称 OGG 使在不同关系型数据库之间能进行实时同步复制可谓非常强大 OGG 支持 oracle sql server mysql db2 Sybase 等关系数据库直接的数据复制 OGG 这种灵活特性能
  • 计算机网络基础知识总结

    计算机网络学习的核心内容就是网络协议的学习 网络协议是为计算机网络中进行数据交换而建立的规则 标准或者说是约定的集合 因为不同用户的数据终端可能采取的字符集是不同的 两者需要进行通信 必须要在一定的标准上进行 一个很形象地比喻就是我们的语言
  • Arduino学习模拟输出

    1 实现效果 通过两个按键可以控制led变亮或者变暗 boolean pushButton1 定义布尔型变量存储按键1的状态 boolean pushButton2 定义布尔型变量存储按键2的状态 int ledPin 9 LED引脚号 i
  • Python3 面向对象

    Python从设计之初就已经是一门面向对象的语言 正因为如此 在Python中创建一个类和对象是很容易的 本章节我们将详细介绍Python的面向对象编程 如果你以前没有接触过面向对象的编程语言 那你可能需要先了解一些面向对象语言的一些基本特
  • 数据库备份和恢复

    这里介绍两种方法 1 mysqldump mysqldump不需要登录到数据库中就可以备份和恢复库和表 1 备份 mysqldump uroot p 123123 mytest gt mnt mytest bak date F sql 注意
  • Django学习 day4

    今天学习了简单的用户登录界面 也是对template的简单初探 Django有个叫模板 Template 的东东 可以直接把你的Html代码写在模板里 返回给浏览器 模板初探 使用模板的两个步骤 配置存html文件的模板目录 在你的view
  • html5期末大作业课程设计仿苹果官网(源码+报告)

    页面展示 下面有下载地址 免费哦 链接 https pan baidu com s 1 5ZDXVZmM64ALY2i31Hwfg 提取码 vtrk 一 需求分析 设计目的 一 可行性分析 时代背景 根据中国互联网络信息中心 CNNIC 在
  • 【教程】电信光猫烽火HG5140A怎么改桥接模式,telecomadmin超级密码

    一 背景 坐标杭州 宽带移机 师傅给我换了个战未来的 支持万兆的光猫 以前我是依据型号网上搜索搞到超级管理员用户就行桥接的 给我换了这个新光猫后 自己死活折腾不出来 二 正文 以前大家都习惯用超级管理员进入光猫 改桥接模式 利用光猫的安全漏
  • libevent中event_base_loopbreak与BEV_OPT_DEFER_CALLBACKS

    最近用C 和libevent改写了一个多线程网络服务器应用 大体框架是前端一个tcp连接监听线程 接收到连接后将socket随机交给一个后台工作线程做进一步处理 所有的线程均使用event base loop事件循环 其中有这样一个需求 我