0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

記一次Rust內(nèi)存泄漏排查之旅

jf_wN0SrCdH ? 來(lái)源:GreptimeDB ? 2023-07-02 11:52 ? 次閱讀

在某次持續(xù)壓測(cè)過(guò)程中,我們發(fā)現(xiàn) GreptimeDB 的 Frontend 節(jié)點(diǎn)內(nèi)存即使在請(qǐng)求量平穩(wěn)的階段也在持續(xù)上漲,直至被 OOM kill。我們判斷 Frontend 應(yīng)該是有內(nèi)存泄漏了,于是開(kāi)啟了排查內(nèi)存泄漏之旅。

Heap Profiling

大型項(xiàng)目幾乎不可能只通過(guò)看代碼就能找到內(nèi)存泄漏的地方。所以我們首先要對(duì)程序的內(nèi)存用量做統(tǒng)計(jì)分析。幸運(yùn)的是,GreptimeDB 使用的 jemalloc 自帶 heap profiling[1],我們也支持了導(dǎo)出 jemalloc 的 profile dump 文件[2]。于是我們?cè)?GreptimeDB 的 Frontend 節(jié)點(diǎn)內(nèi)存達(dá)到 300MB 和 800MB 時(shí),分別 dump 出了其內(nèi)存 profile 文件,再用 jemalloc 自帶的jeprof分析兩者內(nèi)存差異(--base參數(shù)),最后用火焰圖顯示出來(lái):

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

顯然圖片中間那一大長(zhǎng)塊就是不斷增長(zhǎng)的 500MB 內(nèi)存占用了。仔細(xì)觀察,居然有 thread 相關(guān)的 stack trace。難道是創(chuàng)建了太多線(xiàn)程?簡(jiǎn)單用ps -T -p命令看了幾次 Frontend 節(jié)點(diǎn)的進(jìn)程,線(xiàn)程數(shù)穩(wěn)定在 84 個(gè),而且都是預(yù)知的會(huì)創(chuàng)建的線(xiàn)程。所以“線(xiàn)程太多”這個(gè)原因可以排除。

再繼續(xù)往下看,我們發(fā)現(xiàn)了很多 Tokio runtime 相關(guān)的 stack trace,而 Tokio 的 task 泄漏也是常見(jiàn)的一種內(nèi)存泄漏。這個(gè)時(shí)候我們就要祭出另一個(gè)神器:Tokio-console[3]。

Tokio Console

Tokio Console 是 Tokio 官方的診斷工具,輸出結(jié)果如下:

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

我們看到居然有 5559 個(gè)正在運(yùn)行的 task,且絕大多數(shù)都是 Idle 狀態(tài)!于是我們可以確定,內(nèi)存泄漏發(fā)生在 Tokio 的 task 上?,F(xiàn)在問(wèn)題就變成了:GreptimeDB 的代碼里,哪里 spawn 了那么多的無(wú)法結(jié)束的 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)
}
}

接下來(lái)的任務(wù)是找到 GreptimeDB 里所有調(diào)用這個(gè)方法的代碼。

..Default::default()

經(jīng)過(guò)一番看代碼的仔細(xì)排查,我們終于定位到了 Tokio task 泄漏的地方,并在 PR #1512[5]中修復(fù)了這個(gè)泄漏。簡(jiǎn)單地說(shuō),就是我們?cè)?strong>某個(gè)會(huì)被經(jīng)常創(chuàng)建的 struct 的構(gòu)造方法中,spawn 了一個(gè)可以在后臺(tái)持續(xù)運(yùn)行的 Tokio task,卻未能及時(shí)回收它。對(duì)于資源管理來(lái)說(shuō),在構(gòu)造方法中創(chuàng)建 task 本身并不是問(wèn)題,只要在Drop中能夠順利終止這個(gè) task 即可。而我們的內(nèi)存泄漏就壞在忽視了這個(gè)約定。

這個(gè)構(gòu)造方法同時(shí)在該 struct 的Default::default()方法當(dāng)中被調(diào)用了,更增加了我們找到根因的難度。

