You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免libevent 1.4.14中event_base_new因文件句柄耗尽触发SIGABRT崩溃

问题

我们使用libevent 1.4.14版本,在生产环境中发现当主机文件描述符耗尽时,进程会因libevent触发的SIGABRT信号崩溃。以下测试代码可复现该问题:

#include <event.h>
#include <sys/epoll.h>
#include <sys/resource.h>
#include <iostream>

int main(int argc, char* argv[]) {
  struct rlimit rl;
  rl.rlim_cur = rl.rlim_max = 400; // artificially limit number of fds
  int ret = setrlimit(RLIMIT_NOFILE, &rl);
  if (ret) {
    int errno_ = errno;
    std::cerr << "unable to set RLIMIT_NOFILE " << errno_ << std::endl;
  }

  for (int i = 0; i < 400; i++) {
    event_base_new();
  }
}

程序最终会崩溃并输出错误信息:

[err] evsignal_init: socketpair: Too many open files
libevent exit code: 1

调用栈显示崩溃源于event_base_new流程:

@ 00000000025ca144 event_err
                       [...]/log.c:88
    @ 00000000025cc0e2 evsignal_init
                       [...]/signal.c:112
    @ 00000000025cb73b epoll_init
                       [...]/epoll.c:295
    @ 00000000025c8f41 event_base_new
                       [...]/event.c:192

请问是否有办法阻止event_base_new导致进程崩溃,转而将错误向上传递(例如返回NULL)?

解决方案

针对libevent 1.4.14的这个问题,有几种可行的处理方式:

1. 自定义错误处理函数,替换默认崩溃逻辑

libevent允许通过event_set_log_callback设置自定义日志回调,替换默认的event_err(默认会调用abort()触发崩溃)。你可以在初始化libevent前注册自己的回调,遇到错误时仅记录日志,不触发SIGABRT:

#include <event.h>
#include <stdio.h>

void custom_log_callback(int severity, const char* msg) {
    // 仅记录错误日志,不执行abort()
    fprintf(stderr, "[custom err] %s\n", msg);
}

int main() {
    // 注册自定义回调,覆盖默认崩溃逻辑
    event_set_log_callback(custom_log_callback);
    
    // 后续正常调用event_base_new...
}

注意:这种方式会全局改变libevent的错误处理行为,所有原本会触发崩溃的错误都会转为日志记录,需要自行在业务代码中判断event_base_new的返回值是否为NULL,处理初始化失败的情况。

2. 预检查文件描述符余量,提前规避错误

在调用event_base_new前,先检查当前可用的文件描述符数量,确保有足够FD供libevent初始化(libevent初始化信号处理需要创建socketpair,至少需要2个可用FD)。

可以通过getrlimit获取FD上限,再遍历/proc/self/fd目录(Linux环境)统计已使用的FD数量:

#include <sys/resource.h>
#include <dirent.h>
#include <stdio.h>

int get_used_fds() {
    int count = 0;
    DIR* dir = opendir("/proc/self/fd");
    if (dir == NULL) return -1;
    
    struct dirent* entry;
    while ((entry = readdir(dir)) != NULL) {
        count++;
    }
    closedir(dir);
    // 减去.和..两个目录项
    return count - 2;
}

bool has_enough_fds() {
    struct rlimit rl;
    if (getrlimit(RLIMIT_NOFILE, &rl) != 0) return false;
    
    int used = get_used_fds();
    if (used == -1) return false;
    
    // 预留至少2个FD给libevent的socketpair初始化
    return (rl.rlim_cur - used) >= 2;
}

调用event_base_new前先执行has_enough_fds(),如果返回false则跳过初始化,自行处理错误。

3. 修改libevent源码,让错误向上传递

如果上述方法无法满足需求,可以直接修改libevent源码:

  • 找到evsignal_init函数(signal.c文件)中调用event_err的位置,将其改为返回错误码,而非触发abort();
  • 修改epoll_init函数逻辑,让它在evsignal_init失败时返回错误,最终让event_base_new返回NULL。

这种方式需要重新编译libevent,适合对稳定性要求极高的场景,但会增加后续版本升级的维护成本。

内容的提问来源于stack exchange,提问作者Savior

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 21:13:31