RM新时代网站-首页

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

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

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

踩坑rust的partial copy導(dǎo)致metrics丟失

jf_wN0SrCdH ? 來(lái)源: RisingWave中文開(kāi)源社區(qū) ? 2024-01-03 10:02 ? 次閱讀

作者:溫一鳴RisingWaveLabs 內(nèi)核開(kāi)發(fā)工程師

在 RisingWave 的存儲(chǔ)代碼中,我們使用 RAII [1] 的思想來(lái)對(duì) LSM iterator 的 metrics 進(jìn)行監(jiān)控,從而避免在代碼中忘記上報(bào) metrics 而導(dǎo)致 metrics 丟失。在實(shí)現(xiàn)中,我們?cè)O(shè)計(jì)了一個(gè) rust 的 struct MonitoredStateStoreIterStats去收集 LSM iterator 的 metrics,去統(tǒng)計(jì) iterator 中 key 的數(shù)量和長(zhǎng)度,并為這個(gè) struct 實(shí)現(xiàn)了 Drop,在這個(gè) struct 被釋放的時(shí)候把在本地收集的 metrics 上報(bào) prometheus。通過(guò)這種方式,我們不需要在每次 iterator 使用完后都手動(dòng)上報(bào) metrics,從而避免了由于代碼的疏忽導(dǎo)致忘記上報(bào) metrics。

以下是一段簡(jiǎn)化過(guò)的代碼。我們通過(guò)try_stream[2] 這個(gè)宏來(lái)封裝一個(gè) iterator 的 stream 來(lái)收集這個(gè) stream 的 metrics。在返回的 stream 被釋放時(shí),stats 隨著 stream 被釋放,并調(diào)用其 drop 方法來(lái)上報(bào)收集到的 metrics。

pubstructMonitoredStateStoreIter{
inner:S,
stats:MonitoredStateStoreIterStats,
}

structMonitoredStateStoreIterStats{
total_items:usize,
total_size:usize,
storage_metrics:Arc,
}

implMonitoredStateStoreIter{
#[try_stream(ok=StateStoreIterItem,error=StorageError)]
asyncfninto_stream_inner(mutself){
letinner=self.inner;
futures::pin_mut!(inner);
whileletSome((key,value))=inner
.try_next()
.await
.inspect_err(|e|error!("Failedinnext:{:?}",e))?
{
self.stats.total_items+=1;
self.stats.total_size+=key.encoded_len()+value.len();
yield(key,value);
}
}
}

implDropforMonitoredStateStoreIterStats{
fndrop(&mutself){
self.storage_metrics
.iter_item
.observe(self.total_itemsasf64);
self.storage_metrics
.iter_size
.observe(self.total_sizeasf64);
}
}

然而,在使用過(guò)程中,我們遇到了上報(bào)的 metrics 全部為 0 的問(wèn)題。

1最小復(fù)現(xiàn)

由于使用了try_stream宏來(lái)生成 stream,因此我們懷疑在try_stream生成的代碼中有 bug 導(dǎo)致 metrics 丟失。于是我們用 cargo-expand [3] 來(lái)將查看宏生成的代碼。展開(kāi)后的代碼如下:

fninto_stream_inner(
mutself,
)->implStream
>{
::from_generator(staticmove|
mut__task_context:::ResumeTy,
|->::Result<(),?StorageError>{
let():()={
letinner=self.inner;
letmutinner=inner;
#[allow(unused_mut)]
letmutinner=unsafe{
::new_unchecked(
&mutinner,
)
};
whileletSome((key,value))
={
letmut__pinned=inner.try_next();
loop{
iflet::Ready(
result,
)=unsafe{
poll(Pin::as_mut(&mut__pinned),get_context(__task_context))
}{
breakresult;
}
__task_context=(yield::Pending);
}
}?
{
self.stats.total_items+=1;
self.stats.total_size+=key.encoded_len()+value.len();
__task_context=(yield::Ready((
key,
value,
)));
}
};
#[allow(unreachable_code)]
{
return::Ok(());
loop{
__task_context=(yield::Pending);
}
}
})
}

可以看到,try_stream宏生成的代碼中,包含了一個(gè) rust generator 的閉包。閉包中收集和上報(bào) metrics 的邏輯與原代碼基本相同,按照我們對(duì) rust 的理解,仍然不應(yīng)該會(huì)出現(xiàn) metrics 丟失的問(wèn)題。因此我們懷疑是 rust 編譯器中與 generator 相關(guān)的邏輯存在問(wèn)題。在 rust playground 上,我們嘗試了以下代碼來(lái)對(duì)問(wèn)題進(jìn)行復(fù)現(xiàn)。

structStat{
count:usize,
vec:Vec,
}