Rust 有一個(gè)很方便的,可以用另一個(gè) struct 來(lái)構(gòu)造自己 struct 的方法,即 "Struct Update Syntax"[6]。如果 struct 實(shí)現(xiàn)了Default,我們可以簡(jiǎn)單地在 struct 的 field 構(gòu)造中使用..Default::default()。

如果Default::default()內(nèi)部有 “side effect”(比如我們本次內(nèi)存泄漏的原因——?jiǎng)?chuàng)建了一個(gè)后臺(tái)運(yùn)行的 Tokio task),一定要特別注意:struct 構(gòu)造完成后,Default創(chuàng)建出來(lái)的臨時(shí) struct 就被丟棄了,一定要做好資源回收。

例如下面這個(gè)小例子: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方法是會(huì)被調(diào)用的,打印出called A::default()。

總結(jié)

?排查 Rust 程序的內(nèi)存泄漏,我們可以用 jemalloc 的 heap profiling 導(dǎo)出 dump 文件;再生成火焰圖可直觀展現(xiàn)內(nèi)存使用情況。

? Tokio-console 可以方便地顯示出 Tokio runtime 的 task 運(yùn)行情況;要特別注意不斷增長(zhǎng)的 idle tasks。

?盡量不要在常用 struct 的構(gòu)造方法中留下有副作用的代碼。

?Default只應(yīng)該用于值類(lèi)型 struct。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3025

    瀏覽量

    74055
  • 文件
    +關(guān)注

    關(guān)注

    1

    文章

    566

    瀏覽量

    24746
  • 線(xiàn)程
    +關(guān)注

    關(guān)注

    0

    文章

    504

    瀏覽量

    19684
  • Rust
    +關(guān)注

    關(guān)注

    1

    文章

    228

    瀏覽量

    6610

原文標(biāo)題:記一次 Rust 內(nèi)存泄漏排查之旅 | 經(jīng)驗(yàn)總結(jié)篇

