文件描述符泄漏跟踪过程

首先看到报错:

2025-06-16 14:56:27.855 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.855 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.856 INFO  [2087-2087][127.0.0.1:39090][POST /api/BackEndVersion]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]
2025-06-16 14:56:27.856 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.864 INFO  [2087-2087][127.0.0.1:39106][POST /api/indoorStatus]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]
2025-06-16 14:56:27.937 ERROR listenfd=6 accept error: Too many open files:24 [nio.c:172:nio_accept]
2025-06-16 14:56:27.938 INFO  [2087-2087][127.0.0.1:39122][POST /api/preview]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]
2025-06-16 14:56:27.938 ERROR connect 172.16.23.27:80 failed! [HttpClient.cpp:216:http_client_connect]
2025-06-16 14:56:27.938 ERROR connect 172.16.23.27:80 failed! [HttpClient.cpp:216:http_client_connect]
2025-06-16 14:56:27.939 INFO  [2087-2087][127.0.0.1:39118][POST /api/panTilt/otaCheck]=>[200 OK] [HttpHandler.cpp:326:onMessageComplete]

拆解:

  • listenfd=6:说明监听 socket 的文件描述符是 6

  • accept error:说明在调用 accept() 时出错了

  • Too many open files:24:系统返回错误号 24,表示文件描述符已达上限

  • nio.c:172:nio_accept:出错的源码位置

查看进程:

进入文件描述符的目录

sudo ls /proc/2640/fd

sudo ls /proc/2640/fd/6

结果返回:

说明这里开始报错。

理解:fd6 是监听 socket,但不是错误的根源,fd6是受害者

是否
listenfd=6 是出错的 FD 吗?❌ 否(它是监听 socket,一直存在)
是谁导致了错误?✅ 是其他 FD 泄漏或资源未释放导致系统 FD 耗尽
错误是不是 accept 本身?✅ 是 accept() 创建新连接时无法分配新的 FD 引发错误

复习一下文件描述符的使用场景:

场景示例函数fd 用法
文件读写open(), read(), write(), close()文件用 fd 表示
Socket 编程socket(), accept(), send(), recv()网络连接也是 fd
管道pipe(), dup(), fork()管道的两端是 fd
事件监听select(), poll(), epoll()所有监听的对象用 fd 表示
标准输入输出stdin=0, stdout=1, stderr=2它们本质就是 fd
文件重定向dup2(1, fd)改变某个 fd 的输出位置

查看一下fd常见信息

/proc/2640$ sudo ls -l ./fd |wc -l
/proc/2640$ sudo lsof -p 2640

返回:

............
Laserbird 2640 root  mem       REG              259,1  1188160  4857522 /usr/lib/aarch64-linux-gnu/libsqlite3.so.0.8.6
Laserbird 2640 root  mem       REG              259,1   146432  4862729 /usr/lib/aarch64-linux-gnu/ld-2.31.so
Laserbird 2640 root    0r      CHR                1,3      0t0        6 /dev/null
Laserbird 2640 root    1w      REG              259,1    42488 15088371 /var/log/laserbird.log
Laserbird 2640 root    2w      REG              259,1    42488 15088371 /var/log/laserbird.log
Laserbird 2640 root    3w      REG              259,1   114668       19 /libhv.20250617.log
Laserbird 2640 root    4u     unix 0xffff3d4fc8bd3c00      0t0    57583 /tmp/unix_socket_path type=STREAM
Laserbird 2640 root    6u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root    7u     IPv4              54674      0t0      TCP *:8089 (LISTEN)
Laserbird 2640 root    8u  a_inode               0,13        0     8947 [eventpoll]
Laserbird 2640 root    9u  a_inode               0,13        0     8947 [eventfd]
Laserbird 2640 root   10u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   11u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   12u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   13u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   14u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   15u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   16u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   17u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   18u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db
Laserbird 2640 root   19u      REG              259,1    16384 10769944 /opt/sqlite3/device_status.db

 问题:大量重复打开了 /opt/sqlite3/device_status.db

好的,去找程序寻找哪里打开了这么多数据库没关。

找到了罪魁祸首:

成功打开了数据库,已经分配了fd,但由于正忙,直接goto到info_reopen,导致fd爆炸。

sqlite3_prepare_v2() 返回 SQLITE_BUSY 时:

  • 连接 db 已经成功打开,文件描述符也分配了。

  • 然而没有 sqlite3_close(db) 就跳到了 info_reopen

  • 每次 goto 后都再次 sqlite3_open,造成新的 fd 分配

  • 导致 文件描述符泄漏,最终 fd 用尽(达到 ulimit -n),数据库彻底打不开。


//更新DEVICE_STATUS的值
bool Database::updateValueByColumn(const char* column, int newValue, const char* attribute) {
    sqlite3* db;
    sqlite3_stmt* stmt;
info_reopen:
    int rc = sqlite3_open(dbName, &db);
    if (rc) {
        hloge("Can't open database: %s",sqlite3_errmsg(db));
        sqlite3_close(db);
        return false;
    }

    std::string sql = "UPDATE DEVICE_STATUS SET " + std::string(column) + " = ? WHERE ATTRIBUTE = ?";
    rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0);
    if (rc != SQLITE_OK) {
        if(rc == SQLITE_BUSY){  // 如果失败原因是数据库忙
            sleep(0.5);  // 等待 0.5 秒
            goto info_reopen;  // 重新尝试打开数据库
        }
        hloge("Failed to prepare statement: %s",sqlite3_errmsg(db));
        sqlite3_close(db);
        return false;
    }

    sqlite3_bind_int(stmt, 1, newValue);
    sqlite3_bind_text(stmt, 2, attribute, -1, SQLITE_STATIC);
    while(rc = sqlite3_step(stmt) !=SQLITE_DONE){
        if(rc == SQLITE_BUSY || rc == 1){
            //hloge("sqlite was busy\n");
            sleep(0.2);
        }
        else{
            sqlite3_finalize(stmt);
            sqlite3_close(db);
            std::cout << "rc:"<<rc<<std::endl;   //这里容易失败
            hloge("update error\n");
            perror("error update");
            return false;
        }
    }

    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return true;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值