Description
Some types are sensitive to being used and destructed on the same thread they came from. One example of this is emscripten::val
that represents JS values. Its destructor calls into JS to decrease reference counter of a JS value, but the value stored behing the handle would be different or not exist at all if invoked on the wrong thread.
Proxying currently invokes lambda on the target thread, but doesn't destruct it there, which means that even if you move a value into the lambda, it still won't be destructed on the correct thread.
Example:
#include <cassert>
#include <thread>
#include <emscripten/threading.h>
#include <emscripten/proxying.h>
#include <emscripten/val.h>
using namespace emscripten;
int main() {
ProxyingQueue queue;
val v;
queue.proxySync(emscripten_main_runtime_thread_id(), [&] {
// create a value on the main thread but store on the current one
new (&v) val(42);
});
queue.proxySync(emscripten_main_runtime_thread_id(), [v = std::move(v)] {
// move the value back to the main thread to be used & destroyed
val::global("console").call<void>("log", v);
});
// val should have been freed by now by the target thread
}
> ./emcc temp.cpp -o temp.js -lembind -s PROXY_TO_PTHREAD -g2 -pthread
> node temp.js
Aborted(Assertion failed: pthread_equal(thread, pthread_self()) && "val accessed from wrong thread", at: C:\Users\me\Documents\emscripten\cache\sysroot/include\emscripten/val.h,391,as_handle)
C:\Users\me\Documents\emscripten\temp.js:147
throw ex;
^
RuntimeError: Aborted(Assertion failed: pthread_equal(thread, pthread_self()) && "val accessed from wrong thread", at: C:\Users\me\Documents\emscripten\cache\sysroot/include\emscripten/val.h,391,as_handle)
at abort (C:\Users\me\Documents\emscripten\temp.js:753:11)
at ___assert_fail (C:\Users\me\Documents\emscripten\temp.js:1760:7)
at temp.wasm.emscripten::val::as_handle() const (wasm://wasm/temp.wasm-00073e76:wasm-function[57]:0x13f5)
at temp.wasm.emscripten::val::~val() (wasm://wasm/temp.wasm-00073e76:wasm-function[50]:0x107e)
at temp.wasm.emscripten::val::operator=(emscripten::val&&) & (wasm://wasm/temp.wasm-00073e76:wasm-function[167]:0x3833)
at temp.wasm.main::$_0::operator()() const (wasm://wasm/temp.wasm-00073e76:wasm-function[165]:0x3759)
at temp.wasm.decltype(std::declval<main::$_0&>()()) std::__2::__invoke[abi:v160006]<main::$_0&>(main::$_0&) (wasm://wasm/temp.wasm-00073e76:wasm-function[163]:0x36ba)
at temp.wasm.void std::__2::__invoke_void_return_wrapper<void, true>::__call<main::$_0&>(main::$_0&) (wasm://wasm/temp.wasm-00073e76:wasm-function[162]:0x367f)
at temp.wasm.std::__2::__function::__alloc_func<main::$_0, std::__2::allocator<main::$_0>, void ()>::operator()[abi:v160006]() (wasm://wasm/temp.wasm-00073e76:wasm-function[99]:0x23ab)
at temp.wasm.std::__2::__function::__func<main::$_0, std::__2::allocator<main::$_0>, void ()>::operator()() (wasm://wasm/temp.wasm-00073e76:wasm-function[98]:0x236a)
Node.js v20.7.0
I think proxySync
needs either a handling similar to proxyAsync
or, to avoid heap allocation, move it into something like std::optional
to destruct it on the target thread and avoid 2nd destructor running on the main thread.