implDropforStat{
fndrop(&mutself){
println!("count:{}",self.count);
}
}

fnmain(){

letmutstat=Stat{
count:0,
vec:Vec::new(),
};

letmutf=move||{
stat.count+=1;
1
};

println!("num:{}",f());
}

執(zhí)行以后輸出如下。

num:1
count:0

按照預(yù)期,輸出的 num 和 count 應(yīng)該都為1,因?yàn)樵谡{(diào)用閉包 f 時(shí)stat.count += 1被調(diào)用了,但是以上代碼中遇到了和最開(kāi)始同樣的問(wèn)題。因此以上代碼可以作為我們問(wèn)題的一個(gè)最小復(fù)現(xiàn)。

2問(wèn)題分析

對(duì)以上代碼進(jìn)行分析的話,我們看到閉包 f 的代碼中使用了 move,因此在閉包中使用過(guò)的對(duì)象的 ownership 應(yīng)該都會(huì)轉(zhuǎn)移到閉包中。而 struct Stats實(shí)現(xiàn)了Drop,因此Stats是不可以 partial move 的,其必須作為一個(gè)整體被 move 進(jìn)入閉包。而在閉包中執(zhí)行了stats.count += 1,因此 stats 中的 count 應(yīng)該被置為1。但是從程序的輸出可以看到在 stats 被 drop 時(shí),stats 的 count 是 0。

我們嘗試將閉包 f 改為如下代碼,來(lái)顯式的將 stats 的 ownership 給 move 進(jìn)閉包里。

letmutf=move||{
letmutstat=stat;
stat.count+=1;
1
};

輸出恢復(fù)正常。

num:1
count:1

我們?cè)俅螄L試在閉包 f 中使用 stat 中的另一個(gè)字段 vec:

letmutf=move||{
let_=stat.vec.len();
stat.count+=1;
1
};

輸出同樣恢復(fù)正常。

num:1
count:1

可以看到,我們顯式地將 stat 整個(gè) move 進(jìn)閉包,或者在閉包中使用類(lèi)型為 vec 的字段,都會(huì)使得 stat 的ownership 被 move 進(jìn)閉包。

于是我們推測(cè),盡管 stat 實(shí)現(xiàn)了自己的 drop 導(dǎo)致不能被 partial move,但是如果我們?cè)?move 的閉包中只使用了 stat 中實(shí)現(xiàn)了 Copy 類(lèi)型的字段,則這個(gè)字段的值會(huì)被 Copy 到閉包中,而閉包中對(duì)這個(gè)字段的修改只會(huì)作用于被 Copy 后的值,而原字段并不會(huì)改變。

3驗(yàn)證猜想

我們可以通過(guò)將以上代碼編譯成二進(jìn)制代碼后,對(duì)其匯編代碼進(jìn)行分析,從而驗(yàn)證我們的猜想。然而,編譯后的匯編代碼會(huì)過(guò)于復(fù)雜且晦澀難懂,同時(shí)編譯器對(duì)其進(jìn)行的一些優(yōu)化也會(huì)改變?cè)械倪壿媽?dǎo)致匯編代碼難以理解。因此我們打算通過(guò)分析在編譯過(guò)程中產(chǎn)生的 MIR 中間代碼來(lái)對(duì)問(wèn)題進(jìn)行分析。在 rust playground 上可以十分方便地生成 MIR 代碼。

首先我們對(duì)存在問(wèn)題的最小復(fù)現(xiàn)代碼生成 MIR,生成后與閉包相關(guān)的 MIR 如下??梢钥吹竭@個(gè)閉包確實(shí)只包含了一個(gè)類(lèi)型為 usize 的字段,這個(gè)字段的值取的是 stat 中的 count 值。

bb1:{
_1=Stat{count:const0_usize,vec:move_2};
_3={closure@src/main.rs:19:17:19:24}{stat:(_1.0:usize)};
}

而我們對(duì)后續(xù)測(cè)試中有正常輸出的代碼生成 MIR,生成后與閉包相關(guān)的 MIR 如下??梢钥吹竭@個(gè)閉包將整個(gè) stat 的 ownership 給 move 了進(jìn)去。

bb1:{
_1=Stat{count:const0_usize,vec:move_2};
_3={closure@src/main.rs:19:17:19:24}{stat:move_1};
}

于是,我們的猜想得到了驗(yàn)證,在我們出現(xiàn)問(wèn)題的代碼中,閉包確實(shí)沒(méi)有捕獲 stat 的 ownership。

4后續(xù)與總結(jié)

我們向 rust 社區(qū)反映了這個(gè)問(wèn)題,得到的反饋是,這個(gè)是 rust 2021 后實(shí)現(xiàn)的一個(gè) feature [4]。在 rust 2021 中,一個(gè)使用了 move 的閉包在捕獲一個(gè) struct 的時(shí)候,會(huì)盡可能少地去捕獲 struct 中的字段。

