0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

记一次Rust内存泄漏排查之旅

jf_wN0SrCdH 来源:GreptimeDB 2023-07-02 11:52 次阅读

在某次持续压测过程中,我们发现 GreptimeDB 的 Frontend 节点内存即使在请求量平稳的阶段也在持续上涨,直至被 OOM kill。我们判断 Frontend 应该是有内存泄漏了,于是开启了排查内存泄漏之旅。

Heap Profiling

大型项目几乎不可能只通过看代码就能找到内存泄漏的地方。所以我们首先要对程序的内存用量做统计分析。幸运的是,GreptimeDB 使用的 jemalloc 自带 heap profiling[1],我们也支持了导出 jemalloc 的 profile dump 文件[2]。于是我们在 GreptimeDB 的 Frontend 节点内存达到 300MB 和 800MB 时,分别 dump 出了其内存 profile 文件,再用 jemalloc 自带的jeprof分析两者内存差异(--base参数),最后用火焰图显示出来:

d60c4194-1826-11ee-962d-dac502259ad0.png

显然图片中间那一大长块就是不断增长的 500MB 内存占用了。仔细观察,居然有 thread 相关的 stack trace。难道是创建了太多线程?简单用ps -T -p命令看了几次 Frontend 节点的进程,线程数稳定在 84 个,而且都是预知的会创建的线程。所以“线程太多”这个原因可以排除。

再继续往下看,我们发现了很多 Tokio runtime 相关的 stack trace,而 Tokio 的 task 泄漏也是常见的一种内存泄漏。这个时候我们就要祭出另一个神器:Tokio-console[3]。

Tokio Console

Tokio Console 是 Tokio 官方的诊断工具,输出结果如下:

d644dd9c-1826-11ee-962d-dac502259ad0.png

我们看到居然有 5559 个正在运行的 task,且绝大多数都是 Idle 状态!于是我们可以确定,内存泄漏发生在 Tokio 的 task 上。现在问题就变成了:GreptimeDB 的代码里,哪里 spawn 了那么多的无法结束的 Tokio task?

从上图的 "Location" 列我们可以看到 task 被 spawn 的地方[4]:

implRuntime{
///Spawn a future and execute it in this thread pool
///
///Similar to Tokio::spawn()
pubfnspawn(&self,future:F)->JoinHandle
where
F:Future+Send+'static,
F:Send+'static,
{
self.handle.spawn(future)
}
}

接下来的任务是找到 GreptimeDB 里所有调用这个方法的代码。

..Default::default()

经过一番看代码的仔细排查,我们终于定位到了 Tokio task 泄漏的地方,并在 PR #1512[5]中修复了这个泄漏。简单地说,就是我们在某个会被经常创建的 struct 的构造方法中,spawn 了一个可以在后台持续运行的 Tokio task,却未能及时回收它。对于资源管理来说,在构造方法中创建 task 本身并不是问题,只要在Drop中能够顺利终止这个 task 即可。而我们的内存泄漏就坏在忽视了这个约定。

这个构造方法同时在该 struct 的Default::default()方法当中被调用了,更增加了我们找到根因的难度。

Rust 有一个很方便的,可以用另一个 struct 来构造自己 struct 的方法,即 "Struct Update Syntax"[6]。如果 struct 实现了Default,我们可以简单地在 struct 的 field 构造中使用..Default::default()。

如果Default::default()内部有 “side effect”(比如我们本次内存泄漏的原因——创建了一个后台运行的 Tokio task),一定要特别注意:struct 构造完成后,Default创建出来的临时 struct 就被丢弃了,一定要做好资源回收

例如下面这个小例子:Rust Playground[7]

structA{
i:i32,
}

implDefaultforA{
fndefault()->Self{
println!("called A::default()");
A{i:42}
}
}

#[derive(Default)]
structB{
a:A,
i:i32,
}

implB{
fnnew(a:A)->Self{
B{
a,
//A::default()is called in B::default(),even though"a"is provided here.
..Default::default()
}
}
}

fnmain(){
leta=A{i:1};
letb=B::new(a);
println!("{}",b.a.i);
}

struct A 的default方法是会被调用的,打印出called A::default()。

总结

•排查 Rust 程序的内存泄漏,我们可以用 jemalloc 的 heap profiling 导出 dump 文件;再生成火焰图可直观展现内存使用情况。

• Tokio-console 可以方便地显示出 Tokio runtime 的 task 运行情况;要特别注意不断增长的 idle tasks。

•尽量不要在常用 struct 的构造方法中留下有副作用的代码。

