Describe the bug
我们在运行单元测试结束时(以及偶发于线上服务停止时),遇到了进程 Coredump 的问题。崩溃的堆栈指向了 bvar 内部的 CHECK 失败:
F0409 17:44:02.410187 1487207 variable.
cpp:197] Check failed: false
`spatcher_write_latency_percentiles' must
exist
*** Check failure stack trace: ***
注意到这里报错的变量名原本应该是 event_dispatcher_write_latency_percentiles ,但其开头的 8 个字节丢失,被替换成了不可读的乱码。
通过排查 brpc/src/brpc/event_dispatcher.cpp 的代码,我们找到了根本原因。全局的指标指针 g_edisp_read_lantency 和 g_edisp_write_lantency 在 StopAndJoinGlobalDispatchers() 函数中被显式 delete 了,而这个函数是通过 atexit 注册的退出清理钩子:
static void StopAndJoinGlobalDispatchers
() {
// ... join 所有的 epoll 线程 ...
delete g_edisp_read_lantency; //
<--- 问题出在这里
delete g_edisp_write_lantency; //
<--- 问题出在这里
}
尽管业务层面的 epoll 线程已经被 Join,但 bvar 框架自身存在一个后台采样线程( bvar_sampler_collector )。这个线程是一个 Leaky Singleton,伴随进程存活,永远不会被 Join 停止。当主线程通过 atexit 析构并释放这些 LatencyRecorder 时,如果后台采样线程恰好正在并发访问它们,就会发生 Use-After-Free (UAF) 。底层内存分配器(如 tcmalloc)会立刻将释放内存块的前 8 个字节覆写为一个指向空闲链表下一个节点的指针(Free-list pointer)。这直接导致了 bvar 内部的 std::string _name 被破坏(表现为前 8 个字符变成乱码),随后在 bvar::Variable::hide() 查找 Map 时必然失败并触发 CHECK(false) 导致 Coredump。
Versions
1.14.1
OS:
Compiler:
brpc:
protobuf:
Additional context/screenshots
Describe the bug
我们在运行单元测试结束时(以及偶发于线上服务停止时),遇到了进程 Coredump 的问题。崩溃的堆栈指向了 bvar 内部的 CHECK 失败:
注意到这里报错的变量名原本应该是 event_dispatcher_write_latency_percentiles ,但其开头的 8 个字节丢失,被替换成了不可读的乱码。
通过排查 brpc/src/brpc/event_dispatcher.cpp 的代码,我们找到了根本原因。全局的指标指针 g_edisp_read_lantency 和 g_edisp_write_lantency 在 StopAndJoinGlobalDispatchers() 函数中被显式 delete 了,而这个函数是通过 atexit 注册的退出清理钩子:
尽管业务层面的 epoll 线程已经被 Join,但 bvar 框架自身存在一个后台采样线程( bvar_sampler_collector )。这个线程是一个 Leaky Singleton,伴随进程存活,永远不会被 Join 停止。当主线程通过 atexit 析构并释放这些 LatencyRecorder 时,如果后台采样线程恰好正在并发访问它们,就会发生 Use-After-Free (UAF) 。底层内存分配器(如 tcmalloc)会立刻将释放内存块的前 8 个字节覆写为一个指向空闲链表下一个节点的指针(Free-list pointer)。这直接导致了 bvar 内部的 std::string _name 被破坏(表现为前 8 个字符变成乱码),随后在 bvar::Variable::hide() 查找 Map 时必然失败并触发 CHECK(false) 导致 Coredump。
Versions
1.14.1
OS:
Compiler:
brpc:
protobuf:
Additional context/screenshots