【问题标题】:Rust WebAssembly Custom Elements Memory Deallocation ErrorRust WebAssembly 自定义元素内存释放错误
【发布时间】:2019-04-18 07:56:43
【问题描述】:

我的第一个 Rust 生成的 WASM 产生了以下错误,我不知道如何进行调试。

wasm-000650c2-23:340 Uncaught RuntimeError: memory access out of bounds
    at dlmalloc::dlmalloc::Dlmalloc::free::h36961b6fbcc40c05 (wasm-function[23]:670)
    at __rdl_dealloc (wasm-function[367]:8)
    at __rust_dealloc (wasm-function[360]:7)
    at alloc::alloc::dealloc::h90df92e1f727e726 (wasm-function[146]:100)
    at <alloc::alloc::Global as core::alloc::Alloc>::dealloc::h7f22ab187c7f5835 (wasm-function[194]:84)
    at <alloc::raw_vec::RawVec<T, A>>::dealloc_buffer::hdce29184552be976 (wasm-function[82]:231)
    at <alloc::raw_vec::RawVec<T, A> as core::ops::drop::Drop>::drop::h3910dccc175e44e6 (wasm-function[269]:38)
    at core::ptr::real_drop_in_place::hd26be2408c00ce9d (wasm-function[267]:38)
    at core::ptr::real_drop_in_place::h6acb013dbd13c114 (wasm-function[241]:50)
    at core::ptr::real_drop_in_place::hb270ba635548ab74 (wasm-function[69]:192)

上下文:最新的 Chrome,从 TypeScript 自定义元素调用的 Rust wasm-bindgen 代码,在影子 DOM 中的画布上操作。渲染到画布的数据来自 HTML5 AudioBuffer。所有 rust 变量都是局部作用域的。

如果文档中只出现一个实例,则 Web 组件可以完美运行,但如果我进一步实例,堆栈跟踪将如上所示转储。代码运行没有任何其他问题。

我知道 Chrome 中存在突出的内存错误——它们看起来像这样吗,或者有经验的 rust/wasm 开发人员可以告诉我这是否异常?

js-sys = "0.3.19"
wasm-bindgen = "0.2.42"
wee_alloc = { version = "0.4.2", optional = true }
[dependencies.web-sys]
version = "0.3.4"

rust 代码很小,只是将 AudioBuffer 的两个通道渲染到提供的 HTMLCanvasElement:

#[wasm_bindgen]
pub fn render(
    canvas: web_sys::HtmlCanvasElement,
    audio_buffer: &web_sys::AudioBuffer,
    stroke_style: &JsValue,
    line_width: f64,
    step_size: usize,
) { 
  // ...
    let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() }; // !
    for channel_number in 0..1 {
        channel_data[channel_number] = audio_buffer
            .get_channel_data(channel_number as u32)
            .unwrap();
    }
  // ...

我已经尝试注释掉功能,如果代码没有触及画布但执行了上述操作,我会收到错误消息。进行以下更改会导致简单的“内存不足”错误。音频文件为 1,200 k。

    let channel_data: [Vec<f32>; 2] = [
        audio_buffer.get_channel_data(0).unwrap(),
        audio_buffer.get_channel_data(1).unwrap()
    ];

EDIT:后面的out of memory错误,对于上面的正确代码,真的是给我丢了,但其实是Chrome bug

【问题讨论】:

  • 我希望你明白,为什么minimal reproducible example 很重要 ;) 我很高兴我能够解决你的问题。我必须承认,我们(Rust 标签“成员”)有时非常渴望 MCVE,但它有很大帮助!
  • 感谢您帮助我找出这个荒谬案例中的最小示例!我现在只收到内存不足错误,但这是另一个问题。
  • 事实上,“内存不足”错误是因为我昨天看到但忘记了的 Chrome 错误:stackoverflow.com/questions/55039923/…

标签: typescript rust shadow-dom wasm-bindgen


【解决方案1】:

您的问题是您创建了一块未初始化的内存并且没有正确初始化它:

let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in 0..1 {
    channel_data[channel_number] = audio_buffer
        .get_channel_data(channel_number as u32) // no need for `as u32` here btw
        .unwrap();
}

Ranges (a.k.a. a..b) 在 Rust 中是专有的。这意味着您的循环不会像您想象的那样迭代两次,而是只迭代一次,并且您有一个未初始化的Vec&lt;f32&gt; 然后在丢弃它时会恐慌。(请参阅Matthieu M.'s answer 以获得正确的解释)

这里有几种可能性。

  1. 使用正确的范围,例如0..2
  2. 使用inclusive range 0..=1
  3. 不要使用不安全的构造,而是
    let mut channel_data: [Vec<f32>; 2] = Default::default()
    
    这将正确初始化两个Vecs。

有关如何初始化数组的更完整概述,请参阅What is the proper way to initialize a fixed length array?

附带说明:避免使用 unsafe,尤其是如果您是 Rust 新手。

【讨论】:

  • 我认为推荐一个 proper 迭代会更好。不要使用范围,使用迭代+enumerate(),保证迭代所有。
  • @MatthieuM。随意添加第四个选项。
  • 实际上,诊断中有一个错误:问题不是让一个元素未初始化,而是尝试分配给第一个元素会导致未初始化的内存下降。我已决定发布自己的答案,而不是大量编辑您的答案。
【解决方案2】:

这里有两个问题:

  1. 您创建了一块未初始化的内存,并将其视为已初始化。
  2. 你的迭代是错误的,0..1 迭代了[0](它是独占的)。

让我们一次检查一个。


不要使用unsafe

一般来说,你应该尽量避免unsafe。使用它的理由很少,错误使用的方法很多(比如这里)。

问题。

在这种特殊情况下:

let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
    channel_data[channel_number] = /*...*/;
}

有两个问题:

  1. 出于安全原因,不推荐使用std::mem::uninitialized;使用它是一个非常糟糕的主意。它的替换是MaybeUninitialized
  2. 分配给未初始化的内存是未定义的行为。

Rust 中没有赋值运算符,为了执行赋值,该语言将:

  • 删除前一个实例。
  • 覆盖现在未使用的内存。

丢弃认为是Vec 的原始内存是未定义的行为;在这种情况下,可能的效果是读取并释放了一些随机指针值。这可能会崩溃,这可能会释放不相关的指针,从而导致后来的崩溃或内存损坏,这是BAD

解决方案。

没有什么理由在这里使用unsafe

  • 完全可以安全地执行数组的初始化。
  • 完全可以直接初始化数组。
  • 如果您坚持两步初始化,则不执行默认初始化几乎没有性能优势,因为VecDefault 实现不分配内存。

简而言之:

auto create_channel = |channel_number: u32| {
    audio_buffer
        .get_channel_data(channel_number)
        .unwrap()
};

let mut channel_data = [create_channel(0), create_channel(1)];

简单、安全、高效。


更喜欢迭代器而不是索引。

如果您坚持两步初始化,则使用迭代器而不是索引来避免一次错误。

在你的情况下:

let mut channel_data = [vec!(), vec!()];
for (channel_number, channel) = channel_data.iter_mut().enumerate() {
    *channel = audio_buffer
        .get_channel_data(channel_number as u32)
        .unwrap();
}

Iterator 上有很多实用函数,在这种特殊情况下,enumerate 会将iter_mut()(一个&amp;mut Vec&lt;f32&gt;)返回的项目包装成一个元组(usize, &amp;mut Vec&lt;32&gt;)

  • 您可以直接访问元素,无需计算。
  • 您还拥有元素的索引,不会出现差一错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 2017-03-22
    • 2017-05-20
    相关资源
    最近更新 更多