【问题标题】:Out of memory WebAssembly Instantiation in Cloudflare WorkersCloudflare Workers 中的 WebAssembly 实例化内存不足
【发布时间】:2019-02-02 06:11:55
【问题描述】:

我正在通过 wasm-bindgen 在 Rust 中构建一个 WebAssembly 模块以用于 Cloudflare Workers。该模块总体上非常基础;它有一个名为 process 的函数,它将两个二进制文件(由两个 Uint8BitArray 表示)和一个 json_value(由 serde 解释)作为输入,并产生 None 或字符串,通常是这样的。

#[wasm_bindgen]
pub fn process(
    binary_a: &[u8],
    binary_b: &[u8],
    json_value: &JsValue,
) -> Option<String> {
   Some(String::new())
}

实例化 WebAssembly 模块的胶水代码与 wasm-bindgen --no-modules 命令几乎相同,我只是将第 93 行的初始化条件更改为 true

self = {};

(function() {
    var wasm;
    const __exports = {};


    let cachegetUint8Memory = null;
    function getUint8Memory() {
        if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
            cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
        }
        return cachegetUint8Memory;
    }

    let WASM_VECTOR_LEN = 0;

    function passArray8ToWasm(arg) {
        const ptr = wasm.__wbindgen_malloc(arg.length * 1);
        getUint8Memory().set(arg, ptr / 1);
        WASM_VECTOR_LEN = arg.length;
        return ptr;
    }

    const heap = new Array(32);

    heap.fill(undefined);

    heap.push(undefined, null, true, false);

    let stack_pointer = 32;

    function addBorrowedObject(obj) {
        if (stack_pointer == 1) throw new Error('out of js stack');
        heap[--stack_pointer] = obj;
        return stack_pointer;
    }

    let cachedTextDecoder = new TextDecoder('utf-8');

    function getStringFromWasm(ptr, len) {
        return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
    }

    let cachedGlobalArgumentPtr = null;
    function globalArgumentPtr() {
        if (cachedGlobalArgumentPtr === null) {
            cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
        }
        return cachedGlobalArgumentPtr;
    }

    let cachegetUint32Memory = null;
    function getUint32Memory() {
        if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) {
            cachegetUint32Memory = new Uint32Array(wasm.memory.buffer);
        }
        return cachegetUint32Memory;
    }
    /**
    * @param {Uint8Array} arg0
    * @param {Uint8Array} arg1
    * @param {any} arg2
    * @returns {string}
    */
    __exports.process = function(arg0, arg1, arg2) {
        const ptr0 = passArray8ToWasm(arg0);
        const len0 = WASM_VECTOR_LEN;
        const ptr1 = passArray8ToWasm(arg1);
        const len1 = WASM_VECTOR_LEN;
        const retptr = globalArgumentPtr();
        try {
            wasm.process(retptr, ptr0, len0, ptr1, len1, addBorrowedObject(arg2));
            const mem = getUint32Memory();
            const rustptr = mem[retptr / 4];
            const rustlen = mem[retptr / 4 + 1];
            if (rustptr === 0) return;
            const realRet = getStringFromWasm(rustptr, rustlen).slice();
            wasm.__wbindgen_free(rustptr, rustlen * 1);
            return realRet;


        } finally {
            wasm.__wbindgen_free(ptr0, len0 * 1);
            wasm.__wbindgen_free(ptr1, len1 * 1);
            heap[stack_pointer++] = undefined;

        }

    };

    function init(path_or_module) {
        let instantiation;
        const imports = { './phototaxon_worker_utils': __exports };
        if (true) {
            instantiation = WebAssembly.instantiate(path_or_module, imports)
            .then(instance => {
            return { instance, module: path_or_module }
        });
    } else {
        const data = fetch(path_or_module);
        if (typeof WebAssembly.instantiateStreaming === 'function') {
            instantiation = WebAssembly.instantiateStreaming(data, imports);
        } else {
            instantiation = data
            .then(response => response.arrayBuffer())
            .then(buffer => WebAssembly.instantiate(buffer, imports));
        }
    }
    return instantiation.then(({instance}) => {
        wasm = init.wasm = instance.exports;

    });
};
self.wasm_bindgen = Object.assign(init, __exports);
})();

self.wasm_bindgen(MODULE).then(() => { XXX }).catch(error => console.log(error));

我使用cloudworker 试用了整个脚本,它运行起来没有问题。然后,我用Preview Service API 尝试了相同的脚本,它在几次尝试中确实运行良好,直到它开始抛出错误:

RangeError: WebAssembly Instantiation: Out of memory: wasm memory
    at init (worker.js:1200:35)
    at Module.<anonymous> (worker.js:1878:15)
    at __webpack_require__ (worker.js:20:30)
    at worker.js:84:18
    at worker.js:87:10

这发生在实例化时,不管发送的请求是什么(初始化后发生的不是。

我一直在尝试精简我的 Webassembly 脚本,但即使是 hello-world 类型的函数也被拒绝了。不知道怎么调试,是不是和glue-code、rust code或者Cloudflare的Preview Service有关?

【问题讨论】:

  • 您使用的是默认分配器吗? IIRC,您可以使用wee_alloc 显着减小二进制大小。

标签: rust cloudflare-workers wasm-bindgen


【解决方案1】:

问题是缺少 String 容量(在 process 函数中),因此 WebAssembly 模块无法配置运行所需的内存(可能是过度配置)。 通过使用String::with_capacity 设置限制,模块可以正常运行而不会出现任何内存问题。

【讨论】:

  • 嘿@Edouard,实际上,在看到您的问题后,我们注意到Workers 预览服务对所有new WebAssembly.Memory(...) 调用都抛出了错误。我尝试重新启动服务并且...修复了它。显然,我们将调查一个更深层次的错误。只是想让您知道,当您看到它开始工作时,可能不是您更改了什么,而是因为我重新启动了预览服务。 (据我们所知,这个问题只影响预览,而不影响部署在生产中的工作人员。)
  • 这是有道理的。我恢复了更改,它再次工作......直到我尝试提出更多请求,它不再工作。我再次用容量选项替换了代码,它也不起作用。我猜我的模块以某种方式使整个 WebAssembly.Memory 崩溃了?作为一个附带问题,预览服务的 CPU 资源限制是多少? (相当于 5ms 免费套餐?)
  • 我自己无法重现该问题。您愿意通过电子邮件将您的区域名称和详细信息通过电子邮件发送给我,以便我进行追踪吗? cloudflare 的肯顿。为了回答您的第二个问题,我们终止请求的 CPU 限制各不相同,但无论计划级别如何,在生产和预览版中始终至少为 50 毫秒。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-16
  • 2021-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-29
相关资源
最近更新 更多