Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/pthread/proxying.c
6175 views
1
/*
2
* Copyright 2021 The Emscripten Authors. All rights reserved.
3
* Emscripten is available under two separate licenses, the MIT license and the
4
* University of Illinois/NCSA Open Source License. Both these licenses can be
5
* found in the LICENSE file.
6
*/
7
8
#include <assert.h>
9
#include <emscripten/proxying.h>
10
#include <emscripten/threading.h>
11
#include <pthread.h>
12
#include <stdatomic.h>
13
#include <stdbool.h>
14
#include <stdlib.h>
15
#include <string.h>
16
17
#include "em_task_queue.h"
18
#include "thread_mailbox.h"
19
#include "threading_internal.h"
20
21
struct em_proxying_queue {
22
// Protects all accesses to em_task_queues, size, and capacity.
23
pthread_mutex_t mutex;
24
// `size` task queue pointers stored in an array of size `capacity`.
25
em_task_queue** task_queues;
26
int size;
27
int capacity;
28
};
29
30
// The system proxying queue.
31
static em_proxying_queue system_proxying_queue = {
32
.mutex = PTHREAD_MUTEX_INITIALIZER,
33
.task_queues = NULL,
34
.size = 0,
35
.capacity = 0,
36
};
37
38
static _Thread_local bool system_queue_in_use = false;
39
40
em_proxying_queue* emscripten_proxy_get_system_queue(void) {
41
return &system_proxying_queue;
42
}
43
44
em_proxying_queue* em_proxying_queue_create(void) {
45
// Allocate the new queue.
46
em_proxying_queue* q = malloc(sizeof(em_proxying_queue));
47
if (q == NULL) {
48
return NULL;
49
}
50
*q = (em_proxying_queue){
51
.mutex = PTHREAD_MUTEX_INITIALIZER,
52
.task_queues = NULL,
53
.size = 0,
54
.capacity = 0,
55
};
56
return q;
57
}
58
59
void em_proxying_queue_destroy(em_proxying_queue* q) {
60
assert(q != NULL);
61
assert(q != &system_proxying_queue && "cannot destroy system proxying queue");
62
63
pthread_mutex_destroy(&q->mutex);
64
for (int i = 0; i < q->size; i++) {
65
em_task_queue_destroy(q->task_queues[i]);
66
}
67
free(q->task_queues);
68
free(q);
69
}
70
71
// Not thread safe. Returns NULL if there are no tasks for the thread.
72
static em_task_queue* get_tasks_for_thread(em_proxying_queue* q,
73
pthread_t thread) {
74
assert(q != NULL);
75
for (int i = 0; i < q->size; i++) {
76
if (pthread_equal(q->task_queues[i]->thread, thread)) {
77
return q->task_queues[i];
78
}
79
}
80
return NULL;
81
}
82
83
// Not thread safe.
84
static em_task_queue* get_or_add_tasks_for_thread(em_proxying_queue* q,
85
pthread_t thread) {
86
em_task_queue* tasks = get_tasks_for_thread(q, thread);
87
if (tasks != NULL) {
88
return tasks;
89
}
90
// There were no tasks for the thread; initialize a new em_task_queue. If
91
// there are not enough queues, allocate more.
92
if (q->size == q->capacity) {
93
int new_capacity = q->capacity == 0 ? 1 : q->capacity * 2;
94
em_task_queue** new_task_queues =
95
realloc(q->task_queues, sizeof(em_task_queue*) * new_capacity);
96
if (new_task_queues == NULL) {
97
return NULL;
98
}
99
q->task_queues = new_task_queues;
100
q->capacity = new_capacity;
101
}
102
// Initialize the next available task queue.
103
tasks = em_task_queue_create(thread);
104
if (tasks == NULL) {
105
return NULL;
106
}
107
q->task_queues[q->size++] = tasks;
108
return tasks;
109
}
110
111
void emscripten_proxy_execute_queue(em_proxying_queue* q) {
112
assert(q != NULL);
113
assert(pthread_self());
114
115
// Below is a recursion and deadlock guard: The recursion guard is to avoid
116
// infinite recursion when we arrive here from the pthread_lock call below
117
// that executes the system queue. The per-task_queue recursion lock can't
118
// catch these recursions because it can only be checked after the lock has
119
// been acquired.
120
//
121
// This also guards against deadlocks when adding to the system queue. When
122
// the current thread is adding tasks, it locks the queue, but we can
123
// potentially try to execute the queue during the add (from emscripten_yield
124
// when malloc takes a lock). This will deadlock the thread, so only try to
125
// take the lock if the current thread is not using the queue. We then hope
126
// the queue is executed later when it is unlocked.
127
bool is_system_queue = q == &system_proxying_queue;
128
if (is_system_queue) {
129
if (system_queue_in_use) {
130
return;
131
}
132
system_queue_in_use = true;
133
}
134
135
pthread_mutex_lock(&q->mutex);
136
em_task_queue* tasks = get_tasks_for_thread(q, pthread_self());
137
pthread_mutex_unlock(&q->mutex);
138
139
if (tasks != NULL && !tasks->processing) {
140
// Found the task queue and it is not already being processed; process it.
141
em_task_queue_execute(tasks);
142
}
143
144
if (is_system_queue) {
145
system_queue_in_use = false;
146
}
147
}
148
149
static int do_proxy(em_proxying_queue* q, pthread_t target_thread, task t) {
150
assert(q != NULL);
151
pthread_mutex_lock(&q->mutex);
152
bool is_system_queue = q == &system_proxying_queue;
153
if (is_system_queue) {
154
system_queue_in_use = true;
155
}
156
em_task_queue* tasks = get_or_add_tasks_for_thread(q, target_thread);
157
if (is_system_queue) {
158
system_queue_in_use = false;
159
}
160
pthread_mutex_unlock(&q->mutex);
161
if (tasks == NULL) {
162
return 0;
163
}
164
165
return em_task_queue_send(tasks, t);
166
}
167
168
int emscripten_proxy_async(em_proxying_queue* q,
169
pthread_t target_thread,
170
void (*func)(void*),
171
void* arg) {
172
return do_proxy(q, target_thread, (task){func, NULL, arg});
173
}
174
175
enum ctx_kind { SYNC, CALLBACK };
176
177
enum ctx_state { PENDING, DONE, CANCELED };
178
179
struct em_proxying_ctx {
180
// The user-provided function and argument.
181
void (*func)(em_proxying_ctx*, void*);
182
void* arg;
183
184
enum ctx_kind kind;
185
union {
186
// Context for synchronous proxying.
187
struct {
188
// Update `state` and signal the condition variable once the proxied task
189
// is done or canceled.
190
enum ctx_state state;
191
pthread_mutex_t mutex;
192
pthread_cond_t cond;
193
} sync;
194
195
// Context for proxying with callbacks.
196
struct {
197
em_proxying_queue* queue;
198
pthread_t caller_thread;
199
void (*callback)(void*);
200
void (*cancel)(void*);
201
} cb;
202
};
203
204
// A doubly linked list of contexts associated with active work on a single
205
// thread. If the thread is canceled, it will traverse this list to find
206
// contexts that need to be canceled.
207
struct em_proxying_ctx* next;
208
struct em_proxying_ctx* prev;
209
};
210
211
// The key that `cancel_active_ctxs` is bound to so that it runs when a thread
212
// is canceled or exits.
213
static pthread_key_t active_ctxs;
214
static pthread_once_t active_ctxs_once = PTHREAD_ONCE_INIT;
215
216
static void cancel_ctx(void* arg);
217
static void cancel_active_ctxs(void* arg);
218
219
static void init_active_ctxs(void) {
220
int ret = pthread_key_create(&active_ctxs, cancel_active_ctxs);
221
assert(ret == 0);
222
(void)ret;
223
}
224
225
static void add_active_ctx(em_proxying_ctx* ctx) {
226
assert(ctx != NULL);
227
em_proxying_ctx* head = pthread_getspecific(active_ctxs);
228
if (head == NULL) {
229
// This is the only active context; initialize the active contexts list.
230
ctx->next = ctx->prev = ctx;
231
pthread_setspecific(active_ctxs, ctx);
232
} else {
233
// Insert this context at the tail of the list just before `head`.
234
ctx->next = head;
235
ctx->prev = head->prev;
236
ctx->next->prev = ctx;
237
ctx->prev->next = ctx;
238
}
239
}
240
241
static void remove_active_ctx(em_proxying_ctx* ctx) {
242
assert(ctx != NULL);
243
assert(ctx->next != NULL);
244
assert(ctx->prev != NULL);
245
if (ctx->next == ctx) {
246
// This is the only active context; clear the active contexts list.
247
ctx->next = ctx->prev = NULL;
248
pthread_setspecific(active_ctxs, NULL);
249
return;
250
}
251
252
// Update the list head if we are removing the current head.
253
em_proxying_ctx* head = pthread_getspecific(active_ctxs);
254
if (ctx == head) {
255
pthread_setspecific(active_ctxs, head->next);
256
}
257
258
// Remove the context from the list.
259
ctx->prev->next = ctx->next;
260
ctx->next->prev = ctx->prev;
261
ctx->next = ctx->prev = NULL;
262
}
263
264
static void cancel_active_ctxs(void* arg) {
265
pthread_setspecific(active_ctxs, NULL);
266
em_proxying_ctx* head = arg;
267
em_proxying_ctx* curr = head;
268
do {
269
em_proxying_ctx* next = curr->next;
270
cancel_ctx(curr);
271
curr = next;
272
} while (curr != head);
273
}
274
275
static void em_proxying_ctx_init_sync(em_proxying_ctx* ctx,
276
void (*func)(em_proxying_ctx*, void*),
277
void* arg) {
278
pthread_once(&active_ctxs_once, init_active_ctxs);
279
*ctx = (em_proxying_ctx){
280
.func = func,
281
.arg = arg,
282
.kind = SYNC,
283
.sync =
284
{
285
.state = PENDING,
286
.mutex = PTHREAD_MUTEX_INITIALIZER,
287
.cond = PTHREAD_COND_INITIALIZER,
288
},
289
};
290
}
291
292
static void em_proxying_ctx_init_callback(em_proxying_ctx* ctx,
293
em_proxying_queue* queue,
294
pthread_t caller_thread,
295
void (*func)(em_proxying_ctx*, void*),
296
void (*callback)(void*),
297
void (*cancel)(void*),
298
void* arg) {
299
pthread_once(&active_ctxs_once, init_active_ctxs);
300
*ctx = (em_proxying_ctx){
301
.func = func,
302
.arg = arg,
303
.kind = CALLBACK,
304
.cb =
305
{
306
.queue = queue,
307
.caller_thread = caller_thread,
308
.callback = callback,
309
.cancel = cancel,
310
},
311
};
312
}
313
314
static void em_proxying_ctx_deinit(em_proxying_ctx* ctx) {
315
if (ctx->kind == SYNC) {
316
pthread_mutex_destroy(&ctx->sync.mutex);
317
pthread_cond_destroy(&ctx->sync.cond);
318
}
319
// TODO: We should probably have some kind of refcounting scheme to keep
320
// `queue` alive for callback ctxs.
321
}
322
323
static void free_ctx(void* arg) {
324
em_proxying_ctx* ctx = arg;
325
em_proxying_ctx_deinit(ctx);
326
free(ctx);
327
}
328
329
// Free the callback info on the same thread it was originally allocated on.
330
// This may be more efficient.
331
static void call_callback_then_free_ctx(void* arg) {
332
em_proxying_ctx* ctx = arg;
333
ctx->cb.callback(ctx->arg);
334
free_ctx(ctx);
335
}
336
337
void emscripten_proxy_finish(em_proxying_ctx* ctx) {
338
if (ctx->kind == SYNC) {
339
pthread_mutex_lock(&ctx->sync.mutex);
340
ctx->sync.state = DONE;
341
remove_active_ctx(ctx);
342
pthread_mutex_unlock(&ctx->sync.mutex);
343
pthread_cond_signal(&ctx->sync.cond);
344
} else {
345
// Schedule the callback on the caller thread. If the caller thread has
346
// already died or dies before the callback is executed, then at least make
347
// sure the context is freed.
348
remove_active_ctx(ctx);
349
if (!do_proxy(ctx->cb.queue,
350
ctx->cb.caller_thread,
351
(task){call_callback_then_free_ctx, free_ctx, ctx})) {
352
free_ctx(ctx);
353
}
354
}
355
}
356
357
static void call_cancel_then_free_ctx(void* arg) {
358
em_proxying_ctx* ctx = arg;
359
ctx->cb.cancel(ctx->arg);
360
free_ctx(ctx);
361
}
362
363
static void cancel_ctx(void* arg) {
364
em_proxying_ctx* ctx = arg;
365
if (ctx->kind == SYNC) {
366
pthread_mutex_lock(&ctx->sync.mutex);
367
ctx->sync.state = CANCELED;
368
pthread_mutex_unlock(&ctx->sync.mutex);
369
pthread_cond_signal(&ctx->sync.cond);
370
} else {
371
if (ctx->cb.cancel == NULL ||
372
!do_proxy(ctx->cb.queue,
373
ctx->cb.caller_thread,
374
(task){call_cancel_then_free_ctx, free_ctx, ctx})) {
375
free_ctx(ctx);
376
}
377
}
378
}
379
380
// Helper for wrapping the call with ctx as a `void (*)(void*)`.
381
static void call_with_ctx(void* arg) {
382
em_proxying_ctx* ctx = arg;
383
add_active_ctx(ctx);
384
ctx->func(ctx, ctx->arg);
385
}
386
387
int emscripten_proxy_sync_with_ctx(em_proxying_queue* q,
388
pthread_t target_thread,
389
void (*func)(em_proxying_ctx*, void*),
390
void* arg) {
391
assert(!pthread_equal(target_thread, pthread_self()) &&
392
"Cannot synchronously wait for work proxied to the current thread");
393
em_proxying_ctx ctx;
394
em_proxying_ctx_init_sync(&ctx, func, arg);
395
if (!do_proxy(q, target_thread, (task){call_with_ctx, cancel_ctx, &ctx})) {
396
em_proxying_ctx_deinit(&ctx);
397
return 0;
398
}
399
pthread_mutex_lock(&ctx.sync.mutex);
400
while (ctx.sync.state == PENDING) {
401
pthread_cond_wait(&ctx.sync.cond, &ctx.sync.mutex);
402
}
403
pthread_mutex_unlock(&ctx.sync.mutex);
404
int ret = ctx.sync.state == DONE;
405
em_proxying_ctx_deinit(&ctx);
406
return ret;
407
}
408
409
// Helper for signaling the end of the task after the user function returns.
410
static void call_then_finish_task(em_proxying_ctx* ctx, void* arg) {
411
task* t = arg;
412
t->func(t->arg);
413
emscripten_proxy_finish(ctx);
414
}
415
416
int emscripten_proxy_sync(em_proxying_queue* q,
417
pthread_t target_thread,
418
void (*func)(void*),
419
void* arg) {
420
task t = {.func = func, .arg = arg};
421
return emscripten_proxy_sync_with_ctx(
422
q, target_thread, call_then_finish_task, &t);
423
}
424
425
static int do_proxy_callback(em_proxying_queue* q,
426
pthread_t target_thread,
427
void (*func)(em_proxying_ctx* ctx, void*),
428
void (*callback)(void*),
429
void (*cancel)(void*),
430
void* arg,
431
em_proxying_ctx* ctx) {
432
em_proxying_ctx_init_callback(
433
ctx, q, pthread_self(), func, callback, cancel, arg);
434
if (!do_proxy(q, target_thread, (task){call_with_ctx, cancel_ctx, ctx})) {
435
free_ctx(ctx);
436
return 0;
437
}
438
return 1;
439
}
440
441
int emscripten_proxy_callback_with_ctx(em_proxying_queue* q,
442
pthread_t target_thread,
443
void (*func)(em_proxying_ctx* ctx,
444
void*),
445
void (*callback)(void*),
446
void (*cancel)(void*),
447
void* arg) {
448
em_proxying_ctx* ctx = malloc(sizeof(*ctx));
449
if (ctx == NULL) {
450
return 0;
451
}
452
return do_proxy_callback(q, target_thread, func, callback, cancel, arg, ctx);
453
}
454
455
typedef struct callback_ctx {
456
void (*func)(void*);
457
void (*callback)(void*);
458
void (*cancel)(void*);
459
void* arg;
460
} callback_ctx;
461
462
static void call_then_finish_callback(em_proxying_ctx* ctx, void* arg) {
463
callback_ctx* cb_ctx = arg;
464
cb_ctx->func(cb_ctx->arg);
465
emscripten_proxy_finish(ctx);
466
}
467
468
static void callback_call(void* arg) {
469
callback_ctx* cb_ctx = arg;
470
cb_ctx->callback(cb_ctx->arg);
471
}
472
473
static void callback_cancel(void* arg) {
474
callback_ctx* cb_ctx = arg;
475
if (cb_ctx->cancel != NULL) {
476
cb_ctx->cancel(cb_ctx->arg);
477
}
478
}
479
480
int emscripten_proxy_callback(em_proxying_queue* q,
481
pthread_t target_thread,
482
void (*func)(void*),
483
void (*callback)(void*),
484
void (*cancel)(void*),
485
void* arg) {
486
// Allocate the em_proxying_ctx and the user ctx as a single block that will
487
// be freed when the `em_proxying_ctx` is freed.
488
struct block {
489
em_proxying_ctx ctx;
490
callback_ctx cb_ctx;
491
};
492
struct block* block = malloc(sizeof(*block));
493
if (block == NULL) {
494
return 0;
495
}
496
block->cb_ctx = (callback_ctx){func, callback, cancel, arg};
497
return do_proxy_callback(q,
498
target_thread,
499
call_then_finish_callback,
500
callback_call,
501
callback_cancel,
502
&block->cb_ctx,
503
&block->ctx);
504
}
505
506
typedef struct promise_ctx {
507
void (*func)(em_proxying_ctx*, void*);
508
void* arg;
509
em_promise_t promise;
510
} promise_ctx;
511
512
static void promise_call(em_proxying_ctx* ctx, void* arg) {
513
promise_ctx* promise_ctx = arg;
514
promise_ctx->func(ctx, promise_ctx->arg);
515
}
516
517
static void promise_fulfill(void* arg) {
518
promise_ctx* promise_ctx = arg;
519
emscripten_promise_resolve(promise_ctx->promise, EM_PROMISE_FULFILL, NULL);
520
emscripten_promise_destroy(promise_ctx->promise);
521
}
522
523
static void promise_reject(void* arg) {
524
promise_ctx* promise_ctx = arg;
525
emscripten_promise_resolve(promise_ctx->promise, EM_PROMISE_REJECT, NULL);
526
emscripten_promise_destroy(promise_ctx->promise);
527
}
528
529
static em_promise_t do_proxy_promise(em_proxying_queue* q,
530
pthread_t target_thread,
531
void (*func)(em_proxying_ctx*, void*),
532
void* arg,
533
em_promise_t promise,
534
em_proxying_ctx* ctx,
535
promise_ctx* promise_ctx) {
536
*promise_ctx = (struct promise_ctx){func, arg, promise};
537
if (!do_proxy_callback(q,
538
target_thread,
539
promise_call,
540
promise_fulfill,
541
promise_reject,
542
promise_ctx,
543
ctx)) {
544
emscripten_promise_resolve(promise, EM_PROMISE_REJECT, NULL);
545
return promise;
546
}
547
// Return a separate promise to ensure that the internal promise will stay
548
// alive until the callbacks are called.
549
em_promise_t ret = emscripten_promise_create();
550
emscripten_promise_resolve(ret, EM_PROMISE_MATCH, promise);
551
return ret;
552
}
553
554
em_promise_t emscripten_proxy_promise_with_ctx(em_proxying_queue* q,
555
pthread_t target_thread,
556
void (*func)(em_proxying_ctx*,
557
void*),
558
void* arg) {
559
em_promise_t promise = emscripten_promise_create();
560
// Allocate the em_proxying_ctx and promise ctx as a single block that will be
561
// freed when the `em_proxying_ctx` is freed.
562
struct block {
563
em_proxying_ctx ctx;
564
promise_ctx promise_ctx;
565
};
566
struct block* block = malloc(sizeof(*block));
567
if (block == NULL) {
568
emscripten_promise_resolve(promise, EM_PROMISE_REJECT, NULL);
569
return promise;
570
}
571
return do_proxy_promise(
572
q, target_thread, func, arg, promise, &block->ctx, &block->promise_ctx);
573
}
574
575
em_promise_t emscripten_proxy_promise(em_proxying_queue* q,
576
pthread_t target_thread,
577
void (*func)(void*),
578
void* arg) {
579
em_promise_t promise = emscripten_promise_create();
580
// Allocate the em_proxying_ctx, promise ctx, and user task as a single block
581
// that will be freed when the `em_proxying_ctx` is freed.
582
struct block {
583
em_proxying_ctx ctx;
584
promise_ctx promise_ctx;
585
task task;
586
};
587
struct block* block = malloc(sizeof(*block));
588
if (block == NULL) {
589
emscripten_promise_resolve(promise, EM_PROMISE_REJECT, NULL);
590
return promise;
591
}
592
block->task = (task){.func = func, .arg = arg};
593
return do_proxy_promise(q,
594
target_thread,
595
call_then_finish_task,
596
&block->task,
597
promise,
598
&block->ctx,
599
&block->promise_ctx);
600
}
601
602
typedef struct proxied_js_func_t {
603
int funcIndex;
604
void* emAsmAddr;
605
pthread_t callingThread;
606
int bufSize;
607
double* argBuffer;
608
double result;
609
bool owned;
610
} proxied_js_func_t;
611
612
static void run_js_func(void* arg) {
613
proxied_js_func_t* f = (proxied_js_func_t*)arg;
614
f->result = _emscripten_receive_on_main_thread_js(
615
f->funcIndex, f->emAsmAddr, f->callingThread, f->bufSize, f->argBuffer, 0, 0);
616
if (f->owned) {
617
free(f->argBuffer);
618
free(f);
619
}
620
}
621
622
static void run_js_func_with_ctx(em_proxying_ctx* ctx, void* arg) {
623
proxied_js_func_t* f = (proxied_js_func_t*)arg;
624
_emscripten_receive_on_main_thread_js(
625
f->funcIndex, f->emAsmAddr, f->callingThread, f->bufSize, f->argBuffer, ctx, arg);
626
627
// run_js_func_with_ctx is always synchronously proxied and therefore arg
628
// should never be owned on the main thread (i.e. the argument here always
629
// exists on the stack of the calling thread, it's never copied/malloced).
630
assert(!f->owned);
631
}
632
633
void _emscripten_run_js_on_main_thread_done(void* ctx, void* arg, double result) {
634
proxied_js_func_t* f = (proxied_js_func_t*)arg;
635
f->result = result;
636
emscripten_proxy_finish(ctx);
637
}
638
639
/*
640
* The 'proxy_mode' argument to _emscripten_run_js_on_main_thread has 3 possible
641
* values:
642
*
643
* - PROXY_ASYNC: Returns immediately on the calling thread, does not signal
644
* - PROXY_SYNC: Synchronous on the calling thread, and also on the main thread
645
* - PROXY_SYNC_ASYNC: Synchronous on the calling thread, but async on the main
646
* thread.
647
*
648
* Note: 'PROXY_SYNC_ASYNC' is only passed when a function is marked as
649
* both "__async" and "__proxy: 'sync'"
650
*/
651
#define PROXY_ASYNC 0
652
#define PROXY_SYNC 1
653
#define PROXY_SYNC_ASYNC 2
654
655
double _emscripten_run_js_on_main_thread(int func_index,
656
void* em_asm_addr,
657
int buf_size,
658
double* buffer,
659
int proxyMode) {
660
proxied_js_func_t f = {
661
.funcIndex = func_index,
662
.emAsmAddr = em_asm_addr,
663
.callingThread = pthread_self(),
664
.bufSize = buf_size,
665
.argBuffer = buffer,
666
.owned = false,
667
};
668
669
em_proxying_queue* q = emscripten_proxy_get_system_queue();
670
pthread_t target = emscripten_main_runtime_thread_id();
671
672
if (proxyMode != PROXY_ASYNC) {
673
int rtn;
674
if (proxyMode == PROXY_SYNC_ASYNC) {
675
rtn = emscripten_proxy_sync_with_ctx(q, target, run_js_func_with_ctx, &f);
676
} else {
677
rtn = emscripten_proxy_sync(q, target, run_js_func, &f);
678
}
679
if (!rtn) {
680
assert(false && "emscripten_proxy_sync_with_ctx failed");
681
return 0;
682
}
683
return f.result;
684
}
685
686
// Make a heap allocated copy of the proxied_js_func_t
687
proxied_js_func_t* arg = malloc(sizeof(proxied_js_func_t));
688
*arg = f;
689
arg->owned = true;
690
691
// Also make a copy of the argBuffer.
692
arg->argBuffer = malloc(buf_size);
693
memcpy(arg->argBuffer, buffer, buf_size);
694
695
if (!emscripten_proxy_async(q, target, run_js_func, arg)) {
696
assert(false && "emscripten_proxy_async failed");
697
}
698
return 0;
699
}
700
701