Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions system/include/emscripten/proxying.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#pragma once

#include <emscripten/emscripten.h>
#include <emscripten/promise.h>
#include <pthread.h>

#ifdef __cplusplus
Expand Down Expand Up @@ -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
Comment thread
sbc100 marked this conversation as resolved.
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"

Expand Down
103 changes: 100 additions & 3 deletions system/lib/pthread/proxying.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -483,3 +484,99 @@ 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 that will be
// freed when the `em_proxying_ctx` is freed.
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);
}

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
// that will be freed when the `em_proxying_ctx` is freed.
struct block {
em_proxying_ctx ctx;
promise_ctx promise_ctx;
task task;
};
struct block* block = malloc(sizeof(*block));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these "blocks" freed? I don't see the block pointer escaping this function. Does it rely on ctx being free'd?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it relies on ctx being freed, which happens in all the places free_ctx is called.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment on the first eleemnt of the struct? e.g. // This struct is free'd via the ctx pointer so this must be the first element

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);
}
Loading