fix(libmsg): close fd-reuse race silently killing short-lived connections

worker 收 EOF 失败后过早清除 busy 标记,事件循环对同一 fd 重复投递任务,
迟到的失败标记经 fd 复用落在新连接上被 _reap_failed 误杀(客户端收到空 EOF)。
失败路径改由 _reap_failed 统一清除 busy 并关闭;_handle_client 增加防重复投递守卫。
memdb 短连接压力测试错误率从 5-9% 降至 0。
This commit is contained in:
qing committed 2026-08-17 14:29:37 +00:00
1 parent 910ab8aa38
commit b2ecb8e12b
1 file changed
+7 -4
+7 -4
View File
@@ -331,10 +331,11 @@ private:
try { try {
task.message = pmsg_recv(task.fd, _config.recv_timeout); task.message = pmsg_recv(task.fd, _config.recv_timeout);
} catch (const MsgException&) { } catch (const MsgException&) {
// 不在此清除 busy:fd 保持 busy 直到 _reap_failed 关闭连接。
// 旧实现提前清除 busy,EOF 可读性会让事件循环为同一 fd 重复
// 投递任务,迟到的失败标记经 fd 复用可能落在新连接上误杀之。
std::lock_guard<std::mutex> flk(_failed_mtx); std::lock_guard<std::mutex> flk(_failed_mtx);
_failed.insert(task.fd); _failed.insert(task.fd);
std::lock_guard<std::mutex> blk(_busy_mtx);
_busy.erase(task.fd);
continue; continue;
} }
} }
@@ -354,11 +355,12 @@ private:
failed = true; failed = true;
} }
if (failed) { if (failed) {
// 失败连接交由 _reap_failed 统一清除 busy 并关闭,
// 不提前恢复 select 监听(同 recv 失败路径,防 fd 复用竞态)。
std::lock_guard<std::mutex> flk(_failed_mtx); std::lock_guard<std::mutex> flk(_failed_mtx);
_failed.insert(task.fd); _failed.insert(task.fd);
} } else {
// 清除 busy 标记,恢复 select 监听 // 清除 busy 标记,恢复 select 监听
{
std::lock_guard<std::mutex> lock(_busy_mtx); std::lock_guard<std::mutex> lock(_busy_mtx);
_busy.erase(task.fd); _busy.erase(task.fd);
} }
@@ -528,6 +530,7 @@ private:
// 事件循环线程不阻塞接收,防 Slowloris DoS CWE-400) // 事件循环线程不阻塞接收,防 Slowloris DoS CWE-400)
{ {
std::lock_guard<std::mutex> blk(_busy_mtx); std::lock_guard<std::mutex> blk(_busy_mtx);
if (_busy.count(fd)) return; // 已有在途任务,防同一 fd 重复投递(fd 复用竞态)
_busy.insert(fd); _busy.insert(fd);
} }
{ {