如何检测故障并重置/重新启动webassembly模块



我正在尝试使用WebSssembly在浏览器上运行一些C++函数。我正在学习本教程。我想知道:

  1. 如何(在JS端(检测来自C++代码的"未捕获异常">
  2. 如何重置/重新启动emcc生成的WebAssembly模块以避免内存泄漏

添加异常捕获功能(DISABLE_EXCEPTION_CATCHING=0(似乎会过多地增加文件大小。

任何帮助都将不胜感激。


示例C++代码如下:

// C++ source code (fib.cc)
#include <stdexcept>
#include <emscripten.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int fib(int n) {
if (n > 12) {
throw std::out_of_range("input out of range");
}
int i, t, a = 0, b = 1;
for (i = 0; i < n; i++) {
t = a + b;
a = b;
b = t;
}
return b;
}
// >>
// other functions with allocations/deallocations
} // end of extern C

它是用以下命令构建的:

emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]' fib.cc

它通过一个网页进行测试:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WASM Test Page</title>
</head>
<body>
<script src="a.out.js"></script>
<script>
"use strict";
Module.onRuntimeInitialized = _ => {
const fib = Module.cwrap('fib', 'number', ['number']);
console.log(fib(10));
console.log(fib(14)); // causes exception
};
</script>
</body>
</html>

如何(在JS端(检测来自C++代码的"未捕获异常"?

您必须使用try-catch块捕获异常(教程(。

示例:

try {
console.log( fib(14) );
}
catch ( e ) {
console.error( e );
}

但是,到目前为止,它是作为一个指针传播的,所以你会在控制台中看到一些数字:

5249672

如果你想在JS中得到一个正确的错误消息,那么你必须在C++代码中写一个绑定:

#include <emscripten/bind.h>
std::string getExceptionMessage(int eptr)
{
return reinterpret_cast<std::exception*>(eptr)->what();
}
EMSCRIPTEN_BINDINGS(getExceptionMessageBinding)
{
emscripten::function("getExceptionMessage", &getExceptionMessage);
};

这将通过Module对象在JS代码中公开。你可以在JS代码中使用它,如下所示:

try {
console.log( fib(14) );
}
catch ( e ) {
console.error( Module.getExceptionMessage(e) );
}

输出(引发异常(:

input out of range

这是GitHub问题,在这里已经讨论并提出了这一点。

我编译了这段代码,使用C++11和类似的绑定启用了异常:

~/emsdk/upstream/emscripten$ ./em++ -std=c++11 -Os -fexceptions --bind 
-s WASM=1
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
-s DISABLE_EXCEPTION_CATCHING=0
fib.cc

下面是另一个类似的GitHub问题,讨论了另一种方法。


添加异常捕获功能(DISABLE_EXCEPTION_CATCHING=0(似乎会过多地增加文件大小。

如果您担心的是输出文件的大小增加,您可以完全禁用异常处理,并使用函数返回的无效值或错误代码进行错误检查,例如,如果输入无效,则为-1。

然而,这里有一个观察结果:

以前版本的文件大小为:

110K - a.out.js
187K - a.out.wasm

绑定的异常处理和RTTI就是其中的一部分。

我剥离了代码并使用内联JS,使用EM_ASM在以下代码片段中抛出JS错误:

#include <emscripten.h>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int fib(int n) {
if (n > 12) {
EM_ASM(
throw Error("out_of_range");    // JS error with EM_ASM
);
}
int a {0}, b {1};
for ( int i {0}; i < n; ++i ) {
const auto t = a + b;
a = b;
b = t;
}
return b;
}
}

编译时禁用异常:

$ ./em++ -std=c++11 -Os
-fno-exceptions 
-s WASM=1
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap"]'
fib1.cc

这是HTML文件(fib1.html(:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WASM Test Page</title>
</head>
<body>
<script src="a.out.js"></script>
<script>
"use strict";
Module.onRuntimeInitialized = _ => {
const fib = Module.cwrap('fib', 'number', ['number']);
try {
console.log(fib(10));
console.log(fib(14));
}
catch ( e ) {
console.error(e);
}
};
</script>
</body>
</html>

控制台输出(捕捉到异常(:

89
fib1.html:21 Error: out_of_range
at Array.ASM_CONSTS (a.out.js:1)
at _emscripten_asm_const_i (a.out.js:1)
at wasm-function[1]:0x6b
at Module._fib (a.out.js:1)
at Object.Module.onRuntimeInitialized (fib1.html:18)
at doRun (a.out.js:1)
at run (a.out.js:1)
at runCaller (a.out.js:1)
at removeRunDependency (a.out.js:1)
at receiveInstance (a.out.js:1)

文件大小为:

15K - a.out.js
246 - a.out.wasm (bytes)

抛出JS错误仍然可以在禁用异常的情况下工作,并且生成的文件大小要小得多。您可能需要对此进行更多探索。也许,创建一些从Error继承的具有扩展功能的类。然而,从标准API(如std::vector::at()(抛出的异常将不起作用并导致终止。因此,在禁用异常的同时,您需要考虑到这些问题。


如何以避免内存泄漏的方式重置/重新启动emcc生成的WebAssembly模块?

到目前为止,还没有这样的API来重置/重新启动模块。模块本身在不再被引用时会自动进行垃圾收集。在这种情况下,您不必关心内存泄漏。JS运行时对此负责。

但是,如果JS代码创建的C++对象正在管理资源(内存、文件句柄等(,则应该使用Module.destroy销毁它。垃圾收集器(GC(在收集对象时不会调用析构函数,这将导致内存/资源泄漏。调用Module.destory将调用析构函数,并且不会有任何内存泄漏。现在,你的问题没有这样的对象。因此,当你这样做时要注意,并在需要时拨打Module.destory

至于C++代码中的分配/解除分配,您自己负责分配的资源的解除分配。以下几点可能在这方面对您有所帮助:

  • 避免未定义的行为。

  • 虔诚地遵守三/五/零的规则。

  • 使用基于RAII的C++标准库设施进行自动内存管理,如std::unique_ptr/std::shared_ptrstd::make_unique/std::make_shared

  • 寻找STL容器,如std::vectorstd::map等,用于存储和管理集合,而不是编写自己的集合。标准的东西经过了很好的测试,因此对bug的担忧更少。

  • 请始终参考您计划使用的API的文档。验证API是否分配了使用后可能必须以某种方式解除分配的资源。


这里有一个关于加载WASM模块的线程:高效地加载WebAssembly模块

最新更新