Closed lsdh-fei closed 2 weeks ago
@wwbmmm @MJY-HUST 看了下提交记录,不知道是不是 #2645 引入的,辛苦看下哈
thread销毁后也会跟着销毁,不应该无限增长
自己起的线程中使用bthread_keytable吗?
@chenBright 不是,就是rpc处理的bthread中使用的
使用bthread-local的场景是什么样的呢@lsdh-fei 在使用bthread-local变量时,如果create的bthread的addr中没有初始化bthread_keytable_pool_t的话,那么每次return_keytable都会直接析构keytable;另外如果初始化了bthread_keytable_pool_t的话,使用bthread-local变量时需要先调用bthread_getspecific尝试borrow_keytable进行复用,如果每次都是直接bthread_setspecific赋值的话,如果当前bthread不存在keytable的话,就会new一个新的keytable,内存就会不断增长,这个是之前就有的问题
@MJY-HUST 没有单独使用bthread-local,就是task_runner中的,我门最近把brpc从1.8升级到了1.10,然后就发现新版有内存泄漏,LogStream占用了大量内存,看了下brpc源代码,发现log stream的buf是存在keytable中,然后就怀疑是keytable的问题,然后看vars监控,发现keytable的数量一直在不断增长,所以才怀疑是新版的改动导致的
现在观察到的现象是经常会出现 LogStream** get_or_new_tls_stream_array() => bthread_getspecific 无法获取LogStream,然后new了LogStream之后通过 bthread_setspecific 设置到tls_bls.keytable,但是同时在这里new一个KeyTable。所以新增的LogStream和KeyTable的数量是相同的。 应该就是new log stream时创建的keytable泄漏了
我提供一个个人的角度,可以讨论一下。
因为新的keytable的策略,是return到一个pthread 的thread local的list中。但没办法保证的是,同一个keytable被borrow后,会被return到同一个pthread里。
本来其实也没什么问题,只要等待足够时间,每个pthread中,就会create足够的keytable。但是可能,目前的一些task的调度策略,会使得pthread中的keytable数量像波浪一样震荡,使得keytable稳定的总数大大增加。
可以看这么一个例子
void* print_log(void *) {
std::random_device rd;
std::mt19937 mt{rd()};
std::uniform_int_distribution<uint64_t> uid(0, 100000);
bthread_usleep(50000 + uid(mt));
LOG(INFO) << "some log here xxx";
return NULL;
}
class GreeterImpl final : public Greeter {
void SayHello(google::protobuf::RpcController* controller, const HelloRequest* request, HelloReply* response, google::protobuf::Closure* done) override {
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl = static_cast<brpc::Controller*>(controller);
(void)print_log(NULL);
std::string message = "Hello " + request->name() + "!\n";
response->set_message(message);
}
};
先做sleep,再进行LOG(相当于使用keytable),可以看到keytable的数量非常稳定。因为这里,每个pthread里,有一个keytable就可以了。
但我们换一下位置
void* print_log(void *) {
std::random_device rd;
std::mt19937 mt{rd()};
std::uniform_int_distribution<uint64_t> uid(0, 100000);
LOG(INFO) << "some log here xxx";
bthread_usleep(50000 + uid(mt));
return NULL;
}
class GreeterImpl final : public Greeter {
void SayHello(google::protobuf::RpcController* controller, const HelloRequest* request, HelloReply* response, google::protobuf::Closure* done) override {
brpc::ClosureGuard done_guard(done);
brpc::Controller* cntl = static_cast<brpc::Controller*>(controller);
(void)print_log(NULL);
std::string message = "Hello " + request->name() + "!\n";
response->set_message(message);
}
};
可以看到keytable有一个持续的增长。
这里从统计意义上来说,增长可能会一直存在,因为当前worker的task sleep了(或者是等待某个锁),之后它好像会尝试从其他队列steal一个pendding的任务。随着这些任务都sleep(或者都block在某个锁上),这个work所在的pthread的存量keyTable会被持续消耗。而且这些keytable大概率不会被return到这个pthread了。
所以,pthread中的keytables,不是维持在一个较为稳定的状态,而是有着潮水一样的涨落。随着borrow的失败,只能不停的new新的keytable
是的,假设每个bthread都持有keytable的前提下,如果pendding的bthread数量过多(持续增长),那么内存消耗是无法避免的。 如果是因为task调度的不均衡(极端情况下一个task group只执行需要创建/获取keytable的任务,然后yield,这些任务最终在其他的task group中执行完成),那么也会出现内存持续增长的情况。这种情况下的一种解决方法是为每个task group的 tls keytable list的长度设置一个阈值,一旦超过之后加锁批量返回给一个全局链表;而当borrow_keytable失败需要new keytable时,先查看全局链表中是否有keytable,再加锁批量取一批,不直接new keytable。
这个解决方案看上去很棒。 因为现有的borrow_keytable中,已经包含了对thread local 的keytable为空时,落到另一个全局的list(pool->free_keytables)中的逻辑。
看上去,只需要对return_keytable逻辑进行一些调整。
期待大佬们给出修复。
@MJY-HUST 10月节后会发新版本,能赶上新版本修复这个问题吗?
@MJY-HUST 10月节后会发新版本,能赶上新版本修复这个问题吗? 周末修复一下,提个pr
Closed this as completed in #2768.
Describe the bug (描述bug) 升级1.10之后,内存会不断增长,在vars界面查看,发现bthread_keytable_count在不断增长,我理解keytable是thread local的,thread销毁后也会跟着销毁,不应该无限增长
To Reproduce (复现方法) 使用1.10之后就会出现,但是用1.8就不存在泄漏
Expected behavior (期望行为) keytable对象数量维持动态平衡
Versions (各种版本) OS: Ubuntu 20.04.3 LTS Compiler: clang8 brpc: 1.10 protobuf: 3.15.8
Additional context/screenshots (更多上下文/截图)