我在使用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已死。
在回答这个问题之前,我可能应该提到,不应将恐慌处理用作一般错误机制。它们应用于不可恢复的错误。
这允许程序立即终止并向程序的调用者提供反馈。恐慌!当程序达到不可恢复的状态时应使用。
Rust中的Panic实际上比来自C ++背景的人看起来要柔和得多(我认为有些人在评论中写这种情况)。默认情况下,未捕获的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));
}
当然,解决恐慌总比这样做好。