javascript - 如何检测故障并重置/重启 webassembly 模块?

标签 javascript c++ webassembly

我正在尝试使用 WebSssembly 让一些 C++ 函数在浏览器上运行。我正在关注 tutorial .我想知道:

  • 如何检测(在 JS 端)来自 C++ 代码的“未捕获的异常”?
  • 如何重置/重启 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>
    

    最佳答案

    How to detect (at JS side) an 'uncaught exception' coming from C++ code?



    您必须使用 try-catch 捕获异常 block (tutorial)。

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

    但是,到目前为止,它作为指针传播,因此您将在控制台中看到一些数字:
    5249672
    

    如果你想在 JS 中得到正确的错误信息,那么你必须写一个 binding在您的 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 issue讨论过的地方和suggested .

    我编译了这段代码,使用 C++11 和 bindings 启用了异常。像这样:
    ~/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 issue讨论另一种方法。

    Adding exception catching functionality (DISABLE_EXCEPTION_CATCHING=0) seems to increase the file size too much.



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

    但是,这里有一个观察:

    先前版本的文件大小为:
    110K - a.out.js
    187K - a.out.wasm
    

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

    我剥离了代码并使用 EM_ASM 使用内联 JS在以下代码段中引发 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() 将不起作用并导致终止。因此,您需要在禁用异常时考虑这些问题。

    How to reset/restart WebAssembly Module generated by emcc in a way that avoids memory leaks?



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

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

    至于 C++ 代码中的分配/解除分配,您自己负责分配的资源的解除分配。在这方面,以下几点可能对您有所帮助:
  • 避免 Undefined Behavior .
  • 关注 rule of three/five/zero虔诚地。
  • 使用RAII用于自动内存管理的基于 C++ 标准库工具,例如 std::unique_ptr / std::shared_ptr 连同 std::make_unique / std::make_shared .
  • 寻找 STL 容器,例如 std::vector , std::map 等用于存储和管理集合,而不是求助于编写自己的集合。标准的东西经过充分测试,因此对错误的担忧更少。
  • 请始终引用您计划使用的 API 的文档。验证 API 是否分配了您在使用后可能必须以某种方式释放的资源。


  • 这是加载 WASM 模块的线程:Loading WebAssembly modules efficiently

    关于javascript - 如何检测故障并重置/重启 webassembly 模块?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61015985/

    相关文章:

    javascript - 在 JQuery 选项卡中动态加载 DataTables

    javascript - 如何组合不同的事件触发器

    c++ - 如何反转链表的每 k 个元素?

    javascript - 无法加载 wasm 应用程序

    c++ - 有没有办法为 emscripten 构建 ZeroMQ?

    javascript - KeyboardAvoidingView 在包含一个 View 时折叠所有内部组件

    javascript - 如何停止页面重定向

    c++ - 合并排序和快速排序 C++ 的工具箱(有错误的代码)

    c++ - 如何将元组的值转发给成员初始化程序?

    performance - Web Assembly (Wasm),垃圾收集