如果一個(gè) struct 沒(méi)有實(shí)現(xiàn) Drop,這意味著他里面的字段可以被分開(kāi) move,而閉包只會(huì)捕獲閉包中用到的字段。

如果某個(gè)被閉包使用的字段實(shí)現(xiàn)了 Copy,那他閉包并不會(huì)捕獲這個(gè)字段的 ownership,而是將這個(gè)字段 copy 一份放在閉包中。

如果一個(gè) struct 實(shí)現(xiàn)了 Drop,那他里面的字段只能作為一個(gè)整體被捕獲。但如果閉包只使用了這個(gè)閉包中實(shí)現(xiàn)了 Copy 的字段,那這個(gè)閉包不會(huì)捕獲這個(gè) struct,而是將使用到的字段 copy 一份。

我們的代碼中,正是因?yàn)檫@個(gè)行為,導(dǎo)致我們的代碼產(chǎn)生了歧義,而出現(xiàn)了 metrics 的丟失。

針對(duì)這個(gè)問(wèn)題,我們認(rèn)為有兩個(gè)地方有提升的空間。

首先,try_stream這個(gè)宏的封裝存在一定的問(wèn)題。在使用宏來(lái)聲明代碼中,其暴露出來(lái)的使用方法是通過(guò)調(diào)用一個(gè)方法來(lái)生成 stream,而在調(diào)用方法時(shí),如果參數(shù)是通過(guò) move ownership 的形式傳入的,同時(shí)在生成 stream 的代碼中我們使用了這個(gè)參數(shù),那我們應(yīng)該認(rèn)為這個(gè) stream 包含了這個(gè)參數(shù)的 ownership。然而,由于這個(gè)宏在實(shí)現(xiàn)的時(shí)候使用了閉包,導(dǎo)致這個(gè) stream 并沒(méi)有包含這個(gè)參數(shù)的 ownership,從而導(dǎo)致問(wèn)題。這個(gè)是宏封裝邏輯時(shí)的問(wèn)題。

其次,rust 在語(yǔ)言設(shè)計(jì)上,由于引入了這個(gè)閉包捕獲 ownership 的特殊邏輯,導(dǎo)致會(huì)寫(xiě)出有歧義的代碼。例如,在上述代碼中,很難想象stat.count += 1并沒(méi)有去修改 stat 中的 count 值。我們也向 rust 社區(qū)反映了這個(gè)問(wèn)題 [5]。

最后,回到我們最開(kāi)始的問(wèn)題中。要想解決 metrics 丟失的問(wèn)題,在我們的代碼中,我們只需要做以下修改就能讓代碼正常運(yùn)行[6]。

#[try_stream(ok=StateStoreIterItem,error=StorageError)]
asyncfninto_stream_inner(mutself){
letinner=self.inner;
...
self.stats.total_items+=1;
self.stats.total_size+=key.encoded_len()+value.len();
}

修改為

#[try_stream(ok=StateStoreIterItem,error=StorageError)]
asyncfninto_stream_inner(self){
letinner=self.inner;
letmutstats=self.stats;
...
stats.total_items+=1;
stats.total_size+=key.encoded_len()+value.len();
}

審核編輯:湯梓紅

聲明:本文內(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)注

    3

    文章

    1372

    瀏覽量

    40276
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4779

    瀏覽量

    68521
  • Rust
    +關(guān)注

    關(guān)注

    1

    文章

    228

    瀏覽量

    6601

