我在使用 Rust 和 WebAssembly 进行开发时遇到了僵局。
由于使用了一些全局访问的变量,我选择了lazy_static一个Mutex(使用thread_local回调会导致嵌套问题)。我已经声明 JavaScript 通过#[wasm_bindgen]. 他们读取和写入lazy_static变量。
其中一个函数发生恐慌后,互斥锁将无法释放,从而导致其他函数在需要使用相同互斥量时发生恐慌。
我知道恐慌问题是意外的并且需要修复,但是这些功能彼此相对独立。虽然变量的读写lazy_static有交叉,但某个bug不一定会影响其他部分。
如何在 Wasm 发生恐慌后触发释放Mutex以允许其他调用正常?对于此类问题有没有更好的做法呢?
锈:
use std::sync::Mutex;
use std::sync::PoisonError;
use wasm_bindgen::prelude::*;
pub struct CurrentStatus {
pub index: i32,
}
impl CurrentStatus {
fn new() -> Self {
CurrentStatus { index: 1 }
}
fn get_index(&mut self) -> i32 {
self.index += 1;
self.index.clone()
}
fn add_index(&mut self) {
self.index += 2;
}
}
lazy_static! {
pub static ref FOO: Mutex<CurrentStatus> = Mutex::new(CurrentStatus::new());
}
unsafe impl Send for CurrentStatus {}
#[wasm_bindgen]
pub fn add_index() {
FOO.lock()
.unwrap_or_else(PoisonError::into_inner)
.add_index();
}
#[wasm_bindgen]
pub fn get_index() -> i32 {
let mut foo = FOO.lock().unwrap_or_else(PoisonError::into_inner);
if foo.get_index() == 6 {
panic!();
}
return foo.get_index();
}
Run Code Online (Sandbox Code Playgroud)
JavaScript:
const js = import("../pkg/hello_wasm.js");
js.then(js => {
window.js = js;
console.log(js.get_index());
js.add_index();
console.log(js.get_index());
js.add_index();
console.log(js.get_index());
js.add_index();
console.log(js.get_index());
js.add_index();
console.log(js.get_index());
js.add_index();
});
Run Code Online (Sandbox Code Playgroud)
恐慌之后,我根本无法调用该函数,就好像 Wasm 死了一样。
在回答这个问题之前,我可能应该提到,恐慌处理不应该用作一般错误机制。它们应该用于不可恢复的错误。
引用文档。
这允许程序立即终止并向程序的调用者提供反馈。恐慌!当程序达到不可恢复状态时应使用。
对于来自 C++ 背景的人来说,Rust 中的恐慌实际上比一开始看起来要温和得多(我认为某些在评论中写道的人就是这种情况)。默认情况下,未捕获的 Rust 恐慌会终止线程,而 C++ 异常会终止整个进程。
引用文档
Rust 中的致命逻辑错误会导致线程恐慌,在此期间线程将展开堆栈,运行析构函数并释放拥有的资源。虽然并不意味着“try/catch”机制,但 Rust 中的恐慌仍然可以使用 catch_unwind 捕获(除非使用panic=abort 进行编译)并从中恢复,或者使用resume_unwind 恢复。如果没有捕获恐慌,线程将退出,但可以选择使用 join 从不同的线程检测恐慌。如果主线程发生恐慌而没有捕获恐慌,则应用程序将以非零退出代码退出。
可以catch_unwind并从恐慌中恢复线程,但您应该知道这并catch_unwind不能保证捕获所有恐慌。
请注意,此函数可能无法捕获 Rust 中的所有恐慌。Rust 中的恐慌并不总是通过展开来实现,但也可以通过中止进程来实现。此函数仅捕获展开恐慌,而不是中止进程的恐慌。
所以,我们明白从恐慌中恢复是可以的。问题是当锁中毒时该怎么办。
引用文档
该模块中的互斥体实现了一种称为“中毒”的策略,只要线程在持有互斥体时发生恐慌,该互斥体就被视为中毒。一旦互斥体中毒,默认情况下所有其他线程都无法访问数据,因为它可能已被污染(某些不变量未得到维护)。
中毒是有正当理由的,因为你的数据的不变量可能不会被保留。考虑panic!在某个函数的中间。这只是一个额外的安全级别,您可以绕过它。
然而,中毒的互斥锁并不能阻止对底层数据的所有访问。PoisonError 类型有一个 into_inner 方法,该方法将返回本应在成功锁定时返回的防护。尽管锁被中毒,这仍允许访问数据。
use std::sync::{Mutex, PoisonError};
fn main() {
let mutex = Mutex::new(1);
// We are prepared to face bugs if invariants are wrong
println!("{}", mutex.lock().unwrap_or_else(PoisonError::into_inner));
}
Run Code Online (Sandbox Code Playgroud)
当然,解决恐慌总是比这样做更好。
| 归档时间: |
|
| 查看次数: |
1717 次 |
| 最近记录: |