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

    文章

    2767

    浏览量

    72782
  • 文件
    +关注

    关注

    1

    文章

    540

    浏览量

    24402
  • 线程
    +关注

    关注

    0

    文章

    490

    浏览量

    19503
  • Rust
    +关注

    关注

    1

    文章

    223

    浏览量

    6388

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

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

收藏 人收藏

    评论

    相关推荐

    一次读取大数据占用内存很大

    本帖最后由 elecfans跑堂 于 2015-8-31 10:50 编辑 在编写个读取wav文件的程序,发现一次读取40M以上的文件,消耗内存1G左右,太吓人了,请高手指点啊http
    发表于 08-31 09:40

    AliOS Things 维测典型案例分析 —— 内存泄漏

    个已经压测出来的问题出发,通过维测工具的使用,来看一次内存泄漏的分析。1. 问题现象:xx平台压测反复断AP电源第488连接通道时出现dump机现象**2. 重现步骤: 设备认证连接
    发表于 10-17 11:29

    一次网站设计稿的方法

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

    内存泄漏定位该如何去实现呢

    。对于内存泄漏的情况,如果开始不做预防,定位内存泄漏就会相当繁琐,定位也会很长,非常的耗时、耗力。这里可通过malloc、free的第二
    发表于 12-17 07:24

    写了内存泄漏检查工具

    嵌入式环境内存泄漏检查比较麻烦,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

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

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

    什么是内存泄漏内存泄漏有哪些现象

    内存泄漏几乎是很难避免的,不管是老手还是新手,都存在这个问题,甚至 Windows 与 Linux 这类系统软件也或多或少存在着内存泄漏
    的头像 发表于 09-05 17:24 8960次阅读

    怎么解决C语言中的内存泄漏问题呢?

    只有在堆内存里面才会发生内存泄漏的问题,在栈内存中不会发生内存泄漏。因为栈
    发表于 06-11 17:31 392次阅读
    怎么解决C语言中的<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题呢?

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

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

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

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

    线程内存泄漏问题的定位

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

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

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

    C语言内存泄漏问题原理

    内存泄漏问题只有在使用堆内存的时候才会出现,栈内存不存在内存泄漏问题,因为栈
    发表于 03-19 11:38 174次阅读
    C语言<b class='flag-5'>内存</b><b class='flag-5'>泄漏</b>问题原理