问题背景
在 OceanBase 开源代码中,有这样一段代码,它会导致在系统退出时发生 coredump:
oceanbase::sql::ObSQLSessionInfo &session()
{
static oceanbase::sql::ObSQLSessionInfo SESSION;
return SESSION;
}
ObArenaAllocator &session_alloc()
{
static ObArenaAllocator SESSION_ALLOC;
return SESSION_ALLOC;
}
int ObTableApiProcessorBase::init_session()
{
int ret = OB_SUCCESS;
static const uint32_t sess_version = 0;
static const uint32_t sess_id = 1;
static const uint64_t proxy_sess_id = 1;
if (OB_FAIL(session().test_init(sess_version, sess_id, proxy_sess_id, &session_alloc()))) {
LOG_WARN("init session failed", K(ret));
}
// more ...
return ret;
}
利用 ASAN 诊断发现,静态对象 SESSION 析构时会引用一个 SESSION_ALLOC,而 SESSION_ALLOC 也是一个静态对象,当 SESSION_ALLOC 先于 SESSION 析构时,SESSION 析构时就会访问到非法内存(因为 SESSION_ALLOC 已经析构)。
C 语言中,对于 static 变量,析构规则是:先构造者后析构 可以用下面的程序来验证:
[xiaochu.yh ~] $cat test_destroy.cpp
// Copyright 1999-2021 Alibaba Inc. All Rights Reserved.
// Author:
// xiaochu.yh@alipay.com
//
// test static variable destory order
#include <iostream>
using namespace std;
class A
{
public:
A() { cout << "construct A" << endl; }
~A() { cout << "deconstruct A" << endl; }
void init() {}
};
class B
{
public:
B() { cout << "construct B" << endl; }
~B() { cout << "deconstruct B" << endl; }
void init(A &a) { a.init(); }
};
A &getA() {
static A a;
return a;
}
B &getB() {
static B b;
return b;
}
void func()
{
getB().init(getA());
}
int main(int argc, const char *argv[])
{
func();
return 0;
}
[xiaochu.yh ~] $g++ test_destroy.cpp -o test_destroy
[xiaochu.yh ~] $./test_destroy
construct A
construct B
deconstruct B
deconstruct A
修复方法
既然先构造者后析构 ,那么我们可以在 SESSION 构造之前,主动调用一次 session_alloc,使得 SESSION_ALLOC 先构造即可。
代码如下:
oceanbase::sql::ObSQLSessionInfo &session()
{
static oceanbase::sql::ObSQLSessionInfo SESSION;
return SESSION;
}
ObArenaAllocator &session_alloc()
{
static ObArenaAllocator SESSION_ALLOC;
return SESSION_ALLOC;
}
int ObTableApiProcessorBase::init_session()
{
int ret = OB_SUCCESS;
static const uint32_t sess_version = 0;
static const uint32_t sess_id = 1;
static const uint64_t proxy_sess_id = 1;
// ensure allocator is constructed before session to
// avoid coredump at observer exit
ObArenaAllocator &dummy_allocator = session_alloc();
UNUSED(dummy_allocator);
if (OB_FAIL(session().test_init(sess_version, sess_id, proxy_sess_id, &session_alloc()))) {
LOG_WARN("init session failed", K(ret));
}
// more ...
return ret;
}