Rust WebAssembly - ошибка высвобождения памяти пользовательских элементов
Мой первый WASM, созданный в Rust, выдает следующую ошибку, я понятия не имею, как отладить ее.
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, код wasm-bindgen для Rust, вызываемый из пользовательского элемента TypeScript и работающий на холсте в теневом DOM. Данные, отображаемые на холст, поступают из HTML5 AudioBuffer. Все переменные ржавчины локально ограничены.
Веб-компонент работает отлично, если в документе появляется только один экземпляр, но если я продолжу его использовать, трассировка стека будет сброшена, как указано выше. Код работает без каких-либо других проблем.
Я знаю, что в 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"
Код ржавчины невелик и просто отображает два канала 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();
}
// ...
Я попытался закомментировать функциональность, и если код не касается основы, но выполняет вышеуказанные действия, я получаю сообщение об ошибке. Внесение изменений, приведенных ниже, приводит к простой ошибке "Недостаточно памяти". Аудио файл - 1200 кБ.
let channel_data: [Vec<f32>; 2] = [
audio_buffer.get_channel_data(0).unwrap(),
audio_buffer.get_channel_data(1).unwrap()
];
РЕДАКТИРОВАТЬ: последний out of memory
Ошибка, для правильного кода выше, действительно вызвала меня, но это на самом деле ошибка Chrome.
2 ответа
Ваша проблема в том, что вы создаете кусок неинициализированной памяти и не инициализируете его должным образом:
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();
}
Range
с (иначе a..b
) являются эксклюзивными в Rust. Это означает, что ваш цикл не повторяется дважды, как вы предполагаете, а только один раз, и у вас есть один неинициализированный Vec<f32>
который тогда будет паниковать при падении. (Пожалуйста, смотрите ответ Матье М. для правильного объяснения)
Здесь есть несколько возможностей.
- Используйте правильный диапазон, например
0..2
- Используйте инклюзивный диапазон
0..=1
- Не используйте небезопасную конструкцию, но вместо этого
Это правильно инициализирует дваlet mut channel_data: [Vec<f32>; 2] = Default::default()
Vec
s.
Для более полного обзора того, как инициализировать массив, см. Как правильно инициализировать массив фиксированной длины?
В качестве обозначения: избегать использования unsafe
особенно если вы новичок в Rust.
Здесь есть две проблемы:
- Вы создаете неинициализированный кусок памяти и обрабатываете его так, как если бы он был инициализирован.
- Ваша итерация неверна,
0..1
перебирает[0]
(это эксклюзив).
Давайте проверим их по одному.
Не использовать unsafe
,
В общем, вы должны стремиться избегать unsafe
, Существует очень мало причин, чтобы использовать его, и много способов использовать его неправильно (например, здесь).
Проблема.
В этом конкретном случае:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
channel_data[channel_number] = /*...*/;
}
Есть две проблемы:
- Использование
std::mem::uninitialized
не рекомендуется по соображениям безопасности; это очень плохая идея использовать его. Его заменаMaybeUninitialized
, - Присвоение неинициализированной памяти является неопределенным поведением.
В Rust нет оператора присваивания, для выполнения задания язык будет:
- Оставьте предыдущий экземпляр.
- Перезапишите теперь неиспользуемую память.
Отбрасывание сырой памяти, которая думает, что это Vec
является неопределенным поведением; в этом случае вероятным эффектом является то, что некоторое случайное значение указателя считывается и освобождается. Это может привести к сбою, это может освободить несвязанный указатель, что приведет к последнему сбою или повреждению памяти, это ПЛОХО.
Решение.
Есть мало причин для использования unsafe
Вот:
- Вполне возможно безопасно выполнить инициализацию массива.
- Вполне возможно напрямую инициализировать массив.
- Если вы НЕ выполняете инициализацию по умолчанию, вы получаете небольшой выигрыш в производительности, если вы настаиваете на двухэтапной инициализации, так как
Default
реализацияVec
не выделяет память.
Короче:
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()
(а &mut Vec<f32>
) в кортеж (usize, &mut Vec<32>)
:
- У вас есть прямой доступ к элементу, никаких вычислений не требуется.
- У вас также есть индекс элемента, без отдельных ошибок.