brpc icon indicating copy to clipboard operation
brpc copied to clipboard

用户注册的bthread local变量析构函数触发bthread切换引起coredump

Open zhaodongzhi opened this issue 3 years ago • 0 comments

Describe the bug (描述bug)

问题1

image 使用了bthread local会以一定概率出现coredump,并且core的栈不固定,但大多在bthread::TaskGroup::sched_to的 errno = saved_errno;,分析后发现是由于注册的bthread local析构函数调用了bthread_mutex_lock会引发bthread local析构的时候出发bthread切换导致的,如下

image

  1. 假设bthread1在pthread1上调用return_keytable最终会调用用户注册的bthread local析构函数,如果这个析构函数调用了bthread_mutex_lock等会导致bthread切换的函数则会使得执行return_keytable之后bthread1切换到另一个线程pthread2上
  2. 由于g还是用的pthread1上的thread local tls_task_group,在pthread2调用g->set_remained(TaskGroup::_release_last_context, m);会使得pthread1提前将bthread1的stack释放掉,如果此时另一个bthread2申请到了这个stack则会导致bthread1和bthread2共用了同一个stack,会导致互相踩栈引发coredump

问题1修复

image 可以移动g = tls_task_group到bthread local析构函数之后的位置修复

问题2

应用上述修复后会遇到第二个问题,是由于gcc编译优化thread local访问方式结合bthread跨线程调度导致的 image bthread meta中记录的local_storage.keytable是正确的但是tls_bls.keytable被设置为null导致一个运行过程中的bthread突然获取不到这个bthread之前注册过的bthread local变量引起coredump image

image

image

可以看到编译优化后将tls_bls的地址保存在了寄存器r15中

我们知道如果用户注册的bthread local析构函数如果存在引起bthread切换的调用,return_keytable可能会触发bthread从pthread1切换到pthread2上,这里由于gcc将tls_bls的地址保存在r15寄存器的优化会导致在pthread2执行 tls_bls.keytable = NULL; 这个操作实际上是将pthread1的tls_bls.keytable设置为null了,而此时pthread1上运行的bthread2可能设置过tls_bls.keytable是不为null的,也就导致了我们上面看到的现象,pthread1上的bthread2获取不到自己设置的bthread local变量了

问题2修复

这个问题使用原子变量或memory fence也解决不了,因为这两者对gcc来说只能组织缓存值的优化不能阻止缓存地址的优化 可以将 KeyTable* kt = tls_bls.keytable; 修改为 KeyTable* kt = m->local_storage.keytable 判定上是等价的,但是可以先避开gcc 缓存tls_bls地址的优化

也可以考虑将thread local关键字声明的tls_bls用pthread_getspecific函数api替代,因为gcc无法对通过pthread_getspecific获取到的thread local做缓存优化,不过这样改的会有点多

To Reproduce (复现方法)

自己写一个注册bthread local 析构函数中调用bthread_mutex_lock的代码多线程并发启动,停止bthread以触发bthread_local析构

Expected behavior (期望行为)

Versions (各种版本) OS: Compiler: gcc 4.8.5 brpc: v1.0.0 protobuf:

Additional context/screenshots (更多上下文/截图)

zhaodongzhi avatar Jun 06 '22 03:06 zhaodongzhi