•Default只应该用于值类型 struct。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    8

    文章

    3016

    浏览量

    73989
  • 文件
    +关注

    关注

    1

    文章

    565

    浏览量

    24722
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19674
  • Rust
    +关注

    关注

    1

    文章

    228

    浏览量

    6593

原文标题:记一次 Rust 内存泄漏排查之旅 | 经验总结篇

文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    【freeRTOS开发笔记】一次坑爹的freeTOS升级

    【freeRTOS开发笔记】一次坑爹的freeTOS-v9.0.0升级到freeRTOS-v10.4.4
    的头像 发表于 07-11 09:15 4619次阅读
    【freeRTOS开发笔记】<b class='flag-5'>记</b><b class='flag-5'>一次</b>坑爹的freeTOS升级

    一次诡异的内存泄漏

    最近在补些基础知识,恰好涉及到了智能指针std::weak_ptr在解决std::shared_ptr时候循环引用的问题
    的头像 发表于 02-19 13:44 642次阅读
    <b class='flag-5'>记</b><b class='flag-5'>一次</b>诡异的<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>

    一次网站设计稿的方法

    一次网站设计稿
    发表于 06-16 09:43

    写了内存泄漏检查工具

    嵌入式环境内存泄漏检查比较麻烦,valgrind比较适合于在pc上跑,嵌入式上首先移植就很麻烦,移植完了内存比较小,跑起来也比较费劲。所以手动写了
    发表于 12-17 08:25

    分享内存泄漏定位排查技巧

    的调试工具,下面分享内存泄漏定位排查技巧。1.对malloc,free进行封装首先,我们对malloc,f
    发表于 12-17 08:13

    sqlite软件包内存泄漏如何解决?

    内存泄漏到底是我应用程序的问题还是软件包本身的问题,该怎么排查呢?硬件使用的nuc980dk61ycvoid app_sqlite3_thread(void *argument){ sqlite3_initialize(); s
    发表于 05-24 15:25

    嵌入式装置内存泄漏检测系统设计

    ,极易出现应用程序内存泄漏内存泄漏按照发生的频率可分为常发性、偶发性、一次性以及隐式内存
    发表于 04-26 14:35 3次下载
    嵌入式装置<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>检测系统设计

    如何处理服务存在内存泄漏问题?

    上周像往常样例行检查线上机器性能,突然发现个服务的内存使用率是这样的: 很显然该服务存在内存泄漏问题,赶紧
    的头像 发表于 03-02 10:23 2036次阅读

    一次性输液器泄漏正负压检测仪

    一次性输液器泄漏正负压测试仪是根据《GB8368-2018一次性使用输液器 重力输液式》中的相关条款设计研发制造的,是款专业用于检测一次
    发表于 01-28 16:44 852次阅读
    <b class='flag-5'>一次</b>性输液器<b class='flag-5'>泄漏</b>正负压检测仪

    一次性输液器泄漏正负压检测仪

    一次性输液器泄漏正负压测试仪是根据《GB8368-2018一次性使用输液器 重力输液式》中的相关条款设计研发制造的,是款专业用于检测一次
    的头像 发表于 01-29 15:30 1136次阅读
    <b class='flag-5'>一次</b>性输液器<b class='flag-5'>泄漏</b>正负压检测仪

    glibc导致的堆外内存泄露的排查过程

    本文记录一次glibc导致的堆外内存泄露的排查过程。
    的头像 发表于 09-01 09:43 706次阅读
    glibc导致的堆外<b class='flag-5'>内存</b>泄露的<b class='flag-5'>排查</b>过程

    什么是内存泄漏?如何避免JavaScript内存泄漏

    JavaScript 代码中常见的内存泄漏的常见来源: 研究内存泄漏问题就相当于寻找符合垃圾回收机制的编程方式,有效避免对象引用的问题。
    发表于 10-27 11:30 396次阅读
    什么是<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>?如何避免JavaScript<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>

    内存泄漏如何避免

    的数,那就是内存溢出。 2. 内存泄漏 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的
    的头像 发表于 11-10 11:04 733次阅读
    <b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>如何避免

    线程内存泄漏问题的定位

    记录个关于线程内存泄漏问题的定位过程,以及过程中的收获。 1. 初步定位 是否存在内存泄漏:想到内存
    的头像 发表于 11-13 11:38 603次阅读
    线程<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题的定位

    一次Rust重写基础软件的实践

    受到2022年“谷歌使用Rust重写Android系统且所有Rust代码的内存安全漏洞为零” [1] 的启发,最近笔者怀着浓厚的兴趣也顺应Rust 的潮流,尝试着将
    的头像 发表于 01-25 11:21 628次阅读