文章出處:【微信號(hào):Rust語(yǔ)言中文社區(qū),微信公眾號(hào):Rust語(yǔ)言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    【freeRTOS開(kāi)發(fā)筆記】一次坑爹的freeTOS升級(jí)

    【freeRTOS開(kāi)發(fā)筆記】一次坑爹的freeTOS-v9.0.0升級(jí)到freeRTOS-v10.4.4
    的頭像 發(fā)表于 07-11 09:15 ?4644次閱讀
    【freeRTOS開(kāi)發(fā)筆記】<b class='flag-5'>記</b><b class='flag-5'>一次</b>坑爹的freeTOS升級(jí)

    一次詭異的內(nèi)存泄漏

    最近在補(bǔ)些基礎(chǔ)知識(shí),恰好涉及到了智能指針std::weak_ptr在解決std::shared_ptr時(shí)候循環(huán)引用的問(wèn)題
    的頭像 發(fā)表于 02-19 13:44 ?663次閱讀
    <b class='flag-5'>記</b><b class='flag-5'>一次</b>詭異的<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>

    一次網(wǎng)站設(shè)計(jì)稿的方法

    一次網(wǎng)站設(shè)計(jì)稿
    發(fā)表于 06-16 09:43

    寫(xiě)了個(gè)內(nèi)存泄漏檢查工具

    嵌入式環(huán)境內(nèi)存泄漏檢查比較麻煩,valgrind比較適合于在pc上跑,嵌入式上首先移植就很麻煩,移植完了內(nèi)存比較小,跑起來(lái)也比較費(fèi)勁。所以手動(dòng)寫(xiě)了個(gè)
    發(fā)表于 12-17 08:25

    分享內(nèi)存泄漏定位排查技巧

    的調(diào)試工具,下面分享內(nèi)存泄漏定位排查技巧。1.對(duì)malloc,free進(jìn)行封裝首先,我們對(duì)malloc,f
    發(fā)表于 12-17 08:13

    sqlite軟件包內(nèi)存泄漏如何解決?

    內(nèi)存泄漏到底是我應(yīng)用程序的問(wèn)題還是軟件包本身的問(wèn)題,該怎么排查呢?硬件使用的nuc980dk61ycvoid app_sqlite3_thread(void *argument){ sqlite3_initialize(); s
    發(fā)表于 05-24 15:25

    嵌入式裝置內(nèi)存泄漏檢測(cè)系統(tǒng)設(shè)計(jì)

    ,極易出現(xiàn)應(yīng)用程序內(nèi)存泄漏。內(nèi)存泄漏按照發(fā)生的頻率可分為常發(fā)性、偶發(fā)性、一次性以及隱式內(nèi)存
    發(fā)表于 04-26 14:35 ?3次下載
    嵌入式裝置<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>檢測(cè)系統(tǒng)設(shè)計(jì)

    如何處理服務(wù)存在內(nèi)存泄漏問(wèn)題?

    上周像往常樣例行檢查線(xiàn)上機(jī)器性能,突然發(fā)現(xiàn)個(gè)服務(wù)的內(nèi)存使用率是這樣的: 很顯然該服務(wù)存在內(nèi)存泄漏問(wèn)題,趕緊
    的頭像 發(fā)表于 03-02 10:23 ?2058次閱讀

    一次性輸液器泄漏正負(fù)壓檢測(cè)儀

    一次性輸液器泄漏正負(fù)壓測(cè)試儀是根據(jù)《GB8368-2018一次性使用輸液器 重力輸液式》中的相關(guān)條款設(shè)計(jì)研發(fā)制造的,是款專(zhuān)業(yè)用于檢測(cè)一次
    發(fā)表于 01-28 16:44 ?860次閱讀
    <b class='flag-5'>一次</b>性輸液器<b class='flag-5'>泄漏</b>正負(fù)壓檢測(cè)儀

    一次性輸液器泄漏正負(fù)壓檢測(cè)儀

    一次性輸液器泄漏正負(fù)壓測(cè)試儀是根據(jù)《GB8368-2018一次性使用輸液器 重力輸液式》中的相關(guān)條款設(shè)計(jì)研發(fā)制造的,是款專(zhuān)業(yè)用于檢測(cè)一次
    的頭像 發(fā)表于 01-29 15:30 ?1146次閱讀
    <b class='flag-5'>一次</b>性輸液器<b class='flag-5'>泄漏</b>正負(fù)壓檢測(cè)儀

    glibc導(dǎo)致的堆外內(nèi)存泄露的排查過(guò)程

    本文記錄一次glibc導(dǎo)致的堆外內(nèi)存泄露的排查過(guò)程。
    的頭像 發(fā)表于 09-01 09:43 ?722次閱讀
    glibc導(dǎo)致的堆外<b class='flag-5'>內(nèi)存</b>泄露的<b class='flag-5'>排查</b>過(guò)程

    什么是內(nèi)存泄漏?如何避免JavaScript內(nèi)存泄漏

    JavaScript 代碼中常見(jiàn)的內(nèi)存泄漏的常見(jiàn)來(lái)源: 研究內(nèi)存泄漏問(wèn)題就相當(dāng)于尋找符合垃圾回收機(jī)制的編程方式,有效避免對(duì)象引用的問(wèn)題。
    發(fā)表于 10-27 11:30 ?399次閱讀
    什么是<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>?如何避免JavaScript<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>

    內(nèi)存泄漏如何避免

    的數(shù),那就是內(nèi)存溢出。 2. 內(nèi)存泄漏 內(nèi)存泄露 memory leak,是指程序在申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的
    的頭像 發(fā)表于 11-10 11:04 ?749次閱讀
    <b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>如何避免

    線(xiàn)程內(nèi)存泄漏問(wèn)題的定位

    記錄個(gè)關(guān)于線(xiàn)程內(nèi)存泄漏問(wèn)題的定位過(guò)程,以及過(guò)程中的收獲。 1. 初步定位 是否存在內(nèi)存泄漏:想到內(nèi)存
    的頭像 發(fā)表于 11-13 11:38 ?619次閱讀
    線(xiàn)程<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄漏</b>問(wèn)題的定位

    一次Rust重寫(xiě)基礎(chǔ)軟件的實(shí)踐

    受到2022年“谷歌使用Rust重寫(xiě)Android系統(tǒng)且所有Rust代碼的內(nèi)存安全漏洞為零” [1] 的啟發(fā),最近筆者懷著濃厚的興趣也順應(yīng)Rust 的潮流,嘗試著將
    的頭像 發(fā)表于 01-25 11:21 ?642次閱讀