您现在的位置:kastop>> Kas信息 Kaspa网络>>正文内容

kaspa WASM绑定相关问题

我想发布一篇关于WASM绑定相关问题的帖子,以便使用WASM SDK的开发人员了解其原因,并在需要时采取适当措施加以缓解。

以下解释是高度技术性的,需要了解JavaScript和Rust。

关键组件(设置):

RpcClient是一个导出到WASM的Rust结构体

UtxoProcessor也是一个导出到WASM的Rust结构体;UtxoProcessor有一个函数rpc(),它返回RpcClient结构的克隆(副本);此函数作为getter导出到WASM。

随后让rpc=processor.rpc;是访问上述getter并获得附加到UtxoProcessor的RpcClient的一种方式。

PendingTransaction(又名ptx)是导出到WASM的另一种结构,它有一个异步提交(rpc:&RpcClient)函数,用于提交PendingTransaction。执行此函数需要时间(可能从亚秒到几秒不等,因为它调用下面的rpc.submitTransaction()。

问题:

在JS中,当调用wait ptx.submit(processer.rpc)时;submit()函数启动,1-2秒后GC试图删除传递给它的rpc对象。

这会在传递给submit()函数的对象上调用Rust drop(),导致Rust“借用”恐慌。

有趣的是执行:

let result = await ptx.submit(processor.rpc);

Or

let rpc = processor.rpc;

await ptx.submit(rpc);

console.log(rpc);

防止这种情况发生。


观察2:

要调用异步表达式(在我们的例子中是submit),JSVM首先评估processor.rpc getter,获取RpcClient的克隆。然后将其传递给async函数。

与Rust中异步fn仅在等待未来时执行不同,JS中的异步函数会像同步函数一样立即执行。“等待”部分只是承诺的锚,告诉调用者阻止并等待承诺的完成。

所以这里发生的事情如下:

当你打电话的时候

等待ptx.submit(processor.rpc);

JS计算processor.rpc,将其传递给submit(),并计算submit(作为sync)。(submit()返回一个promise,然后等待promise);此时,不再需要rpc对象(因为submit已被评估为sync),因此在JS GC中计划删除该对象。

GC然后以随机间隔唤醒并删除调用其析构函数的对象。析构函数级联到Rust,并在实际的ptx.submit(rpc)仍在运行并持有对RpcClient的内部引用时对对象产生Drop。

JS引用创建是borrow(),而任何WASM导出的对象都是borrow_mut()…。结果是双重借款,导致恐慌。(我强烈建议学习Rust,因为在Rust中不可能有这些和大量其他运行时eror,而在这里引起恐慌的借用机制是Rust的自卫机制……我倒退了)。

解决这个问题最困难的部分是,由于它是由GC引起的,恐慌会在随机的时间间隔内发生,没有任何可以理解的原因。

有趣的是,使用async函数的结果似乎可以防止GC在执行过程中删除它的参数。

请注意,虽然这个例子使用了RpcClient结构(因为这是我们缩小范围的方法),但从技术上讲,任何使用async、getter或临时变量的模式都容易受到这种影响。

以下方法也可以纠正这个问题:

.

A: 创建一个永久分配到某个地方的全局rpc对象(而不是通过getter创建的临时对象)。

B: 在Rust方面,接受原始JsValue,手动获取ABI ref并获取+克隆RpcClient会导致ABI ref立即被删除,之后删除传递的JsValue是可以的。但是,我认为这种方法容易受到时间的影响,如果CPU负载导致GC在函数启动附近参与JSVM执行,仍然可能发生这种情况。

C: 通过值传递RpcClient也很好,因为它通过从中获取值来清空API单元格。然后Rust-fn获得对象的所有权,JS中的原始对象不再与其有任何关系,因此其销毁变得无关。然而,这种方法是不可行的,因为JS中的rpc参数不再可重用。

到目前为止,唯一可靠的解决方案似乎是确保您始终使用async函数的返回值。

遇到这些问题的开发人员忽略了这一点的原因是,ptx.submit(rpc)返回一个txid(他已经拥有),如果发生错误,函数会抛出异常,因此似乎没有必要使用返回值。

.

摘要

这种情况极为罕见,如果以下条件为真,则会随机发生:

异步函数结果未被使用

异步函数执行持续时间超过1-2秒(足够GC唤醒的时间)

传递给async函数的至少一个参数是一个变量,该变量表示WASM导出的Rust结构,该结构不再在其他地方引用(局部变量或来自Rust(WASM)端创建的对象的getter,该对象创建了一个临时变量)。

如果时间允许,我们将进一步研究这个问题,并将其转交给WASM bindgen团队(Rusty Kaspa WASM SDK非常广泛,我们已经与WASM_bindgen团队合作对其进行了更改)。



感动 同情 无聊 愤怒 搞笑 难过 高兴 路过
【字体: 】【收藏】【打印文章】 【 打赏 】 【查看评论

相关文章

    没有相关内容