在Wasm发生恐慌后,我该如何触发Rust Mutex的发布,以便未来的通话可以进行



我在使用Rust和WebAssembly进行开发时遇到了死锁。

由于使用了一些全局访问的变量,我选择了lazy_static和Mutex(使用thread_local回调会导致嵌套问题)。我已经声明了很多Rust函数是JavaScript通过#[wasm_bindgen]使用的。他们读取和写入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死了一样。

在回答这个问题之前,我可能应该提到,恐慌处理不应该用作一般的错误机制。它们应该用于不可恢复的错误。

引用文件。

这允许程序立即终止并向程序的调用方提供反馈。惊恐应在程序达到不可恢复状态时使用。

对于来自C++背景的人来说,Rust中的恐慌实际上比最初看起来要温和得多(我认为有些人在评论中就是这样)。未捕获的Rust默认终止线程,而C++异常终止整个进程。

引用文件

Rust中的致命逻辑错误会导致线程死机,在此期间,线程将展开堆栈,运行析构函数并释放所拥有的资源。虽然不意味着"try/catch"机制,但Rust中的恐慌仍然可以用catch_unwnd捕获(除非使用panic=abort编译)并从中恢复,或者用resume_unwnd恢复。如果没有捕捉到死机,线程将退出,但可以选择从具有联接的其他线程检测到死机。如果主线程死机而没有捕获到死机,则应用程序将使用非零的退出代码退出。

catch_unwind并从死机中恢复线程是可以的,但您应该知道catch_unwind并不能保证捕获所有死机。

请注意,此函数可能无法捕获Rust中的所有恐慌。Rust中的恐慌并不总是通过解除来实现的,但也可以通过中止过程来实现。此函数只捕获正在解除的恐慌,而不是那些中止进程的恐慌。

因此,我们明白从恐慌中恢复是可以的。问题是当锁中毒时该怎么办。

引用文件

该模块中的互斥体实现了一种称为"中毒"的策略,即每当线程在持有互斥体时惊慌失措时,就会认为互斥体中毒。一旦互斥被破坏,所有其他线程在默认情况下都无法访问数据,因为它可能被破坏了(一些不变量没有得到支持)。

中毒是有正当理由的,因为数据的不变量可能没有被保存。在某些函数的中间考虑panic!。这只是一个额外的安全级别,你可以绕过它。

然而,中毒的互斥体并不能阻止对底层数据的所有访问。poiseError类型有一个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));
}

游乐场链接

当然,解决恐慌总比这样做要好。

我遇到了一个问题,我并行运行了几个集成测试,它们有一个互斥锁(可以全局访问),但如果一个测试失败,所有后续测试也会失败。当我想调试测试失败的地方时,这是一个问题,因为输出会有很多其他失败的测试(由于错误)。

解决方案是简单地使用parking_lot板条箱(一个使用互斥锁的库),如果线程崩溃,它似乎可以清除互斥锁。

遗憾的是,我在文档中没有找到任何解释它是如何工作的,或者它是否能在未来的版本中保证这种行为。但当前版本对我来说效果很好,如果它对你有效,它比公认的答案更容易,因为你所要做的就是用parking_lot::Mutex替换sync::Mutex,然后你就可以开始了(也可以将.unwrap删除到lock(),因为它不会返回Result)

相关内容

  • 没有找到相关文章

最新更新