在使用Rust和WebAssembly进行开发时遇到了僵局。
由于使用了一些全局访问的变量,我选择了lazy_static
和一个Mutex(使用thread_local
回调会导致嵌套问题)。我已经通过#[wasm_bindgen]
声明了很多Rust函数被JavaScript使用。他们读取和写入lazy_static
变量。
其中一种功能出现紧急情况后,互斥锁无法释放,如果其他功能需要使用相同的互斥锁,则会导致其他紧急情况。
我知道恐慌问题是意料之外的,需要解决,但是这些功能是相对独立的。尽管lazy_static
变量的读取和写入相交,但是某些错误可能不一定会影响其他部分。
在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();
}
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();
});
在恐慌之后,我根本无法调用该函数,就像Wasm已死一样。
答案 0 :(得分:2)
在回答这个问题之前,我可能应该提到,不应将恐慌处理用作一般错误机制。应该将它们用于不可恢复的错误。
这允许程序立即终止并向程序调用者提供反馈。恐慌!程序达到不可恢复状态时应使用。
对于来自C ++背景的人来说,Rust中的野餐实际上比最初看起来要温和得多(我认为有些人在评论中写这种情况)。默认情况下,未捕获的Rust恐慌会终止线程,而C ++异常会终止整个过程。
Rust中的致命逻辑错误导致线程崩溃,在此期间,线程将展开堆栈,运行析构函数并释放拥有的资源。尽管这不意味着是“尝试/捕获”机制,但是Rust的恐慌仍然可以通过catch_unwind来捕获(除非使用panic = abort进行编译)并从catch_unwind中恢复,或者通过resume_unwind恢复。如果未捕获到紧急情况,则线程将退出,但可以选择通过具有join的其他线程来检测紧急情况。如果主线程出现紧急情况而没有捕获到紧急情况,则应用程序将以非零的退出代码退出。
catch_unwind可以从紧急情况中恢复线程,但是您应该知道,catch_unwind
并不能保证捕获所有紧急情况。
请注意,此功能可能无法捕获Rust中的所有紧急情况。 Rust中的恐慌并不总是通过平仓来实现,但是也可以通过中止该过程来实现。此功能仅捕获正在缓解的紧急情况,而不会中止处理过程的紧急情况。
因此,我们知道从恐慌中恢复是可以的。问题是锁中毒时该怎么办。
此模块中的互斥锁实现了一种称为“中毒”的策略,其中每当线程在按住互斥锁时发生紧急情况时,该互斥锁即被视为有毒。一旦互斥体中毒,默认情况下,所有其他线程都无法访问数据,因为它很可能已被污染(某些不变性未被维护)。
存在中毒的正当理由,因为您的数据不变性可能不被保留。在某些函数中间考虑panic!
。这只是您可以绕过的附加安全级别。
但是,中毒的互斥锁不会阻止所有对基础数据的访问。 PoisonError类型具有int_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));
}
当然,解决恐慌总是比这样做更好。