blob: aec43d56174079732730cc150a98a7b6dc330dda [file]
#define _GNU_SOURCE
#include <assert.h>
#include <emscripten.h>
#include <emscripten/console.h>
#include <emscripten/eventloop.h>
#include <emscripten/proxying.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>
// 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.
pthread_t main_thread;
pthread_t looper;
pthread_t returner;
// Used as the main test runner thread in the proxy_promise* tests.
pthread_t worker;
// The queue used to send work to both `looper` and `returner`.
em_proxying_queue* proxy_queue = NULL;
// Whether `looper` should exit.
_Atomic int should_quit = 0;
void* looper_main(void* arg) {
emscripten_err("looper_main");
while (!should_quit) {
emscripten_proxy_execute_queue(proxy_queue);
sched_yield();
}
emscripten_err("quitting looper");
return NULL;
}
void* returner_main(void* queue) {
emscripten_err("returner_main");
emscripten_exit_with_live_runtime();
}
typedef struct widget {
// `val` will be stored to `out` and the current thread will be stored to
// `thread` when the widget is run.
int* out;
int val;
pthread_t thread;
// Synchronization to allow waiting on a widget to run.
pthread_mutex_t mutex;
pthread_cond_t cond;
// Nonzero iff the widget has been run.
int done;
// Only used for *_with_ctx tests.
em_proxying_ctx* ctx;
} widget;
void init_widget(widget* w, int* out, int val) {
*w = (widget){.out = out,
.val = val,
// .thread will be set in `run_widget`.
.mutex = PTHREAD_MUTEX_INITIALIZER,
.cond = PTHREAD_COND_INITIALIZER,
.done = 0,
.ctx = NULL};
}
void destroy_widget(widget* w) {
pthread_mutex_destroy(&w->mutex);
pthread_cond_destroy(&w->cond);
}
void run_widget(widget* w) {
pthread_t self = pthread_self();
const char* name = pthread_equal(self, main_thread) ? "main"
: pthread_equal(self, looper) ? "looper"
: pthread_equal(self, returner) ? "returner"
: pthread_equal(self, worker) ? "worker"
: "unknown";
printf("running widget %d on %s\n", w->val, name);
pthread_mutex_lock(&w->mutex);
if (w->out) {
*w->out = w->val;
}
w->thread = pthread_self();
w->done = 1;
pthread_mutex_unlock(&w->mutex);
pthread_cond_broadcast(&w->cond);
}
void await_widget(widget* w) {
pthread_mutex_lock(&w->mutex);
while (!w->done) {
pthread_cond_wait(&w->cond, &w->mutex);
}
pthread_mutex_unlock(&w->mutex);
}
// Helper functions we will proxy to perform our work.
void do_run_widget(void* arg) { run_widget((widget*)arg); }
void finish_running_widget(void* arg) {
widget* w = (widget*)arg;
run_widget(w);
emscripten_proxy_finish(w->ctx);
}
void start_running_widget(em_proxying_ctx* ctx, void* arg) {
((widget*)arg)->ctx = ctx;
emscripten_async_call(finish_running_widget, arg, 0);
}
void start_and_finish_running_widget(em_proxying_ctx* ctx, void* arg) {
((widget*)arg)->ctx = ctx;
finish_running_widget(arg);
}
em_promise_result_t check_widget(void** result, void* data, void* value) {
widget* w = data;
assert(w->done);
assert(*w->out == w->val);
return EM_PROMISE_FULFILL;
}
// Main test functions
void test_proxy_async(void) {
printf("Testing async proxying\n");
int i = 0;
widget w1, w2, w3;
init_widget(&w1, &i, 1);
init_widget(&w2, &i, 2);
init_widget(&w3, &i, 3);
// Proxy to ourselves.
emscripten_proxy_async(proxy_queue, pthread_self(), do_run_widget, &w1);
assert(!w1.done);
emscripten_proxy_execute_queue(proxy_queue);
assert(i == 1);
assert(w1.done);
assert(pthread_equal(w1.thread, pthread_self()));
// Proxy to looper.
emscripten_proxy_async(proxy_queue, looper, do_run_widget, &w2);
await_widget(&w2);
assert(i == 2);
assert(w2.done);
assert(pthread_equal(w2.thread, looper));
// Proxy to returner.
emscripten_proxy_async(proxy_queue, returner, do_run_widget, &w3);
await_widget(&w3);
assert(i == 3);
assert(w3.done);
assert(pthread_equal(w3.thread, returner));
destroy_widget(&w1);
destroy_widget(&w2);
destroy_widget(&w3);
}
void test_proxy_sync(void) {
printf("Testing sync proxying\n");
int i = 0;
widget w4, w5;
init_widget(&w4, &i, 4);
init_widget(&w5, &i, 5);
// Proxy to looper.
emscripten_proxy_sync(proxy_queue, looper, do_run_widget, &w4);
assert(i == 4);
assert(w4.done);
assert(pthread_equal(w4.thread, looper));
// Proxy to returner.
emscripten_proxy_sync(proxy_queue, returner, do_run_widget, &w5);
assert(i == 5);
assert(w5.done);
assert(pthread_equal(w5.thread, returner));
destroy_widget(&w4);
destroy_widget(&w5);
}
void test_proxy_sync_with_ctx(void) {
printf("Testing sync_with_ctx proxying\n");
int i = 0;
widget w6, w7;
init_widget(&w6, &i, 6);
init_widget(&w7, &i, 7);
// Proxy to looper.
emscripten_proxy_sync_with_ctx(
proxy_queue, looper, start_and_finish_running_widget, &w6);
assert(i == 6);
assert(w6.done);
assert(pthread_equal(w6.thread, looper));
// Proxy to returner.
emscripten_proxy_sync_with_ctx(
proxy_queue, returner, start_running_widget, &w7);
assert(i == 7);
assert(w7.done);
assert(pthread_equal(w7.thread, returner));
destroy_widget(&w6);
destroy_widget(&w7);
}
thread_local int j = 0;
void set_j(void* arg) {
widget* widget = arg;
j = widget->val;
}
void test_proxy_callback(void) {
printf("Testing callback proxying\n");
int i = 0;
widget w8, w9, w10;
init_widget(&w8, &i, 8);
init_widget(&w9, &i, 9);
init_widget(&w10, &i, 10);
// Proxy to ourselves.
emscripten_proxy_callback(
proxy_queue, pthread_self(), do_run_widget, set_j, NULL, &w8);
assert(!w8.done);
assert(j == 0);
emscripten_proxy_execute_queue(proxy_queue);
assert(i == 8);
assert(w8.done);
assert(pthread_equal(w8.thread, pthread_self()));
assert(j == 8);
// Proxy to looper.
emscripten_proxy_callback(
proxy_queue, looper, do_run_widget, set_j, NULL, &w9);
await_widget(&w9);
assert(i == 9);
assert(w9.done);
assert(pthread_equal(w9.thread, looper));
assert(j == 8);
// TODO: Add a way to wait for work before executing it.
while (j != 9) {
emscripten_proxy_execute_queue(proxy_queue);
}
// Proxy to returner.
emscripten_proxy_callback(
proxy_queue, returner, do_run_widget, set_j, NULL, &w10);
await_widget(&w10);
assert(i == 10);
assert(w10.done);
assert(pthread_equal(w10.thread, returner));
assert(j == 9);
// TODO: Add a way to wait for work before executing it.
while (j != 10) {
emscripten_proxy_execute_queue(proxy_queue);
}
destroy_widget(&w8);
destroy_widget(&w9);
destroy_widget(&w10);
}
void test_proxy_callback_with_ctx(void) {
printf("Testing callback_with_ctx proxying\n");
int i = 0;
widget w11, w12, w13;
init_widget(&w11, &i, 11);
init_widget(&w12, &i, 12);
init_widget(&w13, &i, 13);
// Proxy to ourselves.
emscripten_proxy_callback_with_ctx(
proxy_queue, pthread_self(), start_and_finish_running_widget, set_j, NULL, &w11);
assert(!w11.done);
assert(j == 10);
emscripten_proxy_execute_queue(proxy_queue);
assert(i == 11);
assert(w11.done);
assert(pthread_equal(w11.thread, pthread_self()));
assert(j == 11);
// Proxy to looper.
emscripten_proxy_callback_with_ctx(
proxy_queue, looper, start_and_finish_running_widget, set_j, NULL, &w12);
await_widget(&w12);
assert(i == 12);
assert(w12.done);
assert(pthread_equal(w12.thread, looper));
assert(j == 11);
// TODO: Add a way to wait for work before executing it.
while (j != 12) {
emscripten_proxy_execute_queue(proxy_queue);
}
// Proxy to returner.
emscripten_proxy_callback_with_ctx(
proxy_queue, returner, start_running_widget, set_j, NULL, &w13);
await_widget(&w13);
assert(i == 13);
assert(w13.done);
assert(pthread_equal(w13.thread, returner));
assert(j == 12);
// TODO: Add a way to wait for work before executing it.
while (j != 13) {
emscripten_proxy_execute_queue(proxy_queue);
}
destroy_widget(&w11);
destroy_widget(&w12);
destroy_widget(&w13);
}
em_promise_result_t explode(void** result, void* data, void* value) {
printf("error!");
abort();
return EM_PROMISE_REJECT;
}
em_promise_result_t test_promise_self(void** result, void* data, void* value) {
widget* w14 = data;
em_promise_t promise =
emscripten_proxy_promise(proxy_queue, pthread_self(), do_run_widget, w14);
*result = emscripten_promise_then(promise, check_widget, explode, w14);
emscripten_promise_destroy(promise);
return EM_PROMISE_MATCH_RELEASE;
}
em_promise_result_t
test_promise_looper(void** result, void* data, void* value) {
widget* w15 = data;
em_promise_t promise =
emscripten_proxy_promise(proxy_queue, looper, do_run_widget, w15);
*result = emscripten_promise_then(promise, check_widget, explode, w15);
emscripten_promise_destroy(promise);
return EM_PROMISE_MATCH_RELEASE;
}
em_promise_result_t
test_promise_returner(void** result, void* data, void* value) {
widget* w16 = data;
em_promise_t promise =
emscripten_proxy_promise(proxy_queue, returner, do_run_widget, w16);
*result = emscripten_promise_then(promise, check_widget, explode, w16);
emscripten_promise_destroy(promise);
return EM_PROMISE_MATCH_RELEASE;
}
void do_exit_thread(void* arg) { emscripten_runtime_keepalive_pop(); }
em_promise_result_t exit_thread(void** result, void* data, void* value) {
// TODO: exit the thread directly rather than on the next turn of the event
// loop.
emscripten_async_call(do_exit_thread, NULL, 0);
return EM_PROMISE_FULFILL;
}
struct promise_widgets {
widget w14, w15, w16;
};
void* do_test_proxy_promise(void* arg) {
struct promise_widgets* widgets = arg;
em_promise_t start = emscripten_promise_create();
em_promise_t test1 =
emscripten_promise_then(start, test_promise_self, explode, &widgets->w14);
em_promise_t test2 =
emscripten_promise_then(test1, test_promise_looper, explode, &widgets->w15);
em_promise_t test3 = emscripten_promise_then(
test2, test_promise_returner, explode, &widgets->w16);
em_promise_t end = emscripten_promise_then(test3, exit_thread, explode, NULL);
emscripten_promise_resolve(start, EM_PROMISE_FULFILL, NULL);
emscripten_promise_destroy(start);
emscripten_promise_destroy(test1);
emscripten_promise_destroy(test2);
emscripten_promise_destroy(test3);
emscripten_promise_destroy(end);
emscripten_runtime_keepalive_push();
return NULL;
}
void test_proxy_promise() {
printf("Testing promise proxying\n");
int i = 0;
struct promise_widgets widgets;
init_widget(&widgets.w14, &i, 14);
init_widget(&widgets.w15, &i, 15);
init_widget(&widgets.w16, &i, 16);
pthread_create(&worker, NULL, do_test_proxy_promise, &widgets);
pthread_join(worker, NULL);
destroy_widget(&widgets.w14);
destroy_widget(&widgets.w15);
destroy_widget(&widgets.w16);
}
em_promise_result_t
test_promise_ctx_self(void** result, void* data, void* value) {
widget* w17 = data;
em_promise_t promise = emscripten_proxy_promise_with_ctx(
proxy_queue, pthread_self(), start_running_widget, w17);
*result = emscripten_promise_then(promise, check_widget, explode, w17);
emscripten_promise_destroy(promise);
return EM_PROMISE_MATCH_RELEASE;
}
em_promise_result_t
test_promise_ctx_looper(void** result, void* data, void* value) {
widget* w18 = data;
em_promise_t promise = emscripten_proxy_promise_with_ctx(
proxy_queue, looper, start_and_finish_running_widget, w18);
*result = emscripten_promise_then(promise, check_widget, explode, w18);
emscripten_promise_destroy(promise);
return EM_PROMISE_MATCH_RELEASE;
}
em_promise_result_t
test_promise_ctx_returner(void** result, void* data, void* value) {
widget* w19 = data;
em_promise_t promise = emscripten_proxy_promise_with_ctx(
proxy_queue, returner, start_running_widget, w19);
*result = emscripten_promise_then(promise, check_widget, explode, w19);
emscripten_promise_destroy(promise);
return EM_PROMISE_MATCH_RELEASE;
}
struct promise_ctx_widgets {
widget w17, w18, w19;
};
void* do_test_proxy_promise_with_ctx(void* arg) {
struct promise_ctx_widgets* widgets = arg;
em_promise_t start = emscripten_promise_create();
em_promise_t test1 = emscripten_promise_then(
start, test_promise_ctx_self, explode, &widgets->w17);
em_promise_t test2 = emscripten_promise_then(
test1, test_promise_ctx_looper, explode, &widgets->w18);
em_promise_t test3 = emscripten_promise_then(
test2, test_promise_ctx_returner, explode, &widgets->w19);
em_promise_t end = emscripten_promise_then(test3, exit_thread, explode, NULL);
emscripten_promise_resolve(start, EM_PROMISE_FULFILL, NULL);
emscripten_promise_destroy(start);
emscripten_promise_destroy(test1);
emscripten_promise_destroy(test2);
emscripten_promise_destroy(test3);
emscripten_promise_destroy(end);
emscripten_runtime_keepalive_push();
return NULL;
}
void test_proxy_promise_with_ctx() {
printf("Testing promise_with_ctx proxying\n");
int i = 0;
struct promise_ctx_widgets widgets;
init_widget(&widgets.w17, &i, 17);
init_widget(&widgets.w18, &i, 18);
init_widget(&widgets.w19, &i, 19);
pthread_create(&worker, NULL, do_test_proxy_promise_with_ctx, &widgets);
pthread_join(worker, NULL);
destroy_widget(&widgets.w17);
destroy_widget(&widgets.w18);
destroy_widget(&widgets.w19);
}
typedef struct increment_to_arg {
em_proxying_queue* queue;
int* ip;
int i;
} increment_to_arg;
void increment_to(void* arg_p) {
increment_to_arg* arg = (increment_to_arg*)arg_p;
// Try executing the queue; since the queue is already being executed, this
// shouldn't do anything and *arg->ip should still be one less than arg->i
// afterward.
emscripten_proxy_execute_queue(arg->queue);
assert(*arg->ip == arg->i - 1);
*arg->ip = arg->i;
free(arg);
}
void test_tasks_queue_growth(void) {
printf("Testing tasks queue growth\n");
em_proxying_queue* queue = em_proxying_queue_create();
assert(queue != NULL);
int incremented = 0;
// Initial queue capacity is 128. Force that to double twice with the head at
// index 0 by inserting more than 256 items.
for (int i = 1; i <= 300; i++) {
increment_to_arg* arg = malloc(sizeof(increment_to_arg));
*arg = (increment_to_arg){queue, &incremented, i};
int res = emscripten_proxy_async(queue, pthread_self(), increment_to, arg);
assert(res == 1);
}
// Drain the queue, moving the head somewhere into the middle of the buffer of
// capacity 512.
emscripten_proxy_execute_queue(queue);
assert(incremented == 300);
// Double the queue size twice more by inserting more than 1024 items.
for (int i = 301; i <= 1500; i++) {
increment_to_arg* arg = malloc(sizeof(increment_to_arg));
*arg = (increment_to_arg){queue, &incremented, i};
int res = emscripten_proxy_async(queue, pthread_self(), increment_to, arg);
assert(res == 1);
}
// Drain the queue again.
emscripten_proxy_execute_queue(queue);
assert(incremented == 1500);
em_proxying_queue_destroy(queue);
}
typedef struct proxying_queue_growth_arg {
em_proxying_queue* queue;
pthread_t a;
pthread_t b;
_Atomic int work_count;
} proxying_queue_growth_arg;
void trivial_work(void* arg) {
printf("work\n");
(*(_Atomic int*)arg)++;
}
void grow_proxying_queue(void* arg_p) {
// Add task_queues for two new threads, causing a reallocation of the
// `em_proxying_queue`'s task_queues array the first time this is called.
proxying_queue_growth_arg* arg = (proxying_queue_growth_arg*)arg_p;
emscripten_proxy_async(arg->queue, arg->a, trivial_work, &arg->work_count);
emscripten_proxy_async(arg->queue, arg->b, trivial_work, &arg->work_count);
}
void test_proxying_queue_growth(void) {
printf("Testing proxying queue growth\n");
proxying_queue_growth_arg arg;
arg.queue = em_proxying_queue_create();
assert(arg.queue != NULL);
pthread_create(&arg.a, NULL, returner_main, arg.queue);
pthread_create(&arg.b, NULL, returner_main, arg.queue);
arg.work_count = 0;
// Queue a task for the current thread, allocating an array of one task_queue.
// Then when the task is executed, work is queued on two new threads, bumping
// up the array size to 4 and causing it to be reallocated elsewhere. Make
// sure we correctly handle this reallocation in the middle of executing the
// queue.
emscripten_proxy_async(arg.queue, pthread_self(), grow_proxying_queue, &arg);
emscripten_proxy_execute_queue(arg.queue);
while (arg.work_count < 2) {
sched_yield();
}
// Do it again to make sure the queue was left in a valid state. Specifically,
// if the reallocation is not handled correctly, the recursion guard might not
// have been updated correctly, so the work will not be completed.
emscripten_proxy_async(arg.queue, pthread_self(), grow_proxying_queue, &arg);
emscripten_proxy_execute_queue(arg.queue);
while (arg.work_count < 4) {
sched_yield();
}
// Clean up.
pthread_cancel(arg.a);
pthread_cancel(arg.b);
pthread_join(arg.a, NULL);
pthread_join(arg.b, NULL);
em_proxying_queue_destroy(arg.queue);
}
int main(int argc, char* argv[]) {
main_thread = pthread_self();
proxy_queue = em_proxying_queue_create();
assert(proxy_queue != NULL);
pthread_create(&looper, NULL, looper_main, NULL);
pthread_create(&returner, NULL, returner_main, proxy_queue);
test_proxy_async();
test_proxy_sync();
test_proxy_sync_with_ctx();
test_proxy_callback();
test_proxy_callback_with_ctx();
test_proxy_promise();
test_proxy_promise_with_ctx();
should_quit = 1;
pthread_join(looper, NULL);
pthread_cancel(returner);
pthread_join(returner, NULL);
em_proxying_queue_destroy(proxy_queue);
test_tasks_queue_growth();
test_proxying_queue_growth();
printf("done\n");
}