cannot allocate memory in static TLS block问题记录

一、背景

最近发了一个新版本,服务升级后,加载动态库时候抛出了一个很奇怪的报错
“cannot allocate memory in static TLS block”,导致服务启动失败,但是在线下测试是没任何问题的。在解决问题后,做个小结

By the way,最近又遇到一个服加载动态库时候抛出
“cannot allocate memory in static TLS block”,原因和之前又不太相同,更新下。

二、TLS

2.1 定义

TLS:全程是Thread Local Storage,即线程局部存储:在多线程中,存储和维护一些线程相关的数据,即线程之间隔离,因此也就没有多线程之间资源竞争问题。目的:因为数据线程局部,不需要锁来保护,可以利用这特性达到优化性能的目的。

2.2 实现方式

  1. gcc、clang等编译器可以通过修饰符__thread来声明实现。该方式很简单,本身需要编译器支持就可以,直接用__thread加在static或全局变量前即可,线程局部读取和设置。比如:
void thread_func()
{
    // 定义一个线程局部变量
    static __thread int a = 0;
        
    // 初始化变量为当前线程id
    if (!a) a = tb_thread_self();
}
  1. pthread库pthread_setspecific和pthread_getspecific接口
    ptherad库通过接口声明变量以及支持注册free函数,那么线程推出的时候,可以自动释放TLS内存
  2. windows下msvc的__declspec(thread)修饰符,和第一种方式类似

三、问题排查

3.1 一些思路

  1. 开始怀疑是机器堆栈内存不足,ulimti -a
    stack size (kbytes, -s) 8192
    因为之前机器装机模版是10240,改了之后还是不行
  2. 后来突然想到一个点,线上其实和线下使用方式不同,线上是通过dlopen使用的,感觉和这个是否有关

3.2 dlopen

#include <dlfcn.h>
// 功能: 以指定模式打开指定的动态链接库文件,并返回一个句柄给dlsym()的调用进程。
void *dlopen(const char *filename, int flag);
// 使用dlclose()来卸载打开的库。
int dlclose(void *handle);

3.3 原因

怀疑新版本TLS增大,导致dlopen失败了。开始去代码库中看最近的git提交,发现一个同学提交很有疑惑点:静态编译了jemalloc,版本为5.2以上。查询jemalloc的相关文档:找到如下

For performance reasons jemalloc 5 uses more TLS space. This causes isses for dlopen jemalloc where the pre allocated TLS may not be enough for jemalloc. The option --disable-initial-exec-tls in INSTALL.md should solve it for you

因此找相关同学修改编译方式,验证OK

3.4 最近dlopen失败原因

因为之前的排查经验,一开始就在怀疑目前一些第三方依赖(开始类似TLS优化)导致,而且最近确实很多依赖在升级和变更,在比对了很多库,找到了一些怀疑的地方,比如Libunwind(llvm依赖库),如果编译时开启"enable-per-thread-cache",那么dlopen加载动态库也会抛出"cannot allocate memory in static TLS block".参考类似文档:

I just noticed that one cannot dlopen() libunwind when it is build with –
enable-per-thread-cache. In such scenarios, dlopen will always fail with
“cannot allocate memory in static TLS block”.

参考Libunwind

但是确认了其实目前很多依赖并没有开启上述优化,同时依赖的库较多,因此此次排查没有一个很好的思路。
在和一个同事讨论该问题后,重新在代码里找到一个如下实现。类似如下:

static thread_local ClassA* attribute((tls_model(“initial-exec”))) ctx = nullptr;

即在声明一个线程局部的变量,同时指定了tls_mode为initial-exec, 通过查询一些资料,确认确实这里会导致dlopen加载失败,抛出"cannot allocate memory in static TLS block".
原因如下:
tls_mode属性提供了一种在代码层面,控制thread-local storage变量的行为,指定tls_model属性允许链接器来检查是否使用了正确的线程模型来构建应用程序或共享库,
行为如下:
在这里插入图片描述
因为指定initial-exec,同时编译环境最后产生的其中一个动态库使用dlopen会异常。解决方式:去除tls属性或者修改tls属性(local-dynamic),不过需要关注是否会对性能有影响,本身指定tls_mode为initial-exec,是为了更快的寻址,以及优化高频结构访问的性能,因此可以支持不同的编译选项,在需要开启的时候再指定。

其他一些思路:

  1. dlopen修改支持更大的TLS,参考:
    https://stackoverflow.com/questions/45640573/gulp-node-error-while-loading-shared-libraries-cannot-allocate-memory-in-stati
  2. ftls-model,参考:
    https://scc.ustc.edu.cn/zlsc/sugon/intel/compiler_c/main_cls/copts/ccpp_options/option_ftls_model.htm
  3. IBM The tls_model attribute
  4. tls_mode
  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值