原文標(biāo)題:踩坑 rust 的 partial copy 導(dǎo)致 metrics 丟失

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

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    開(kāi)發(fā)STM32 USB HID過(guò)的

    記錄一下 開(kāi)發(fā)STM32 USB HID過(guò)的一、前言二、代碼配置一、前言MCU: STM32F103C8T6CubeMX: STM32CubeMX 5.3.0二、代碼配置引腳配置時(shí)鐘樹(shù)配置我
    發(fā)表于 08-24 07:15

    有沒(méi)有關(guān)于STM32入門(mén)經(jīng)驗(yàn)分享

    有沒(méi)有關(guān)于STM32入門(mén)經(jīng)驗(yàn)分享
    發(fā)表于 10-13 06:52

    NodeMCU開(kāi)發(fā)板經(jīng)歷分享

    寫(xiě)在前面今天入手了一個(gè)NodeMCU的板子,準(zhǔn)備學(xué)習(xí)一下物聯(lián)網(wǎng)相關(guān)的知識(shí)。不過(guò)由于博主學(xué)藝不精,在第一步燒寫(xiě)固件上就了,所以就想著把自己的經(jīng)歷寫(xiě)出來(lái)分享給大家,希望能有一些幫助
    發(fā)表于 11-01 07:55

    Linux學(xué)習(xí)過(guò)程過(guò)的與如何解決

    Linux記錄記錄Linux學(xué)習(xí)過(guò)程過(guò)的與如何解決1解決方法:F10進(jìn)入BIOS使能
    發(fā)表于 11-04 08:44

    移植debian系統(tǒng)過(guò)的

    基本的linux系統(tǒng),板子的交叉編譯器是arm-linux-gnueabihf-gcc,這給我?guī)?lái)了不少的麻煩,以至于想重新移植一下debian系統(tǒng)。ok,轉(zhuǎn)入正題,說(shuō)說(shuō)這兩天我吧。首先...
    發(fā)表于 12-14 08:42

    STM32編程常有哪些?

    STM32編程常有哪些?
    發(fā)表于 12-17 06:15

    記錄寫(xiě)SAM4S的bootloader所

    記錄寫(xiě)SAM4S的bootloader所
    發(fā)表于 01-24 07:16

    嵌入式Linux記錄

    Linux記錄記錄Linux學(xué)習(xí)過(guò)程過(guò)的與如何解決1解決方法:F10進(jìn)入BIOS使能
    發(fā)表于 11-01 17:21 ?10次下載
    嵌入式Linux<b class='flag-5'>踩</b><b class='flag-5'>坑</b>記錄

    將NodeMCU接入物聯(lián)網(wǎng)平臺(tái)的之路(阿里云、百度天工)

    將NodeMCU接入物聯(lián)網(wǎng)平臺(tái)的之路(阿里云、百度天工)
    發(fā)表于 11-29 18:06 ?15次下載
    將NodeMCU接入物聯(lián)網(wǎng)平臺(tái)的<b class='flag-5'>踩</b><b class='flag-5'>坑</b>之路(阿里云、百度天工)

    Arduino-IDE配置ESP32-CAM開(kāi)發(fā)環(huán)境過(guò)的那些

    Arduino-IDE配置ESP32-CAM開(kāi)發(fā)環(huán)境過(guò)的那些
    發(fā)表于 11-30 18:36 ?24次下載
    Arduino-IDE配置ESP32-CAM開(kāi)發(fā)環(huán)境<b class='flag-5'>踩</b>過(guò)的那些<b class='flag-5'>坑</b>

    STM32CubeIDE+FREERTOS記錄

    STM32CubeIDE+FREERTOS記錄
    發(fā)表于 12-05 18:06 ?15次下載
    STM32CubeIDE+FREERTOS<b class='flag-5'>踩</b><b class='flag-5'>坑</b>記錄

    搭建D1s RT-Smart開(kāi)發(fā)環(huán)境筆記

    作為一個(gè)linux新手想要嘗試RT-Smart的開(kāi)發(fā),但是網(wǎng)上教程前輩們的linux環(huán)境都是已經(jīng)相對(duì)完備的,因此像我這樣新手在搭建環(huán)境時(shí)常常缺這缺那的導(dǎo)致報(bào)錯(cuò),經(jīng)過(guò)一段時(shí)間的終于搞定了,因此和大家分享我遇到的
    的頭像 發(fā)表于 09-28 16:26 ?819次閱讀
    搭建D1s RT-Smart開(kāi)發(fā)環(huán)境<b class='flag-5'>踩</b><b class='flag-5'>坑</b>筆記

    推挽電路的,你過(guò)沒(méi)?

    推挽電路的,你過(guò)沒(méi)?
    的頭像 發(fā)表于 11-24 16:25 ?1110次閱讀
    推挽電路的<b class='flag-5'>坑</b>,你<b class='flag-5'>踩</b>過(guò)沒(méi)?

    關(guān)于圖像傳感器圖像質(zhì)量的四大誤區(qū)!你過(guò)幾個(gè)?

    關(guān)于圖像傳感器圖像質(zhì)量的四大誤區(qū)!你過(guò)幾個(gè)
    的頭像 發(fā)表于 11-27 16:56 ?445次閱讀
    關(guān)于圖像傳感器圖像質(zhì)量的四大誤區(qū)!你<b class='flag-5'>踩</b>過(guò)幾個(gè)<b class='flag-5'>坑</b>?

    反相輸入放大器的,你過(guò)沒(méi)有?

    反相輸入放大器的,你過(guò)沒(méi)有?
    的頭像 發(fā)表于 12-06 15:35 ?636次閱讀
    反相輸入放大器的<b class='flag-5'>坑</b>,你<b class='flag-5'>踩</b>過(guò)沒(méi)有?
    RM新时代网站-首页