arudradey's picture
download
raw
23 kB
/*
* Copyright 2021 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*/
#include <assert.h>
#include <emscripten/proxying.h>
#include <emscripten/threading.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "em_task_queue.h"
#include "thread_mailbox.h"
#include "threading_internal.h"
struct em_proxying_queue {
// Protects all accesses to em_task_queues, size, and capacity.
pthread_mutex_t mutex;
// `size` task queue pointers stored in an array of size `capacity`.
em_task_queue** task_queues;
int size;
int capacity;
};
// The system proxying queue.
static em_proxying_queue system_proxying_queue = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.task_queues = NULL,
.size = 0,
.capacity = 0,
};
static _Thread_local bool system_queue_in_use = false;
em_proxying_queue* emscripten_proxy_get_system_queue(void) {
return &system_proxying_queue;
}
em_proxying_queue* em_proxying_queue_create(void) {
// Allocate the new queue.
em_proxying_queue* q = malloc(sizeof(em_proxying_queue));
if (q == NULL) {
return NULL;
}
*q = (em_proxying_queue){
.mutex = PTHREAD_MUTEX_INITIALIZER,
.task_queues = NULL,
.size = 0,
.capacity = 0,
};
return q;
}
void em_proxying_queue_destroy(em_proxying_queue* q) {
assert(q != NULL);
assert(q != &system_proxying_queue && "cannot destroy system proxying queue");
pthread_mutex_destroy(&q->mutex);
for (int i = 0; i < q->size; i++) {
em_task_queue_destroy(q->task_queues[i]);
}
free(q->task_queues);
free(q);
}
// Not thread safe. Returns NULL if there are no tasks for the thread.
static em_task_queue* get_tasks_for_thread(em_proxying_queue* q,
pthread_t thread) {
assert(q != NULL);
for (int i = 0; i < q->size; i++) {
if (pthread_equal(q->task_queues[i]->thread, thread)) {
return q->task_queues[i];
}
}
return NULL;
}
// Not thread safe.
static em_task_queue* get_or_add_tasks_for_thread(em_proxying_queue* q,
pthread_t thread) {
em_task_queue* tasks = get_tasks_for_thread(q, thread);
if (tasks != NULL) {
return tasks;
}
// There were no tasks for the thread; initialize a new em_task_queue. If
// there are not enough queues, allocate more.
if (q->size == q->capacity) {
int new_capacity = q->capacity == 0 ? 1 : q->capacity * 2;
em_task_queue** new_task_queues =
realloc(q->task_queues, sizeof(em_task_queue*) * new_capacity);
if (new_task_queues == NULL) {
return NULL;
}
q->task_queues = new_task_queues;
q->capacity = new_capacity;
}
// Initialize the next available task queue.
tasks = em_task_queue_create(thread);
if (tasks == NULL) {
return NULL;
}
q->task_queues[q->size++] = tasks;
return tasks;
}
void emscripten_proxy_execute_queue(em_proxying_queue* q) {
assert(q != NULL);
assert(pthread_self());
// Below is a recursion and deadlock guard: The recursion guard is to avoid
// infinite recursion when we arrive here from the pthread_lock call below
// that executes the system queue. The per-task_queue recursion lock can't
// catch these recursions because it can only be checked after the lock has
// been acquired.
//
// This also guards against deadlocks when adding to the system queue. When
// the current thread is adding tasks, it locks the queue, but we can
// potentially try to execute the queue during the add (from emscripten_yield
// when malloc takes a lock). This will deadlock the thread, so only try to
// take the lock if the current thread is not using the queue. We then hope
// the queue is executed later when it is unlocked.
bool is_system_queue = q == &system_proxying_queue;
if (is_system_queue) {
if (system_queue_in_use) {
return;
}
system_queue_in_use = true;
}
pthread_mutex_lock(&q->mutex);
em_task_queue* tasks = get_tasks_for_thread(q, pthread_self());
pthread_mutex_unlock(&q->mutex);
if (tasks != NULL && !tasks->processing) {
// Found the task queue and it is not already being processed; process it.
em_task_queue_execute(tasks);
}
if (is_system_queue) {
system_queue_in_use = false;
}
}
static bool do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) {
assert(q != NULL);
pthread_mutex_lock(&q->mutex);
bool is_system_queue = q == &system_proxying_queue;
if (is_system_queue) {
system_queue_in_use = true;
}
em_task_queue* tasks = get_or_add_tasks_for_thread(q, target_thread);
if (is_system_queue) {
system_queue_in_use = false;
}
pthread_mutex_unlock(&q->mutex);
if (tasks == NULL) {
return false;
}
bool ret = em_task_queue_send(tasks, t);
// When proxying work to the main thread using the system queue we have a
// special case in that we need to wake the target thread in case it is in
// `emscripten_futex_wait`.
if (ret && is_system_queue &&
pthread_equal(target_thread, emscripten_main_runtime_thread_id())) {
DBG("waking main runtime thread using _emscripten_thread_notify");
_emscripten_thread_notify(target_thread);
}
return ret;
}
bool emscripten_proxy_async(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(void*),
void* arg) {
return do_proxy(q, target_thread, (task){func, NULL, arg});
}
enum ctx_kind { SYNC, CALLBACK };
enum ctx_state { PENDING, DONE, CANCELED };
struct em_proxying_ctx {
// The user-provided function and argument.
void (*func)(em_proxying_ctx*, void*);
void* arg;
enum ctx_kind kind;
union {
// Context for synchronous proxying.
struct {
// Update `state` and signal the condition variable once the proxied task
// is done or canceled.
enum ctx_state state;
pthread_mutex_t mutex;
pthread_cond_t cond;
} sync;
// Context for proxying with callbacks.
struct {
em_proxying_queue* queue;
pthread_t caller_thread;
void (*callback)(void*);
void (*cancel)(void*);
} cb;
};
// A doubly linked list of contexts associated with active work on a single
// thread. If the thread is canceled, it will traverse this list to find
// contexts that need to be canceled.
struct em_proxying_ctx* next;
struct em_proxying_ctx* prev;
};
// The key that `cancel_active_ctxs` is bound to so that it runs when a thread
// is canceled or exits.
static pthread_key_t active_ctxs;
static pthread_once_t active_ctxs_once = PTHREAD_ONCE_INIT;
static void cancel_ctx(void* arg);
static void cancel_active_ctxs(void* arg);
static void init_active_ctxs(void) {
int ret = pthread_key_create(&active_ctxs, cancel_active_ctxs);
assert(ret == 0);
(void)ret;
}
static void add_active_ctx(em_proxying_ctx* ctx) {
assert(ctx != NULL);
em_proxying_ctx* head = pthread_getspecific(active_ctxs);
if (head == NULL) {
// This is the only active context; initialize the active contexts list.
ctx->next = ctx->prev = ctx;
pthread_setspecific(active_ctxs, ctx);
} else {
// Insert this context at the tail of the list just before `head`.
ctx->next = head;
ctx->prev = head->prev;
ctx->next->prev = ctx;
ctx->prev->next = ctx;
}
}
static void remove_active_ctx(em_proxying_ctx* ctx) {
assert(ctx != NULL);
assert(ctx->next != NULL);
assert(ctx->prev != NULL);
if (ctx->next == ctx) {
// This is the only active context; clear the active contexts list.
ctx->next = ctx->prev = NULL;
pthread_setspecific(active_ctxs, NULL);
return;
}
// Update the list head if we are removing the current head.
em_proxying_ctx* head = pthread_getspecific(active_ctxs);
if (ctx == head) {
pthread_setspecific(active_ctxs, head->next);
}
// Remove the context from the list.
ctx->prev->next = ctx->next;
ctx->next->prev = ctx->prev;
ctx->next = ctx->prev = NULL;
}
static void cancel_active_ctxs(void* arg) {
pthread_setspecific(active_ctxs, NULL);
em_proxying_ctx* head = arg;
em_proxying_ctx* curr = head;
do {
em_proxying_ctx* next = curr->next;
cancel_ctx(curr);
curr = next;
} while (curr != head);
}
static void em_proxying_ctx_init_sync(em_proxying_ctx* ctx,
void (*func)(em_proxying_ctx*, void*),
void* arg) {
pthread_once(&active_ctxs_once, init_active_ctxs);
*ctx = (em_proxying_ctx){
.func = func,
.arg = arg,
.kind = SYNC,
.sync =
{
.state = PENDING,
.mutex = PTHREAD_MUTEX_INITIALIZER,
.cond = PTHREAD_COND_INITIALIZER,
},
};
}
static void em_proxying_ctx_init_callback(em_proxying_ctx* ctx,
em_proxying_queue* queue,
pthread_t caller_thread,
void (*func)(em_proxying_ctx*, void*),
void (*callback)(void*),
void (*cancel)(void*),
void* arg) {
pthread_once(&active_ctxs_once, init_active_ctxs);
*ctx = (em_proxying_ctx){
.func = func,
.arg = arg,
.kind = CALLBACK,
.cb =
{
.queue = queue,
.caller_thread = caller_thread,
.callback = callback,
.cancel = cancel,
},
};
}
static void em_proxying_ctx_deinit(em_proxying_ctx* ctx) {
if (ctx->kind == SYNC) {
pthread_mutex_destroy(&ctx->sync.mutex);
pthread_cond_destroy(&ctx->sync.cond);
}
// TODO: We should probably have some kind of refcounting scheme to keep
// `queue` alive for callback ctxs.
}
static void free_ctx(void* arg) {
em_proxying_ctx* ctx = arg;
em_proxying_ctx_deinit(ctx);
free(ctx);
}
// Free the callback info on the same thread it was originally allocated on.
// This may be more efficient.
static void call_callback_then_free_ctx(void* arg) {
em_proxying_ctx* ctx = arg;
ctx->cb.callback(ctx->arg);
free_ctx(ctx);
}
void emscripten_proxy_finish(em_proxying_ctx* ctx) {
if (ctx->kind == SYNC) {
pthread_mutex_lock(&ctx->sync.mutex);
ctx->sync.state = DONE;
remove_active_ctx(ctx);
// Signal must come before unlock to avoid emscripten_proxy_sync_with ctx
// seeing the state as DONE and freeing the ctx before we call unlock.
// See https://github.com/emscripten-core/emscripten/pull/26582
pthread_cond_signal(&ctx->sync.cond);
pthread_mutex_unlock(&ctx->sync.mutex);
} else {
// Schedule the callback on the caller thread. If the caller thread has
// already died or dies before the callback is executed, then at least make
// sure the context is freed.
remove_active_ctx(ctx);
if (!do_proxy(ctx->cb.queue,
ctx->cb.caller_thread,
(task){call_callback_then_free_ctx, free_ctx, ctx})) {
free_ctx(ctx);
}
}
}
static void call_cancel_then_free_ctx(void* arg) {
em_proxying_ctx* ctx = arg;
ctx->cb.cancel(ctx->arg);
free_ctx(ctx);
}
static void cancel_ctx(void* arg) {
em_proxying_ctx* ctx = arg;
if (ctx->kind == SYNC) {
pthread_mutex_lock(&ctx->sync.mutex);
ctx->sync.state = CANCELED;
// Signal must be first, see comment in emscripten_proxy_finish.
pthread_cond_signal(&ctx->sync.cond);
pthread_mutex_unlock(&ctx->sync.mutex);
} else {
if (ctx->cb.cancel == NULL ||
!do_proxy(ctx->cb.queue,
ctx->cb.caller_thread,
(task){call_cancel_then_free_ctx, free_ctx, ctx})) {
free_ctx(ctx);
}
}
}
// Helper for wrapping the call with ctx as a `void (*)(void*)`.
static void call_with_ctx(void* arg) {
em_proxying_ctx* ctx = arg;
add_active_ctx(ctx);
ctx->func(ctx, ctx->arg);
}
bool emscripten_proxy_sync_with_ctx(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(em_proxying_ctx*, void*),
void* arg) {
assert(!pthread_equal(target_thread, pthread_self()) &&
"Cannot synchronously wait for work proxied to the current thread");
em_proxying_ctx ctx;
em_proxying_ctx_init_sync(&ctx, func, arg);
if (!do_proxy(q, target_thread, (task){call_with_ctx, cancel_ctx, &ctx})) {
em_proxying_ctx_deinit(&ctx);
return false;
}
pthread_mutex_lock(&ctx.sync.mutex);
while (ctx.sync.state == PENDING) {
pthread_cond_wait(&ctx.sync.cond, &ctx.sync.mutex);
}
pthread_mutex_unlock(&ctx.sync.mutex);
int ret = ctx.sync.state == DONE;
em_proxying_ctx_deinit(&ctx);
return ret;
}
// Helper for signaling the end of the task after the user function returns.
static void call_then_finish_task(em_proxying_ctx* ctx, void* arg) {
task* t = arg;
t->func(t->arg);
emscripten_proxy_finish(ctx);
}
bool emscripten_proxy_sync(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(void*),
void* arg) {
task t = {.func = func, .arg = arg};
return emscripten_proxy_sync_with_ctx(
q, target_thread, call_then_finish_task, &t);
}
static bool do_proxy_callback(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(em_proxying_ctx* ctx, void*),
void (*callback)(void*),
void (*cancel)(void*),
void* arg,
em_proxying_ctx* ctx) {
em_proxying_ctx_init_callback(
ctx, q, pthread_self(), func, callback, cancel, arg);
if (!do_proxy(q, target_thread, (task){call_with_ctx, cancel_ctx, ctx})) {
free_ctx(ctx);
return false;
}
return true;
}
bool emscripten_proxy_callback_with_ctx(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(em_proxying_ctx* ctx,
void*),
void (*callback)(void*),
void (*cancel)(void*),
void* arg) {
em_proxying_ctx* ctx = malloc(sizeof(*ctx));
if (ctx == NULL) {
return false;
}
return do_proxy_callback(q, target_thread, func, callback, cancel, arg, ctx);
}
typedef struct callback_ctx {
void (*func)(void*);
void (*callback)(void*);
void (*cancel)(void*);
void* arg;
} callback_ctx;
static void call_then_finish_callback(em_proxying_ctx* ctx, void* arg) {
callback_ctx* cb_ctx = arg;
cb_ctx->func(cb_ctx->arg);
emscripten_proxy_finish(ctx);
}
static void callback_call(void* arg) {
callback_ctx* cb_ctx = arg;
cb_ctx->callback(cb_ctx->arg);
}
static void callback_cancel(void* arg) {
callback_ctx* cb_ctx = arg;
if (cb_ctx->cancel != NULL) {
cb_ctx->cancel(cb_ctx->arg);
}
}
bool emscripten_proxy_callback(em_proxying_queue* q,
pthread_t target_thread,
void (*func)(void*),
void (*callback)(void*),
void (*cancel)(void*),
void* arg) {
// 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;
};
struct block* block = malloc(sizeof(*block));
if (block == NULL) {
return false;
}
block->cb_ctx = (callback_ctx){func, callback, cancel, arg};
return do_proxy_callback(q,
target_thread,
call_then_finish_callback,
callback_call,
callback_cancel,
&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));
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);
}
typedef struct proxied_js_func_t {
int funcIndex;
void* emAsmAddr;
pthread_t callingThread;
int bufSize;
double* argBuffer;
double result;
bool owned;
} proxied_js_func_t;
static void run_js_func(void* arg) {
proxied_js_func_t* f = (proxied_js_func_t*)arg;
f->result = _emscripten_receive_on_main_thread_js(
f->funcIndex, f->emAsmAddr, f->callingThread, f->bufSize, f->argBuffer, 0, 0);
if (f->owned) {
free(f->argBuffer);
free(f);
}
}
static void run_js_func_with_ctx(em_proxying_ctx* ctx, void* arg) {
proxied_js_func_t* f = (proxied_js_func_t*)arg;
_emscripten_receive_on_main_thread_js(
f->funcIndex, f->emAsmAddr, f->callingThread, f->bufSize, f->argBuffer, ctx, arg);
// run_js_func_with_ctx is always synchronously proxied and therefore arg
// should never be owned on the main thread (i.e. the argument here always
// exists on the stack of the calling thread, it's never copied/malloced).
assert(!f->owned);
}
void _emscripten_run_js_on_main_thread_done(void* ctx, void* arg, double result) {
proxied_js_func_t* f = (proxied_js_func_t*)arg;
f->result = result;
emscripten_proxy_finish(ctx);
}
/*
* The 'proxy_mode' argument to _emscripten_run_js_on_main_thread has 3 possible
* values:
*
* - PROXY_ASYNC: Returns immediately on the calling thread, does not signal
* - PROXY_SYNC: Synchronous on the calling thread, and also on the main thread
* - PROXY_SYNC_ASYNC: Synchronous on the calling thread, but async on the main
* thread.
*
* Note: 'PROXY_SYNC_ASYNC' is only passed when a function is marked as
* both "__async" and "__proxy: 'sync'"
*/
#define PROXY_ASYNC 0
#define PROXY_SYNC 1
#define PROXY_SYNC_ASYNC 2
double _emscripten_run_js_on_main_thread(int func_index,
void* em_asm_addr,
int buf_size,
double* buffer,
int proxyMode) {
proxied_js_func_t f = {
.funcIndex = func_index,
.emAsmAddr = em_asm_addr,
.callingThread = pthread_self(),
.bufSize = buf_size,
.argBuffer = buffer,
.owned = false,
};
em_proxying_queue* q = emscripten_proxy_get_system_queue();
pthread_t target = emscripten_main_runtime_thread_id();
if (proxyMode != PROXY_ASYNC) {
int rtn;
if (proxyMode == PROXY_SYNC_ASYNC) {
rtn = emscripten_proxy_sync_with_ctx(q, target, run_js_func_with_ctx, &f);
} else {
rtn = emscripten_proxy_sync(q, target, run_js_func, &f);
}
if (!rtn) {
assert(false && "emscripten_proxy_sync_with_ctx failed");
return 0;
}
return f.result;
}
// Make a heap allocated copy of the proxied_js_func_t
proxied_js_func_t* arg = malloc(sizeof(proxied_js_func_t));
*arg = f;
arg->owned = true;
// Also make a copy of the argBuffer.
arg->argBuffer = malloc(buf_size);
memcpy(arg->argBuffer, buffer, buf_size);
if (!emscripten_proxy_async(q, target, run_js_func, arg)) {
assert(false && "emscripten_proxy_async failed");
}
return 0;
}

Xet Storage Details

Size:
23 kB
·
Xet hash:
5a73b711d365bacee28dac0a90cd5f20ea6f1646864066a37f9d7e61966d8885

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.