日更系列:谷歌pb结构mutable滥用导致的潜在内存泄漏

2022-01-29 18:22:14 浏览数 (1)

算法通信协议定了一个大pb,然后往pb里面塞入各种特征。这些特征会去不同的redis表查询获取。redis查询不同表,解析返回数据,因为回调函数做成了并发异步,所以不是各个特征请求间不是串行序列化进行,所以会存在一个并发竞争问题。

对于自定义复杂对象非pld类型,protobuf (c ) 使用两种方法设置内嵌class对象的值,分别是set_allocated_ 和 mutable_。这个mutable_如果在非线程安全的环境被滥用,会有潜在的内存泄漏,这个问题比较隐蔽。

假设我们有一个例子:

代码语言:javascript复制
message browser_user_feature
{
     feature_user_download   user_download   = 1;
     feature_query_word  query_word = 2;
}

message UserFeature {
     browser_user_feature  user_feature = 1;
}

然后用户下载数据和query数据是通过不同的redis表获取。然后我们注册回调函数去解析, 这是伪代码

代码语言:javascript复制
register_parse_func("key1", 解析的user_download的回调函数)
register_parse_func("key2", 解析的query_word的回调函数)

解析的user_download的回调函数(data, size) {
    result.mutable_user_feature()->mutable_user_download()->ParseFromString(data, size);

}

解析的query_word的回调函数(data, size) {
    result.mutable_user_feature()->mutable_query_word()->ParseFromString(data, size);
}

那么mutable这个方面内部会进行是否空指针判断,如果是空指针,进行对象内存分配。

代码语言:javascript复制
 inline ::feature_process::browser_user_feature* UserFeature::mutable_browser_search_user_feature() {
 
   if (browser_user_feature_ == nullptr) {
     auto* p = CreateMaybeMessage<::feature_process::browser_user_feature>(GetArenaNoVirtual());
     browser_user_feature_ = p;
   }
   // @@protoc_insertion_point(field_mutable:feature_process.UserFeature.browser_user_feature)
   return browser_user_feature_;
 }

使用内存分析工具:gperftools  https://github.com/gperftools/gperftools,会看到大量的内存分配在这个函数CreateMaybeMessage。这里注意mutable_xxx不是线程安全函数,所以分配的时候,可能会出现分配两次或多次的问题,那么后面即使释放内存也只会释放一次。

那修改这个bug的方法是什么呢?我们要保证在一个不存在线程安全问题的地方先调用一下mutable方法,预分配下内存,后续用到的时候就不会创建新内存。

0 人点赞