blob: d7fd6da1be27f5d784a4b9a2e6ccf241b70afc52 [file]
#include <cassert>
#include <condition_variable>
#include <emscripten/proxying.h>
#include <emscripten/eventloop.h>
#include <iostream>
#include <sched.h>
using namespace emscripten;
// The worker threads we will use. `looper` sits in a loop, continuously
// processing work as it becomes available, while `returner` returns to the JS
// event loop each time it processes work.
std::thread looper;
std::thread returner;
// The queue used to send work to both `looper` and `returner`.
ProxyingQueue queue;
// Whether `looper` should exit.
std::atomic<bool> should_quit{false};
void looper_main() {
while (!should_quit) {
queue.execute();
sched_yield();
}
}
void returner_main() {
// Return back to the event loop while keeping the runtime alive.
// Note that we can't use `emscripten_exit_with_live_runtime` here without
// introducing a memory leak due to way to C++11 threads interact with
// unwinding. See https://github.com/emscripten-core/emscripten/issues/17091.
emscripten_runtime_keepalive_push();
}
void test_proxy_async() {
std::cout << "Testing async proxying\n";
int i = 0;
std::mutex mutex;
std::condition_variable cond;
std::thread::id executor;
// Proxy to ourselves.
queue.proxyAsync(pthread_self(), [&]() {
i = 1;
executor = std::this_thread::get_id();
});
assert(i == 0);
queue.execute();
assert(i == 1);
assert(executor == std::this_thread::get_id());
// Proxy to looper.
{
queue.proxyAsync(looper.native_handle(), [&]() {
{
std::unique_lock<std::mutex> lock(mutex);
i = 2;
}
executor = std::this_thread::get_id();
cond.notify_one();
});
std::unique_lock<std::mutex> lock(mutex);
cond.wait(lock, [&]() { return i == 2; });
assert(executor == looper.get_id());
}
// Proxy to returner.
{
queue.proxyAsync(returner.native_handle(), [&]() {
{
std::unique_lock<std::mutex> lock(mutex);
i = 3;
}
executor = std::this_thread::get_id();
cond.notify_one();
});
std::unique_lock<std::mutex> lock(mutex);
cond.wait(lock, [&]() { return i == 3; });
assert(executor == returner.get_id());
}
}
void test_proxy_sync() {
std::cout << "Testing sync proxying\n";
int i = 0;
std::thread::id executor;
// Proxy to looper.
{
queue.proxySync(looper.native_handle(), [&]() {
i = 2;
executor = std::this_thread::get_id();
});
assert(i == 2);
assert(executor == looper.get_id());
}
// Proxy to returner.
{
queue.proxySync(returner.native_handle(), [&]() {
i = 3;
executor = std::this_thread::get_id();
});
assert(i == 3);
assert(executor == returner.get_id());
}
}
void test_proxy_sync_with_ctx(void) {
std::cout << "Testing sync_with_ctx proxying\n";
int i = 0;
std::thread::id executor;
// Proxy to looper.
{
queue.proxySyncWithCtx(looper.native_handle(), [&](auto ctx) {
i = 2;
executor = std::this_thread::get_id();
ctx.finish();
});
assert(i == 2);
assert(executor == looper.get_id());
}
// Proxy to returner.
{
queue.proxySyncWithCtx(returner.native_handle(), [&](auto ctx) {
i = 3;
executor = std::this_thread::get_id();
auto finish = (void(*)(void*))emscripten_proxy_finish;
emscripten_async_call(finish, ctx.ctx, 0);
});
assert(i == 3);
assert(executor == returner.get_id());
}
}
void test_proxy_callback(void) {
std::cout << "Testing callback proxying\n";
int i = 0;
thread_local int j = 0;
std::thread::id executor;
// Proxy to ourselves.
queue.proxyCallback(
pthread_self(),
[&]() {
i = 1;
executor = std::this_thread::get_id();
},
[&]() { j = 1; },
{});
assert(i == 0);
queue.execute();
assert(i == 1);
assert(executor == std::this_thread::get_id());
assert(j == 1);
// Proxy to looper.
{
queue.proxyCallback(
looper.native_handle(),
[&]() {
i = 2;
executor = std::this_thread::get_id();
},
[&]() { j = 2; },
{});
// TODO: Add a way to wait for work before executing it.
while (j != 2) {
queue.execute();
}
assert(i == 2);
assert(executor == looper.get_id());
}
// Proxy to returner.
{
queue.proxyCallback(
returner.native_handle(),
[&]() {
i = 3;
executor = std::this_thread::get_id();
},
[&]() { j = 3; },
{});
// TODO: Add a way to wait for work before executing it.
while (j != 3) {
queue.execute();
}
assert(i == 3);
assert(executor == returner.get_id());
}
}
void test_proxy_callback_with_ctx(void) {
std::cout << "Testing callback_with_ctx proxying\n";
int i = 0;
thread_local int j = 0;
std::thread::id executor;
// Proxy to ourselves.
queue.proxyCallbackWithCtx(
pthread_self(),
[&](auto ctx) {
i = 1;
executor = std::this_thread::get_id();
ctx.finish();
},
[&]() { j = 1; },
{});
assert(i == 0);
queue.execute();
assert(i == 1);
assert(executor == std::this_thread::get_id());
assert(j == 1);
// Proxy to looper.
{
queue.proxyCallbackWithCtx(
looper.native_handle(),
[&](auto ctx) {
i = 2;
executor = std::this_thread::get_id();
ctx.finish();
},
[&]() { j = 2; },
{});
// TODO: Add a way to wait for work before executing it.
while (j != 2) {
queue.execute();
}
assert(i == 2);
assert(executor == looper.get_id());
}
// Proxy to returner.
{
queue.proxyCallbackWithCtx(
returner.native_handle(),
[&](auto ctx) {
i = 3;
executor = std::this_thread::get_id();
auto finish = (void (*)(void*))emscripten_proxy_finish;
emscripten_async_call(finish, ctx.ctx, 0);
},
[&]() { j = 3; },
{});
// TODO: Add a way to wait for work before executing it.
while (j != 3) {
queue.execute();
}
assert(i == 3);
assert(executor == returner.get_id());
}
}
int main(int argc, char* argv[]) {
looper = std::thread(looper_main);
returner = std::thread(returner_main);
test_proxy_async();
test_proxy_sync();
test_proxy_sync_with_ctx();
test_proxy_callback();
test_proxy_callback_with_ctx();
should_quit = true;
looper.join();
pthread_cancel(returner.native_handle());
returner.join();
std::cout << "done\n";
}