From c27bf7aac027e3ed230e1e25484e22ff54b680f6 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 22 Feb 2023 10:59:35 -0800 Subject: [PATCH 1/2] [Proxying] Add proxying functions that return promises Add `emscripten_proxy_promise` and `emscripten_proxy_promise_with_ctx` that return `em_promise_t` handles to promises that are fulfilled when the proxied work is completed or rejected when the target thread dies before it can complete the work. Do not document these new APIs yet since they depend on the promise API in emscripten/promise.h, which is not yet documented. Similarly, do not add a C++ wrapper yet because emscripten/promise.h does not yet have a C++ API. --- system/include/emscripten/proxying.h | 13 ++ system/lib/pthread/proxying.c | 99 ++++++++- test/pthread/test_pthread_proxying.c | 198 +++++++++++++++++- test/pthread/test_pthread_proxying.out | 8 + .../test_pthread_proxying_canceled_work.c | 175 +++++++--------- .../test_pthread_proxying_canceled_work.out | 2 - 6 files changed, 389 insertions(+), 106 deletions(-) diff --git a/system/include/emscripten/proxying.h b/system/include/emscripten/proxying.h index bdcc328a55e8a..db58f27386049 100644 --- a/system/include/emscripten/proxying.h +++ b/system/include/emscripten/proxying.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #ifdef __cplusplus @@ -102,6 +103,18 @@ int emscripten_proxy_callback_with_ctx(em_proxying_queue* q, void (*cancel)(void*), void* arg); +__attribute__((warn_unused_result)) em_promise_t +emscripten_proxy_promise(em_proxying_queue* q, + pthread_t target_thread, + void (*func)(void*), + void* arg); + +__attribute__((warn_unused_result)) em_promise_t +emscripten_proxy_promise_with_ctx(em_proxying_queue* q, + pthread_t target_thread, + void (*func)(em_proxying_ctx*, void*), + void* arg); + #ifdef __cplusplus } // extern "C" diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 1fdd69c38d895..1556b59b51104 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -389,7 +389,7 @@ int emscripten_proxy_sync_with_ctx(em_proxying_queue* q, } // Helper for signaling the end of the task after the user function returns. -static void call_then_finish_sync(em_proxying_ctx* ctx, void* arg) { +static void call_then_finish_task(em_proxying_ctx* ctx, void* arg) { task* t = arg; t->func(t->arg); emscripten_proxy_finish(ctx); @@ -401,7 +401,7 @@ int emscripten_proxy_sync(em_proxying_queue* q, void* arg) { task t = {.func = func, .arg = arg}; return emscripten_proxy_sync_with_ctx( - q, target_thread, call_then_finish_sync, &t); + q, target_thread, call_then_finish_task, &t); } static int do_proxy_callback(em_proxying_queue* q, @@ -483,3 +483,98 @@ int emscripten_proxy_callback(em_proxying_queue* q, &block->cb_ctx, &block->ctx); } + +typedef struct promise_ctx { + void (*func)(em_proxying_ctx*, void*); + void* arg; + em_promise_t promise; +} promise_ctx; + +static void promise_call(em_proxying_ctx* ctx, void* arg) { + promise_ctx* promise_ctx = arg; + promise_ctx->func(ctx, promise_ctx->arg); +} + +static void promise_fulfill(void* arg) { + promise_ctx* promise_ctx = arg; + emscripten_promise_resolve(promise_ctx->promise, EM_PROMISE_FULFILL, NULL); + emscripten_promise_destroy(promise_ctx->promise); +} + +static void promise_reject(void* arg) { + promise_ctx* promise_ctx = arg; + emscripten_promise_resolve(promise_ctx->promise, EM_PROMISE_REJECT, NULL); + emscripten_promise_destroy(promise_ctx->promise); +} + +static em_promise_t do_proxy_promise(em_proxying_queue* q, + pthread_t target_thread, + void (*func)(em_proxying_ctx*, void*), + void* arg, + em_promise_t promise, + em_proxying_ctx* ctx, + promise_ctx* promise_ctx) { + *promise_ctx = (struct promise_ctx){func, arg, promise}; + if (!do_proxy_callback(q, + target_thread, + promise_call, + promise_fulfill, + promise_reject, + promise_ctx, + ctx)) { + emscripten_promise_resolve(promise, EM_PROMISE_REJECT, NULL); + return promise; + } + // Return a separate promise to ensure that the internal promise will stay + // alive until the callbacks are called. + em_promise_t ret = emscripten_promise_create(); + emscripten_promise_resolve(ret, EM_PROMISE_MATCH, promise); + return ret; +} + +em_promise_t emscripten_proxy_promise_with_ctx(em_proxying_queue* q, + pthread_t target_thread, + void (*func)(em_proxying_ctx*, + void*), + void* arg) { + em_promise_t promise = emscripten_promise_create(); + // Allocate the em_proxying_ctx and promise ctx as a single block. + struct block { + em_proxying_ctx ctx; + promise_ctx promise_ctx; + }; + struct block* block = malloc(sizeof(*block)); + if (block == NULL) { + emscripten_promise_resolve(promise, EM_PROMISE_REJECT, NULL); + return promise; + } + return do_proxy_promise( + q, target_thread, func, arg, promise, &block->ctx, &block->promise_ctx); +} + +__attribute__((warn_unused_result)) em_promise_t +emscripten_proxy_promise(em_proxying_queue* q, + pthread_t target_thread, + void (*func)(void*), + void* arg) { + em_promise_t promise = emscripten_promise_create(); + // Allocate the em_proxying_ctx, promise ctx, and user task as a single block. + struct block { + em_proxying_ctx ctx; + promise_ctx promise_ctx; + task task; + }; + struct block* block = malloc(sizeof(*block)); + if (block == NULL) { + emscripten_promise_resolve(promise, EM_PROMISE_REJECT, NULL); + return promise; + } + block->task = (task){.func = func, .arg = arg}; + return do_proxy_promise(q, + target_thread, + call_then_finish_task, + &block->task, + promise, + &block->ctx, + &block->promise_ctx); +} diff --git a/test/pthread/test_pthread_proxying.c b/test/pthread/test_pthread_proxying.c index 2e931f64e47c4..8920da0be653e 100644 --- a/test/pthread/test_pthread_proxying.c +++ b/test/pthread/test_pthread_proxying.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,9 @@ 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; @@ -51,7 +55,7 @@ typedef struct widget { // Nonzero iff the widget has been run. int done; - // Only used for async_as_sync tests. + // Only used for *_with_ctx tests. em_proxying_ctx* ctx; } widget; @@ -75,6 +79,7 @@ void run_widget(widget* w) { 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); @@ -115,6 +120,13 @@ void start_and_finish_running_widget(em_proxying_ctx* ctx, void* arg) { 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) { @@ -220,7 +232,6 @@ void test_proxy_callback(void) { 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); @@ -272,7 +283,6 @@ void test_proxy_callback_with_ctx(void) { 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); @@ -314,6 +324,186 @@ void test_proxy_callback_with_ctx(void) { 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; @@ -446,6 +636,8 @@ int main(int argc, char* argv[]) { 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); diff --git a/test/pthread/test_pthread_proxying.out b/test/pthread/test_pthread_proxying.out index 9b445fbb4342e..4f1862dad0b4b 100644 --- a/test/pthread/test_pthread_proxying.out +++ b/test/pthread/test_pthread_proxying.out @@ -16,6 +16,14 @@ Testing callback_with_ctx proxying running widget 11 on main running widget 12 on looper running widget 13 on returner +Testing promise proxying +running widget 14 on worker +running widget 15 on looper +running widget 16 on returner +Testing promise_with_ctx proxying +running widget 17 on worker +running widget 18 on looper +running widget 19 on returner Testing tasks queue growth Testing proxying queue growth work diff --git a/test/pthread/test_pthread_proxying_canceled_work.c b/test/pthread/test_pthread_proxying_canceled_work.c index 816dcfbb23ad7..c21a19553f495 100644 --- a/test/pthread/test_pthread_proxying_canceled_work.c +++ b/test/pthread/test_pthread_proxying_canceled_work.c @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -7,6 +9,30 @@ em_proxying_queue* queue; void explode(void* arg) { assert(0 && "the work should not be run!"); } +em_promise_result_t explode_reject(void** result, void* data, void* val) { + assert(0 && "unexpected promise result"); + return EM_PROMISE_REJECT; +} + +em_promise_result_t fulfill(void** result, void* data, void* val) { + return EM_PROMISE_FULFILL; +} + +void fulfill_promise(void* arg) { + em_promise_t promise = arg; + emscripten_promise_resolve(promise, EM_PROMISE_FULFILL, NULL); +} + +// The promises we need to wait for at the end of the test. +#define MAX_PROMISES 6 +int promise_count = 0; +em_promise_t promises[MAX_PROMISES]; + +void add_promise(em_promise_t promise) { + assert(promise_count < MAX_PROMISES); + promises[promise_count++] = promise; +} + void set_flag(void* flag) { // Schedule the flag to be set on the next turn of the event loop so that we // can be sure cleanup has finished first. We need to use EM_ASM and JS here @@ -53,6 +79,11 @@ void test_cancel_then_proxy() { emscripten_proxy_callback(queue, thread, explode, explode, explode, NULL); assert(ret == 0); + // Or should result in a rejected promise. + em_promise_t promise = emscripten_proxy_promise(queue, thread, explode, NULL); + add_promise(emscripten_promise_then(promise, explode_reject, fulfill, NULL)); + emscripten_promise_destroy(promise); + pthread_join(thread, NULL); } @@ -74,6 +105,11 @@ void test_exit_then_proxy() { emscripten_proxy_callback(queue, thread, explode, explode, explode, NULL); assert(ret == 0); + // Or should result in a rejected promise. + em_promise_t promise = emscripten_proxy_promise(queue, thread, explode, NULL); + add_promise(emscripten_promise_then(promise, explode_reject, fulfill, NULL)); + emscripten_promise_destroy(promise); + pthread_join(thread, NULL); } @@ -117,7 +153,17 @@ void test_proxy_then_cancel() { } // The pending proxied work should be canceled when the thread is canceled. - int ret = emscripten_proxy_sync(queue, thread, explode, NULL); + em_promise_t promise = emscripten_proxy_promise(queue, thread, explode, NULL); + add_promise(emscripten_promise_then(promise, explode_reject, fulfill, NULL)); + emscripten_promise_destroy(promise); + + promise = emscripten_promise_create(); + int ret = emscripten_proxy_callback( + queue, thread, explode, explode, fulfill_promise, promise); + assert(ret == 1); + add_promise(promise); + + ret = emscripten_proxy_sync(queue, thread, explode, NULL); assert(ret == 0); pthread_join(thread, NULL); @@ -134,104 +180,20 @@ void test_proxy_then_exit() { } // The pending proxied work should be canceled when the thread exits. - int ret = emscripten_proxy_sync(queue, thread, explode, NULL); - assert(ret == 0); - - pthread_join(thread, NULL); -} - -struct callback_info { - pthread_t worker; - pthread_t proxier; - _Atomic int worker_running; - _Atomic int should_exit; - _Atomic int callback_called; -}; - -void* report_running_then_cancel(void* arg) { - struct callback_info* info = arg; - - info->worker_running = 1; - - while (!info->should_exit) { - } - - // The callback will never be dequeued because we exit before returning to the - // event loop. - pthread_cancel(pthread_self()); - pthread_testcancel(); - assert(0 && "thread should have been canceled!"); - return NULL; -} - -void* report_running_then_exit(void* arg) { - struct callback_info* info = arg; - - info->worker_running = 1; - - while (!info->should_exit) { - } - - // The callback will never be dequeued because we exit before returning to the - // event loop. - pthread_exit(NULL); - assert(0 && "thread should have been canceled!"); - return NULL; -} - -void cancel_callback(void* arg) { - struct callback_info* info = arg; - info->callback_called = 1; -} - -void* proxy_with_callback(void* arg) { - struct callback_info* info = arg; - - while (!info->worker_running) { - } + em_promise_t promise = emscripten_proxy_promise(queue, thread, explode, NULL); + add_promise(emscripten_promise_then(promise, explode_reject, fulfill, NULL)); + emscripten_promise_destroy(promise); + promise = emscripten_promise_create(); int ret = emscripten_proxy_callback( - queue, info->worker, explode, explode, cancel_callback, info); + queue, thread, explode, explode, fulfill_promise, promise); assert(ret == 1); + add_promise(promise); - info->should_exit = 1; - - // Keep runtime alive so we can receive the cancellation callback. - emscripten_exit_with_live_runtime(); -} - -void test_proxy_callback_then_cancel() { - printf("testing callback proxy followed by cancel\n"); - - struct callback_info info = {0}; - - pthread_create(&info.worker, NULL, report_running_then_cancel, &info); - pthread_create(&info.proxier, NULL, proxy_with_callback, &info); - - while (!info.callback_called) { - } - - pthread_join(info.worker, NULL); - - pthread_cancel(info.proxier); - pthread_join(info.proxier, NULL); -} - -void test_proxy_callback_then_exit() { - printf("testing callback proxy followed by exit\n"); - - struct callback_info info = {0}; - - pthread_create(&info.worker, NULL, report_running_then_exit, &info); - pthread_create(&info.proxier, NULL, proxy_with_callback, &info); - - while (!info.callback_called) { - } - - pthread_join(info.worker, NULL); + ret = emscripten_proxy_sync(queue, thread, explode, NULL); + assert(ret == 0); - pthread_cancel(info.proxier); - pthread_join(info.proxier, NULL); + pthread_join(thread, NULL); } enum proxy_kind { SYNC, CALLBACK }; @@ -369,6 +331,19 @@ void test_cancel_in_progress() { } } +void do_exit(void* arg) { emscripten_runtime_keepalive_pop(); } + +em_promise_result_t cleanup(void** result, void* data, void* val) { + for (int i = 0; i < MAX_PROMISES; i++) { + emscripten_promise_destroy(promises[i]); + } + em_proxying_queue_destroy(queue); + // TODO: exit directly here + emscripten_async_call(do_exit, NULL, 0); + printf("done\n"); + return EM_PROMISE_FULFILL; +} + int main() { queue = em_proxying_queue_create(); pthread_key_create(&dtor_key, set_flag); @@ -377,12 +352,14 @@ int main() { test_exit_then_proxy(); test_proxy_then_cancel(); test_proxy_then_exit(); - test_proxy_callback_then_cancel(); - test_proxy_callback_then_exit(); test_cancel_in_progress(); - em_proxying_queue_destroy(queue); - - printf("done\n"); + // Wait for the promises to resolve. + assert(promise_count == MAX_PROMISES); + em_promise_t done = emscripten_promise_all(promises, NULL, MAX_PROMISES); + emscripten_promise_destroy( + emscripten_promise_then(done, cleanup, explode_reject, NULL)); + emscripten_promise_destroy(done); + emscripten_runtime_keepalive_push(); } diff --git a/test/pthread/test_pthread_proxying_canceled_work.out b/test/pthread/test_pthread_proxying_canceled_work.out index 07e0a9bc8d04c..861beed8b0f42 100644 --- a/test/pthread/test_pthread_proxying_canceled_work.out +++ b/test/pthread/test_pthread_proxying_canceled_work.out @@ -2,8 +2,6 @@ testing cancel followed by proxy testing exit followed by proxy testing proxy followed by cancel testing proxy followed by exit -testing callback proxy followed by cancel -testing callback proxy followed by exit testing cancellation of in-progress work checking pattern 0 finishing task 0 From c3c38e77fe64d9ef29f4380aeca861f19bfc1781 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 3 Mar 2023 16:25:31 -0800 Subject: [PATCH 2/2] address comments --- system/lib/pthread/proxying.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/system/lib/pthread/proxying.c b/system/lib/pthread/proxying.c index 1556b59b51104..5a10c08a6e758 100644 --- a/system/lib/pthread/proxying.c +++ b/system/lib/pthread/proxying.c @@ -465,7 +465,8 @@ int emscripten_proxy_callback(em_proxying_queue* q, void (*callback)(void*), void (*cancel)(void*), void* arg) { - // Allocate the em_proxying_ctx and the user ctx as a single block. + // Allocate the em_proxying_ctx and the user ctx as a single block that will + // be freed when the `em_proxying_ctx` is freed. struct block { em_proxying_ctx ctx; callback_ctx cb_ctx; @@ -538,7 +539,8 @@ em_promise_t emscripten_proxy_promise_with_ctx(em_proxying_queue* q, void*), void* arg) { em_promise_t promise = emscripten_promise_create(); - // Allocate the em_proxying_ctx and promise ctx as a single block. + // Allocate the em_proxying_ctx and promise ctx as a single block that will be + // freed when the `em_proxying_ctx` is freed. struct block { em_proxying_ctx ctx; promise_ctx promise_ctx; @@ -552,13 +554,13 @@ em_promise_t emscripten_proxy_promise_with_ctx(em_proxying_queue* q, q, target_thread, func, arg, promise, &block->ctx, &block->promise_ctx); } -__attribute__((warn_unused_result)) em_promise_t -emscripten_proxy_promise(em_proxying_queue* q, - pthread_t target_thread, - void (*func)(void*), - void* arg) { +em_promise_t emscripten_proxy_promise(em_proxying_queue* q, + pthread_t target_thread, + void (*func)(void*), + void* arg) { em_promise_t promise = emscripten_promise_create(); - // Allocate the em_proxying_ctx, promise ctx, and user task as a single block. + // Allocate the em_proxying_ctx, promise ctx, and user task as a single block + // that will be freed when the `em_proxying_ctx` is freed. struct block { em_proxying_ctx ctx; promise_ctx promise_ctx;