From 556991192928e3bf394ed38e9839b050a486f34a Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 00:35:44 +0800 Subject: [PATCH 01/14] basic async support --- .vscode/c_cpp_properties.json | 2 +- .vscode/launch.json | 2 +- packages/emnapi/include/node_api.h | 24 +- packages/emnapi/include/node_api_types.h | 21 ++ packages/emnapi/src/emnapi.c | 153 ++++++++++++ packages/emnapi/src/init.ts | 28 +-- packages/emnapi/src/simple-async-operation.ts | 116 +++++++++ packages/emnapi/src/typings/runtime.d.ts | 4 +- packages/test/async/async.test.js | 53 ++++ packages/test/async/binding.c | 231 ++++++++++++++++++ packages/test/cgen.config.js | 43 ++-- 11 files changed, 633 insertions(+), 44 deletions(-) create mode 100644 packages/emnapi/include/node_api_types.h create mode 100644 packages/emnapi/src/simple-async-operation.ts create mode 100644 packages/test/async/async.test.js create mode 100644 packages/test/async/binding.c diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index b9ddd9e0..77bb36eb 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -57,7 +57,7 @@ }, { "name": "Win32 Emscripten", - "defines": ["${defines}", "__wasm32__"], + "defines": ["${defines}", "__wasm32__", "__EMSCRIPTEN_PTHREADS__"], "compilerPath": "${env:EMSDK}\\upstream\\emscripten\\emcc.bat", "intelliSenseMode": "clang-x86", "cStandard": "c99", diff --git a/.vscode/launch.json b/.vscode/launch.json index 4fb8aec6..99b8a5f7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "request": "launch", "name": "Launch Program", "runtimeArgs": ["--expose-gc"], - "program": "${workspaceFolder}/packages/test/emnapi/emnapi.test.js", + "program": "${workspaceFolder}/packages/test/async/async.test.js", "args": [] }, { diff --git a/packages/emnapi/include/node_api.h b/packages/emnapi/include/node_api.h index bfbf25ae..c67d6cd4 100644 --- a/packages/emnapi/include/node_api.h +++ b/packages/emnapi/include/node_api.h @@ -3,6 +3,7 @@ #include #include "js_native_api.h" +#include "node_api_types.h" #define NAPI_MODULE_EXPORT __attribute__((used)) #define NAPI_NO_RETURN __attribute__((__noreturn__)) @@ -27,9 +28,11 @@ typedef napi_value (*napi_addon_register_func)(napi_env env, void _emnapi_keepalive(void* f) {} \ void _emnapi_runtime_init(int* malloc_p, int* free_p, \ const char** key, const char*** error_messages); \ + void _emnapi_execute_async_work(napi_async_work work); \ NAPI_MODULE_EXPORT napi_value NAPI_WASM_INITIALIZER(napi_env env, \ napi_value exports) { \ _emnapi_keepalive((void*)_emnapi_runtime_init); \ + _emnapi_keepalive((void*)_emnapi_execute_async_work); \ return regfunc(env, exports); \ } \ EXTERN_C_END @@ -49,15 +52,24 @@ typedef napi_value (*napi_addon_register_func)(napi_env env, napi_value NAPI_MODULE_INITIALIZER(napi_env env, \ napi_value exports) -typedef struct { - uint32_t major; - uint32_t minor; - uint32_t patch; - const char* release; -} napi_node_version; EXTERN_C_START +NAPI_EXTERN +napi_status napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result); +NAPI_EXTERN napi_status napi_delete_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, + napi_async_work work); +NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, + napi_async_work work); + NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, diff --git a/packages/emnapi/include/node_api_types.h b/packages/emnapi/include/node_api_types.h new file mode 100644 index 00000000..3685fd8a --- /dev/null +++ b/packages/emnapi/include/node_api_types.h @@ -0,0 +1,21 @@ +#ifndef SRC_NODE_API_TYPES_H_ +#define SRC_NODE_API_TYPES_H_ + +#include "js_native_api_types.h" + +typedef struct napi_async_work__* napi_async_work; + +typedef void (*napi_async_execute_callback)(napi_env env, + void* data); +typedef void (*napi_async_complete_callback)(napi_env env, + napi_status status, + void* data); + +typedef struct { + uint32_t major; + uint32_t minor; + uint32_t patch; + const char* release; +} napi_node_version; + +#endif // SRC_NODE_API_TYPES_H_ diff --git a/packages/emnapi/src/emnapi.c b/packages/emnapi/src/emnapi.c index 105a955b..99efcb37 100644 --- a/packages/emnapi/src/emnapi.c +++ b/packages/emnapi/src/emnapi.c @@ -1,7 +1,30 @@ #include +#include #include "emnapi.h" #include "node_api.h" +#ifdef __EMSCRIPTEN_PTHREADS__ +// #include +#include +#endif + +#define CHECK_ENV(env) \ + do { \ + if ((env) == NULL) { \ + return napi_invalid_arg; \ + } \ + } while (0) + +#define RETURN_STATUS_IF_FALSE(env, condition, status) \ + do { \ + if (!(condition)) { \ + return napi_set_last_error((env), (status), 0, NULL); \ + } \ + } while (0) + +#define CHECK_ARG(env, arg) \ + RETURN_STATUS_IF_FALSE((env), ((arg) != NULL), napi_invalid_arg) + EXTERN_C_START extern napi_status napi_set_last_error(napi_env env, @@ -38,11 +61,26 @@ const char* emnapi_error_messages[] = { #define EMNAPI_MOD_NAME_X_HELPER(modname) #modname #define EMNAPI_MOD_NAME_X(modname) EMNAPI_MOD_NAME_X_HELPER(modname) +// #ifdef __EMSCRIPTEN_PTHREADS__ +// void* returner_main(void* queue) { +// emscripten_exit_with_live_runtime(); +// } + +// static pthread_t worker_thread = NULL; +// static em_proxying_queue* proxy_queue = NULL; +// #endif + EMSCRIPTEN_KEEPALIVE void _emnapi_runtime_init(int* malloc_p, int* free_p, const char** key, const char*** error_messages) { +// #ifdef __EMSCRIPTEN_PTHREADS__ + // proxy_queue = em_proxying_queue_create(); + // pthread_create(&worker_thread, NULL, returner_main, proxy_queue); + // pthread_detach(worker_thread); +// #endif + if (malloc_p) *malloc_p = (int)(malloc); if (free_p) *free_p = (int)(free); if (key) { @@ -84,4 +122,119 @@ emnapi_get_emscripten_version(napi_env env, return napi_clear_last_error(env); } +#ifdef __EMSCRIPTEN_PTHREADS__ + +struct napi_async_work__ { + napi_env env; + napi_async_execute_callback execute; + napi_async_complete_callback complete; + void* data; + napi_status status; + pthread_t tid; +}; + +// extern void _emnapi_create_async_work_js(napi_async_work work); +extern void _emnapi_delete_async_work_js(napi_async_work work); +extern void _emnapi_queue_async_work_js(napi_async_work work); +extern void _emnapi_on_execute_async_work_js(napi_async_work work); +extern int _emnapi_get_unused_worker_size(); + +napi_async_work _emnapi_async_work_init( + napi_env env, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data +) { + napi_async_work work = (napi_async_work)malloc(sizeof(struct napi_async_work__)); + work->env = env; + work->execute = execute; + work->complete = complete; + work->data = data; + work->status = napi_ok; + work->tid = NULL; + return work; +} + +void _emnapi_async_work_destroy(napi_async_work work) { + free(work); +} + +void* _emnapi_on_execute_async_work(void* arg) { + napi_async_work work = (napi_async_work) arg; + work->execute(work->env, work->data); + work->status = napi_ok; + _emnapi_on_execute_async_work_js(work); // postMessage to main thread + return NULL; +} +#endif + +EMSCRIPTEN_KEEPALIVE +void _emnapi_execute_async_work(napi_async_work work) { +#ifdef __EMSCRIPTEN_PTHREADS__ + pthread_t t; + pthread_create(&t, NULL, _emnapi_on_execute_async_work, work); + work->tid = t; + _emnapi_queue_async_work_js(work); // listen complete event + pthread_detach(t); +#endif +} + +napi_status napi_create_async_work(napi_env env, + napi_value async_resource, + napi_value async_resource_name, + napi_async_execute_callback execute, + napi_async_complete_callback complete, + void* data, + napi_async_work* result) { +#ifdef __EMSCRIPTEN_PTHREADS__ + CHECK_ENV(env); + CHECK_ARG(env, execute); + CHECK_ARG(env, result); + + napi_async_work work = _emnapi_async_work_init(env, execute, complete, data); + // _emnapi_create_async_work_js(work); // listen complete event + *result = work; + + return napi_clear_last_error(env); +#else + return napi_set_last_error(env, napi_generic_failure, 0, NULL); +#endif +} + +napi_status napi_delete_async_work(napi_env env, napi_async_work work) { +#ifdef __EMSCRIPTEN_PTHREADS__ + CHECK_ENV(env); + CHECK_ARG(env, work); + + _emnapi_delete_async_work_js(work); // clean listeners + _emnapi_async_work_destroy(work); + return napi_clear_last_error(env); +#else + return napi_set_last_error(env, napi_generic_failure, 0, NULL); +#endif +} + +napi_status napi_queue_async_work(napi_env env, napi_async_work work) { +#ifdef __EMSCRIPTEN_PTHREADS__ + CHECK_ENV(env); + CHECK_ARG(env, work); + + int unused_worker_size = _emnapi_get_unused_worker_size(); + if (unused_worker_size > 0) { + _emnapi_execute_async_work(work); + } else { + _emnapi_queue_async_work_js(work); // queue work + } + + // work->tid = worker_thread; + // _emnapi_queue_async_work_js(work); // listen complete event + // emscripten_proxy_async(proxy_queue, worker_thread, + // _emnapi_on_execute_async_work, work); + + return napi_clear_last_error(env); +#else + return napi_set_last_error(env, napi_generic_failure, 0, NULL); +#endif +} + EXTERN_C_END diff --git a/packages/emnapi/src/init.ts b/packages/emnapi/src/init.ts index d69ed291..0e128b22 100644 --- a/packages/emnapi/src/init.ts +++ b/packages/emnapi/src/init.ts @@ -8,20 +8,18 @@ declare function _napi_register_wasm_v1 (env: napi_env, exports: napi_value): na declare function __emnapi_runtime_init (...args: [number, number, number, number]): void mergeInto(LibraryManager.library, { - $emnapiGetDynamicCalls: function () { - return { - call_vi (_ptr: number, a: int32_t): void { - return makeDynCall('vi', '_ptr')(a) - }, - call_ii (_ptr: number, a: int32_t): int32_t { - return makeDynCall('ii', '_ptr')(a) - }, - call_iii (_ptr: number, a: int32_t, b: int32_t): int32_t { - return makeDynCall('iii', '_ptr')(a, b) - }, - call_viii (_ptr: number, a: int32_t, b: int32_t, c: int32_t): void { - return makeDynCall('viii', '_ptr')(a, b, c) - } + $emnapiGetDynamicCalls: { + call_vi: function (_ptr: number, a: int32_t): void { + return makeDynCall('vi', '_ptr')(a) + }, + call_ii: function (_ptr: number, a: int32_t): int32_t { + return makeDynCall('ii', '_ptr')(a) + }, + call_iii: function (_ptr: number, a: int32_t, b: int32_t): int32_t { + return makeDynCall('iii', '_ptr')(a, b) + }, + call_viii: function (_ptr: number, a: int32_t, b: int32_t, c: int32_t): void { + return makeDynCall('viii', '_ptr')(a, b, c) } }, @@ -38,7 +36,7 @@ mergeInto(LibraryManager.library, { let exportsKey: string let env: emnapi.Env | undefined - const dynCalls = emnapiGetDynamicCalls() + const dynCalls = emnapiGetDynamicCalls let malloc: ((size: number) => number) | undefined let free: ((ptr: number) => void) | undefined diff --git a/packages/emnapi/src/simple-async-operation.ts b/packages/emnapi/src/simple-async-operation.ts new file mode 100644 index 00000000..4e9e8e92 --- /dev/null +++ b/packages/emnapi/src/simple-async-operation.ts @@ -0,0 +1,116 @@ +/* eslint-disable @typescript-eslint/indent */ +/* eslint-disable no-unreachable */ + +declare const PThread: any +declare const emnapiAsyncWorkerQueue: number[] +// declare function __emnapi_execute_async_work (work: number): void + +mergeInto(LibraryManager.library, { + $emnapiAsyncWorkerQueue: [], + $emnapiAsyncWorkerQueue__deps: ['$PThread', '_emnapi_execute_async_work'], + $emnapiAsyncWorkerQueue__postset: 'PThread.unusedWorkers.push=function(){' + + 'var r=Array.prototype.push.apply(this,arguments);' + + 'setTimeout(function(){' + + 'if(PThread.unusedWorkers.length>0&&emnapiAsyncWorkerQueue.length>0){' + + '__emnapi_execute_async_work(emnapiAsyncWorkerQueue.shift());' + + '}' + + '});' + + 'return r;' + + '};', + + _emnapi_get_unused_worker_size__deps: ['$PThread'], + _emnapi_get_unused_worker_size: function () { + return PThread.unusedWorkers.length + }, + + _emnapi_on_execute_async_work_js: function (work: number) { + if (ENVIRONMENT_IS_PTHREAD) { + postMessage({ emnapiAsyncWorkPtr: work }) + } + }, + + _emnapi_delete_async_work_js: function (_work: number) { + // TODO + } +}) + +function _emnapi_queue_async_work_js (work: number): void { + const tid = HEAP32[(work + 20) >> 2] + if (tid === 0) { + emnapiAsyncWorkerQueue.push(work) + return + } + const env = HEAP32[work >> 2] + const worker: Worker = PThread.pthreads[tid].worker + const listener: (this: Worker, ev: MessageEvent) => any = (e) => { + const removeListener = (): void => { + if (ENVIRONMENT_IS_NODE) { + (worker as any).off('message', listener) + } else { + worker.removeEventListener('message', listener, false) + } + } + const data = ENVIRONMENT_IS_NODE ? e : e.data + if (data.emnapiAsyncWorkPtr === work) { + HEAP32[(work + 20) >> 2] = 0 // tid + const complete = HEAP32[(work + 8) >> 2] + if (complete !== NULL) { + const envObject = emnapi.envStore.get(env)! + const scope = envObject.openScope(emnapi.HandleScope) + try { + envObject.callIntoModule((envObject) => { + envObject.call_viii(complete, env, HEAP32[(work + 16) >> 2], HEAP32[(work + 12) >> 2]) + }) + } catch (err) { + envObject.closeScope(scope) + removeListener() + throw err + } + envObject.closeScope(scope) + } + removeListener() + } + } + if (ENVIRONMENT_IS_NODE) { + (worker as any).on('message', listener) + } else { + worker.addEventListener('message', listener, false) + } +} + +function napi_cancel_async_work (env: napi_env, work: number): napi_status { +// #if USE_PTHREADS + return emnapi.checkEnv(env, (envObject) => { + return emnapi.checkArgs(envObject, [work], () => { + const tid = HEAP32[(work + 20) >> 2] + if (tid !== 0) { + return envObject.setLastError(napi_status.napi_generic_failure) + } + + emnapiAsyncWorkerQueue.splice(emnapiAsyncWorkerQueue.indexOf(work), 1) + HEAP32[(work + 16) >> 2] = napi_status.napi_cancelled + const complete = HEAP32[(work + 8) >> 2] + if (complete !== NULL) { + const envObject = emnapi.envStore.get(env)! + const scope = envObject.openScope(emnapi.HandleScope) + try { + envObject.callIntoModule((envObject) => { + envObject.call_viii(complete, env, napi_status.napi_cancelled, HEAP32[(work + 12) >> 2]) + }) + } catch (err) { + envObject.closeScope(scope) + throw err + } + envObject.closeScope(scope) + } + + return envObject.clearLastError() + }) + }) +// #else + return _napi_set_last_error(env, napi_status.napi_generic_failure, 0, 0) +// #endif +} + +emnapiImplement('_emnapi_queue_async_work_js', _emnapi_queue_async_work_js, ['$PThread', '$emnapiAsyncWorkerQueue']) +emnapiImplement('napi_cancel_async_work', napi_cancel_async_work, ['$emnapiAsyncWorkerQueue']) diff --git a/packages/emnapi/src/typings/runtime.d.ts b/packages/emnapi/src/typings/runtime.d.ts index 2eaec702..31c51f4a 100644 --- a/packages/emnapi/src/typings/runtime.d.ts +++ b/packages/emnapi/src/typings/runtime.d.ts @@ -5,12 +5,14 @@ declare interface IDynamicCalls { call_viii (_ptr: number, a: int32_t, b: int32_t, c: int32_t): void // call_malloc (_size: size_t): void_p } -declare function emnapiGetDynamicCalls (): IDynamicCalls +declare const emnapiGetDynamicCalls: IDynamicCalls declare const HEAPU32: Uint32Array declare const HEAP32: Int32Array declare const HEAPF64: Float64Array declare const HEAPU8: Uint8Array +declare const ENVIRONMENT_IS_NODE: boolean +declare const ENVIRONMENT_IS_PTHREAD: boolean declare const wasmTable: WebAssembly.Table declare function UTF8ToString (ptr: const_char_p, maxRead?: number): string diff --git a/packages/test/async/async.test.js b/packages/test/async/async.test.js new file mode 100644 index 00000000..a6c578f0 --- /dev/null +++ b/packages/test/async/async.test.js @@ -0,0 +1,53 @@ +/* eslint-disable camelcase */ +'use strict' +const { load } = require('../util') +const common = require('../common') +const assert = require('assert') +const child_process = require('child_process') + +async function main () { + const test_async = await load('async') + + const testException = 'test_async_cb_exception' + + // Exception thrown from async completion callback. + // (Tested in a spawned process because the exception is fatal.) + if (process.argv[2] === 'child') { + test_async.Test(1, {}, common.mustCall(function () { + throw new Error(testException) + })) + return + } + const p = child_process.spawnSync( + process.execPath, [__filename, 'child']) + assert.ifError(p.error) + const stderr = p.stderr.toString() + assert.ok(stderr.includes(testException)) + + await new Promise((resolve) => { + // Successful async execution and completion callback. + test_async.Test(5, {}, common.mustCall(function (err, val) { + console.log(11111) + assert.strictEqual(err, null) + assert.strictEqual(val, 10) + process.nextTick(common.mustCall(() => { + console.log(22222) + resolve() + })) + })) + }) + + await new Promise((resolve) => { + // Async work item cancellation with callback. + test_async.TestCancel(common.mustCall(() => { + console.log(33333) + resolve() + })) + }) + + await new Promise((resolve) => { + setTimeout(resolve, 4000) + }) +} + +module.exports = main() diff --git a/packages/test/async/binding.c b/packages/test/async/binding.c new file mode 100644 index 00000000..7e418441 --- /dev/null +++ b/packages/test/async/binding.c @@ -0,0 +1,231 @@ +#include +#include +#include +#include "../common.h" +#include + +#if defined _WIN32 +#include +#else +#include +#endif + +// this needs to be greater than the thread pool size +#define MAX_CANCEL_THREADS 6 + +typedef struct { + int32_t _input; + int32_t _output; + napi_ref _callback; + napi_async_work _request; +} carrier; + +static carrier the_carrier; +static carrier async_carrier[MAX_CANCEL_THREADS]; + +static void Execute(napi_env env, void* data) { +#if defined _WIN32 + Sleep(1000); +#else + sleep(1); +#endif + carrier* c = (carrier*)(data); + + assert(c == &the_carrier); + + c->_output = c->_input * 2; +} + +static void Complete(napi_env env, napi_status status, void* data) { + carrier* c = (carrier*)(data); + + if (c != &the_carrier) { + napi_throw_type_error(env, NULL, "Wrong data parameter to Complete."); + return; + } + + if (status != napi_ok) { + napi_throw_type_error(env, NULL, "Execute callback failed."); + return; + } + + napi_value argv[2]; + + NAPI_CALL_RETURN_VOID(env, napi_get_null(env, &argv[0])); + NAPI_CALL_RETURN_VOID(env, napi_create_int32(env, c->_output, &argv[1])); + napi_value callback; + NAPI_CALL_RETURN_VOID(env, + napi_get_reference_value(env, c->_callback, &callback)); + napi_value global; + NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &global)); + + napi_value result; + NAPI_CALL_RETURN_VOID(env, + napi_call_function(env, global, callback, 2, argv, &result)); + + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback)); + NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request)); +} + +static napi_value Test(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value argv[3]; + napi_value _this; + napi_value resource_name; + void* data; + NAPI_CALL(env, + napi_get_cb_info(env, info, &argc, argv, &_this, &data)); + NAPI_ASSERT(env, argc >= 3, "Not enough arguments, expected 2."); + + napi_valuetype t; + NAPI_CALL(env, napi_typeof(env, argv[0], &t)); + NAPI_ASSERT(env, t == napi_number, + "Wrong first argument, integer expected."); + NAPI_CALL(env, napi_typeof(env, argv[1], &t)); + NAPI_ASSERT(env, t == napi_object, + "Wrong second argument, object expected."); + NAPI_CALL(env, napi_typeof(env, argv[2], &t)); + NAPI_ASSERT(env, t == napi_function, + "Wrong third argument, function expected."); + + the_carrier._output = 0; + + NAPI_CALL(env, + napi_get_value_int32(env, argv[0], &the_carrier._input)); + NAPI_CALL(env, + napi_create_reference(env, argv[2], 1, &the_carrier._callback)); + + NAPI_CALL(env, napi_create_string_utf8( + env, "TestResource", NAPI_AUTO_LENGTH, &resource_name)); + NAPI_CALL(env, napi_create_async_work(env, argv[1], resource_name, + Execute, Complete, &the_carrier, &the_carrier._request)); + NAPI_CALL(env, + napi_queue_async_work(env, the_carrier._request)); + + return NULL; +} + +static void BusyCancelComplete(napi_env env, napi_status status, void* data) { + carrier* c = (carrier*)(data); + NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request)); +} + +static void CancelComplete(napi_env env, napi_status status, void* data) { + carrier* c = (carrier*)(data); + + if (status == napi_cancelled) { + // ok we got the status we expected so make the callback to + // indicate the cancel succeeded. + napi_value callback; + NAPI_CALL_RETURN_VOID(env, + napi_get_reference_value(env, c->_callback, &callback)); + napi_value global; + NAPI_CALL_RETURN_VOID(env, napi_get_global(env, &global)); + napi_value result; + NAPI_CALL_RETURN_VOID(env, + napi_call_function(env, global, callback, 0, NULL, &result)); + } + + NAPI_CALL_RETURN_VOID(env, napi_delete_async_work(env, c->_request)); + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, c->_callback)); +} + +static void CancelExecute(napi_env env, void* data) { +#if defined _WIN32 + Sleep(1000); +#else + sleep(1); +#endif + printf("CancelExecute: 0x%08x\n", (int)data); +} + +static napi_value TestCancel(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + napi_value _this; + napi_value resource_name; + void* data; + + NAPI_CALL(env, napi_create_string_utf8( + env, "TestResource", NAPI_AUTO_LENGTH, &resource_name)); + + // make sure the work we are going to cancel will not be + // able to start by using all the threads in the pool + for (int i = 1; i < MAX_CANCEL_THREADS; i++) { + NAPI_CALL(env, napi_create_async_work(env, NULL, resource_name, + CancelExecute, BusyCancelComplete, + &async_carrier[i], &async_carrier[i]._request)); + NAPI_CALL(env, napi_queue_async_work(env, async_carrier[i]._request)); + } + + // now queue the work we are going to cancel and then cancel it. + // cancel will fail if the work has already started, but + // we have prevented it from starting by consuming all of the + // workers above. + NAPI_CALL(env, + napi_get_cb_info(env, info, &argc, argv, &_this, &data)); + NAPI_CALL(env, napi_create_async_work(env, NULL, resource_name, + CancelExecute, CancelComplete, + &async_carrier[0], &async_carrier[0]._request)); + NAPI_CALL(env, + napi_create_reference(env, argv[0], 1, &async_carrier[0]._callback)); + NAPI_CALL(env, napi_queue_async_work(env, async_carrier[0]._request)); + NAPI_CALL(env, napi_cancel_async_work(env, async_carrier[0]._request)); + return NULL; +} + +struct { + napi_ref ref; + napi_async_work work; +} repeated_work_info = { NULL, NULL }; + +static void RepeatedWorkerThread(napi_env env, void* data) {} + +static void RepeatedWorkComplete(napi_env env, napi_status status, void* data) { + napi_value cb, js_status; + NAPI_CALL_RETURN_VOID(env, + napi_get_reference_value(env, repeated_work_info.ref, &cb)); + NAPI_CALL_RETURN_VOID(env, + napi_delete_async_work(env, repeated_work_info.work)); + NAPI_CALL_RETURN_VOID(env, + napi_delete_reference(env, repeated_work_info.ref)); + repeated_work_info.work = NULL; + repeated_work_info.ref = NULL; + NAPI_CALL_RETURN_VOID(env, + napi_create_uint32(env, (uint32_t)status, &js_status)); + NAPI_CALL_RETURN_VOID(env, + napi_call_function(env, cb, cb, 1, &js_status, NULL)); +} + +static napi_value DoRepeatedWork(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value cb, name; + NAPI_ASSERT(env, repeated_work_info.ref == NULL, + "Reference left over from previous work"); + NAPI_ASSERT(env, repeated_work_info.work == NULL, + "Work pointer left over from previous work"); + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &cb, NULL, NULL)); + NAPI_CALL(env, napi_create_reference(env, cb, 1, &repeated_work_info.ref)); + NAPI_CALL(env, + napi_create_string_utf8(env, "Repeated Work", NAPI_AUTO_LENGTH, &name)); + NAPI_CALL(env, + napi_create_async_work(env, NULL, name, RepeatedWorkerThread, + RepeatedWorkComplete, &repeated_work_info, &repeated_work_info.work)); + NAPI_CALL(env, napi_queue_async_work(env, repeated_work_info.work)); + return NULL; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("Test", Test), + DECLARE_NAPI_PROPERTY("TestCancel", TestCancel), + DECLARE_NAPI_PROPERTY("DoRepeatedWork", DoRepeatedWork), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/packages/test/cgen.config.js b/packages/test/cgen.config.js index 0758034b..ba71b54b 100644 --- a/packages/test/cgen.config.js +++ b/packages/test/cgen.config.js @@ -2,14 +2,12 @@ module.exports = function (_options, { isDebug, isEmscripten }) { const compilerFlags = isEmscripten ? [ // ...(isDebug ? ['-sDISABLE_EXCEPTION_CATCHING=0'] : []) - '-sUSE_PTHREADS=1' ] : [] const linkerFlags = isEmscripten ? [ // "-sEXPORTED_FUNCTIONS=['_malloc','_free']", - '-sUSE_PTHREADS=1', '-sWASM_BIGINT=1', '-sALLOW_MEMORY_GROWTH=1', '-sMIN_CHROME_VERSION=67', @@ -21,48 +19,52 @@ module.exports = function (_options, { isDebug, isEmscripten }) { ? ['../emnapi/include'] : [`${require('path').join(require('os').homedir(), 'AppData/Local/node-gyp/Cache', process.versions.node, 'include/node')}`, '../node_modules/node-addon-api'] - const createTarget = (name, sources, needEntry) => ({ + const jsLib = `--js-library=${require('path').join(__dirname, '../emnapi/dist/library_napi_no_runtime.js')}` + + const createTarget = (name, sources, needEntry, pthread) => ({ name: name, type: isEmscripten ? 'exe' : 'node', - sources: [...(needEntry ? (sources.push('./entry_point.c'), sources) : sources)], + sources: [ + ...(needEntry ? (sources.push('./entry_point.c'), sources) : sources), + ...(isEmscripten ? ['../emnapi/src/emnapi.c'] : []) + ], emwrap: { exports: ['emnapi'] }, includePaths, - libs: ['testcommon', ...(isEmscripten ? ['emnapi'] : [])], - compileOptions: [...compilerFlags], + libs: ['testcommon'], + compileOptions: [...compilerFlags, ...(isEmscripten && pthread ? ['-sUSE_PTHREADS=1'] : [])], // eslint-disable-next-line no-template-curly-in-string - linkOptions: [...linkerFlags] + linkOptions: [ + ...linkerFlags, + ...(isEmscripten ? [jsLib] : []), + ...(isEmscripten && pthread ? ['-sUSE_PTHREADS=1', '-sPTHREAD_POOL_SIZE=4'] : []) + ] }) const createNodeAddonApiTarget = (name, sources) => ({ name: name, type: isEmscripten ? 'exe' : 'node', - sources: [...sources], + sources: [ + ...sources, + ...(isEmscripten ? ['../emnapi/src/emnapi.c'] : []) + ], emwrap: { exports: ['emnapi'] }, includePaths, - libs: [...(isEmscripten ? ['emnapi'] : [])], defines: ['NAPI_DISABLE_CPP_EXCEPTIONS', 'NODE_ADDON_API_ENABLE_MAYBE'], compileOptions: [...compilerFlags], // eslint-disable-next-line no-template-curly-in-string - linkOptions: [...linkerFlags] + linkOptions: [ + ...linkerFlags, + ...(isEmscripten ? [jsLib] : []) + ] }) return { project: 'emnapitest', targets: [ - ...(isEmscripten - ? [{ - type: 'lib', - name: 'emnapi', - sources: ['../emnapi/src/emnapi.c'], - includePaths, - compileOptions: [...compilerFlags], - publicLinkOptions: [`--js-library=${require('path').join(__dirname, '../emnapi/dist/library_napi_no_runtime.js')}`] - }] - : []), { type: 'lib', name: 'testcommon', @@ -72,6 +74,7 @@ module.exports = function (_options, { isDebug, isEmscripten }) { }, createTarget('env', ['./env/binding.c']), createTarget('hello', ['./hello/binding.c']), + createTarget('async', ['./async/binding.c'], false, true), createTarget('arg', ['./arg/binding.c'], true), createTarget('callback', ['./callback/binding.c'], true), createTarget('objfac', ['./objfac/binding.c'], true), From 6cbcebb20ba0fe2e23091260a8e672a4dd9f3f1d Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 00:44:36 +0800 Subject: [PATCH 02/14] ci emscripten version --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d2667820..9e3962f6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v2 - uses: mymindstorm/setup-emsdk@v10 with: - version: '3.0.0' + version: '3.1.13' # no-cache: true actions-cache-folder: 'emsdk-cache' - uses: actions/setup-node@v2 From d6dad6cb1eef8ad7b9fbb0e0c1a2250cea899694 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 00:44:52 +0800 Subject: [PATCH 03/14] test loop async --- packages/test/async/async.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/test/async/async.test.js b/packages/test/async/async.test.js index a6c578f0..9c900952 100644 --- a/packages/test/async/async.test.js +++ b/packages/test/async/async.test.js @@ -45,6 +45,17 @@ async function main () { })) }) + const iterations = 500 + let x = 0 + const workDone = common.mustCall((status) => { + assert.strictEqual(status, 0) + console.log(status) + if (++x < iterations) { + setImmediate(() => test_async.DoRepeatedWork(workDone)) + } + }, iterations) + test_async.DoRepeatedWork(workDone) + await new Promise((resolve) => { setTimeout(resolve, 4000) }) From a1745054a8ce63473de3f70e677a934bc7bad487 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 01:02:44 +0800 Subject: [PATCH 04/14] remove console.log --- packages/test/async/async.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/test/async/async.test.js b/packages/test/async/async.test.js index 9c900952..5c562fa0 100644 --- a/packages/test/async/async.test.js +++ b/packages/test/async/async.test.js @@ -49,7 +49,6 @@ async function main () { let x = 0 const workDone = common.mustCall((status) => { assert.strictEqual(status, 0) - console.log(status) if (++x < iterations) { setImmediate(() => test_async.DoRepeatedWork(workDone)) } From 32d3db269e10ffe1a35e11954a85834091fd12f1 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 09:56:21 +0800 Subject: [PATCH 05/14] test -sNODEJS_CATCH_EXIT=0 --- packages/test/async/async.test.js | 28 ++++++++++++++++++++++++++-- packages/test/cgen.config.js | 1 + packages/test/util.js | 1 + 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/test/async/async.test.js b/packages/test/async/async.test.js index 5c562fa0..593e485a 100644 --- a/packages/test/async/async.test.js +++ b/packages/test/async/async.test.js @@ -6,13 +6,20 @@ const assert = require('assert') const child_process = require('child_process') async function main () { - const test_async = await load('async') + const loadPromise = load('async') + const test_async = await loadPromise const testException = 'test_async_cb_exception' // Exception thrown from async completion callback. // (Tested in a spawned process because the exception is fatal.) if (process.argv[2] === 'child') { + process.on('uncaughtException', function (ex) { + // suppress ExitStatus exceptions from showing an error + if (!(ex instanceof loadPromise.Module.ExitStatus)) { + throw ex + } + }) test_async.Test(1, {}, common.mustCall(function () { throw new Error(testException) })) @@ -56,7 +63,24 @@ async function main () { test_async.DoRepeatedWork(workDone) await new Promise((resolve) => { - setTimeout(resolve, 4000) + process.once('uncaughtException', common.mustCall(function (err) { + try { + throw new Error('should not fail') + } catch (err) { + assert.strictEqual(err.message, 'should not fail') + } + assert.strictEqual(err.message, 'uncaught') + resolve() + })) + + // Successful async execution and completion callback. + test_async.Test(5, {}, common.mustCall(function () { + throw new Error('uncaught') + })) + }) + + await new Promise((resolve) => { + setTimeout(resolve, 3000) }) } diff --git a/packages/test/cgen.config.js b/packages/test/cgen.config.js index ba71b54b..56769248 100644 --- a/packages/test/cgen.config.js +++ b/packages/test/cgen.config.js @@ -8,6 +8,7 @@ module.exports = function (_options, { isDebug, isEmscripten }) { const linkerFlags = isEmscripten ? [ // "-sEXPORTED_FUNCTIONS=['_malloc','_free']", + '-sNODEJS_CATCH_EXIT=0', '-sWASM_BIGINT=1', '-sALLOW_MEMORY_GROWTH=1', '-sMIN_CHROME_VERSION=67', diff --git a/packages/test/util.js b/packages/test/util.js index 42fb8d4f..d881f905 100644 --- a/packages/test/util.js +++ b/packages/test/util.js @@ -22,6 +22,7 @@ exports.load = function (targetName) { } } }).then(({ Module, emnapi }) => { + p.Module = Module p.emnapi = emnapi resolve(Module.emnapiExports) }) From 9a6edc8c3ed9f72b380222efeeda1d663753acd2 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 10:14:06 +0800 Subject: [PATCH 06/14] support pthread pool size is 0 --- packages/emnapi/src/emnapi.c | 12 +++++++++--- packages/emnapi/src/simple-async-operation.ts | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/emnapi/src/emnapi.c b/packages/emnapi/src/emnapi.c index 99efcb37..193ec13b 100644 --- a/packages/emnapi/src/emnapi.c +++ b/packages/emnapi/src/emnapi.c @@ -133,11 +133,16 @@ struct napi_async_work__ { pthread_t tid; }; +typedef struct worker_count { + int unused; + int running; +} worker_count; + // extern void _emnapi_create_async_work_js(napi_async_work work); extern void _emnapi_delete_async_work_js(napi_async_work work); extern void _emnapi_queue_async_work_js(napi_async_work work); extern void _emnapi_on_execute_async_work_js(napi_async_work work); -extern int _emnapi_get_unused_worker_size(); +extern int _emnapi_get_worker_count(worker_count* count); napi_async_work _emnapi_async_work_init( napi_env env, @@ -219,8 +224,9 @@ napi_status napi_queue_async_work(napi_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); - int unused_worker_size = _emnapi_get_unused_worker_size(); - if (unused_worker_size > 0) { + worker_count count; + _emnapi_get_worker_count(&count); + if (count.unused > 0 || count.running == 0) { _emnapi_execute_async_work(work); } else { _emnapi_queue_async_work_js(work); // queue work diff --git a/packages/emnapi/src/simple-async-operation.ts b/packages/emnapi/src/simple-async-operation.ts index 4e9e8e92..b8a6b57d 100644 --- a/packages/emnapi/src/simple-async-operation.ts +++ b/packages/emnapi/src/simple-async-operation.ts @@ -18,9 +18,11 @@ mergeInto(LibraryManager.library, { 'return r;' + '};', - _emnapi_get_unused_worker_size__deps: ['$PThread'], - _emnapi_get_unused_worker_size: function () { - return PThread.unusedWorkers.length + _emnapi_get_worker_count__deps: ['$PThread'], + _emnapi_get_worker_count: function (struct: number) { + const address = struct >> 2 + HEAP32[address] = PThread.unusedWorkers.length + HEAP32[address + 1] = PThread.runningWorkers.length }, _emnapi_on_execute_async_work_js: function (work: number) { From 419d4eb5d120e0ce83324fde7dfac9adc2b47ff2 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 10:34:51 +0800 Subject: [PATCH 07/14] remove work status --- packages/emnapi/src/emnapi.c | 7 ++---- packages/emnapi/src/simple-async-operation.ts | 25 ++++++++----------- packages/test/async/async.test.js | 7 +++--- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/packages/emnapi/src/emnapi.c b/packages/emnapi/src/emnapi.c index 193ec13b..59b5becf 100644 --- a/packages/emnapi/src/emnapi.c +++ b/packages/emnapi/src/emnapi.c @@ -129,7 +129,6 @@ struct napi_async_work__ { napi_async_execute_callback execute; napi_async_complete_callback complete; void* data; - napi_status status; pthread_t tid; }; @@ -139,7 +138,7 @@ typedef struct worker_count { } worker_count; // extern void _emnapi_create_async_work_js(napi_async_work work); -extern void _emnapi_delete_async_work_js(napi_async_work work); +// extern void _emnapi_delete_async_work_js(napi_async_work work); extern void _emnapi_queue_async_work_js(napi_async_work work); extern void _emnapi_on_execute_async_work_js(napi_async_work work); extern int _emnapi_get_worker_count(worker_count* count); @@ -155,7 +154,6 @@ napi_async_work _emnapi_async_work_init( work->execute = execute; work->complete = complete; work->data = data; - work->status = napi_ok; work->tid = NULL; return work; } @@ -167,7 +165,6 @@ void _emnapi_async_work_destroy(napi_async_work work) { void* _emnapi_on_execute_async_work(void* arg) { napi_async_work work = (napi_async_work) arg; work->execute(work->env, work->data); - work->status = napi_ok; _emnapi_on_execute_async_work_js(work); // postMessage to main thread return NULL; } @@ -211,7 +208,7 @@ napi_status napi_delete_async_work(napi_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); - _emnapi_delete_async_work_js(work); // clean listeners + // _emnapi_delete_async_work_js(work); // clean listeners _emnapi_async_work_destroy(work); return napi_clear_last_error(env); #else diff --git a/packages/emnapi/src/simple-async-operation.ts b/packages/emnapi/src/simple-async-operation.ts index b8a6b57d..e3b20178 100644 --- a/packages/emnapi/src/simple-async-operation.ts +++ b/packages/emnapi/src/simple-async-operation.ts @@ -25,19 +25,17 @@ mergeInto(LibraryManager.library, { HEAP32[address + 1] = PThread.runningWorkers.length }, + // _emnapi_delete_async_work_js: function (_work: number) {} + _emnapi_on_execute_async_work_js: function (work: number) { if (ENVIRONMENT_IS_PTHREAD) { postMessage({ emnapiAsyncWorkPtr: work }) } - }, - - _emnapi_delete_async_work_js: function (_work: number) { - // TODO } }) function _emnapi_queue_async_work_js (work: number): void { - const tid = HEAP32[(work + 20) >> 2] + const tid = HEAP32[(work + 16) >> 2] if (tid === 0) { emnapiAsyncWorkerQueue.push(work) return @@ -45,32 +43,30 @@ function _emnapi_queue_async_work_js (work: number): void { const env = HEAP32[work >> 2] const worker: Worker = PThread.pthreads[tid].worker const listener: (this: Worker, ev: MessageEvent) => any = (e) => { - const removeListener = (): void => { + const data = ENVIRONMENT_IS_NODE ? e : e.data + if (data.emnapiAsyncWorkPtr === work) { + // remove listener if (ENVIRONMENT_IS_NODE) { (worker as any).off('message', listener) } else { worker.removeEventListener('message', listener, false) } - } - const data = ENVIRONMENT_IS_NODE ? e : e.data - if (data.emnapiAsyncWorkPtr === work) { - HEAP32[(work + 20) >> 2] = 0 // tid + + HEAP32[(work + 16) >> 2] = 0 // tid const complete = HEAP32[(work + 8) >> 2] if (complete !== NULL) { const envObject = emnapi.envStore.get(env)! const scope = envObject.openScope(emnapi.HandleScope) try { envObject.callIntoModule((envObject) => { - envObject.call_viii(complete, env, HEAP32[(work + 16) >> 2], HEAP32[(work + 12) >> 2]) + envObject.call_viii(complete, env, napi_status.napi_ok, HEAP32[(work + 12) >> 2]) }) } catch (err) { envObject.closeScope(scope) - removeListener() throw err } envObject.closeScope(scope) } - removeListener() } } if (ENVIRONMENT_IS_NODE) { @@ -84,13 +80,12 @@ function napi_cancel_async_work (env: napi_env, work: number): napi_status { // #if USE_PTHREADS return emnapi.checkEnv(env, (envObject) => { return emnapi.checkArgs(envObject, [work], () => { - const tid = HEAP32[(work + 20) >> 2] + const tid = HEAP32[(work + 16) >> 2] if (tid !== 0) { return envObject.setLastError(napi_status.napi_generic_failure) } emnapiAsyncWorkerQueue.splice(emnapiAsyncWorkerQueue.indexOf(work), 1) - HEAP32[(work + 16) >> 2] = napi_status.napi_cancelled const complete = HEAP32[(work + 8) >> 2] if (complete !== NULL) { const envObject = emnapi.envStore.get(env)! diff --git a/packages/test/async/async.test.js b/packages/test/async/async.test.js index 593e485a..cbfb9011 100644 --- a/packages/test/async/async.test.js +++ b/packages/test/async/async.test.js @@ -34,11 +34,11 @@ async function main () { await new Promise((resolve) => { // Successful async execution and completion callback. test_async.Test(5, {}, common.mustCall(function (err, val) { - console.log(11111) + console.log('test_async.Test(5, {}, callback)') assert.strictEqual(err, null) assert.strictEqual(val, 10) process.nextTick(common.mustCall(() => { - console.log(22222) + console.log('process.nextTick(callback)') resolve() })) })) @@ -47,7 +47,7 @@ async function main () { await new Promise((resolve) => { // Async work item cancellation with callback. test_async.TestCancel(common.mustCall(() => { - console.log(33333) + console.log('test_async.TestCancel(callback)') resolve() })) }) @@ -70,6 +70,7 @@ async function main () { assert.strictEqual(err.message, 'should not fail') } assert.strictEqual(err.message, 'uncaught') + console.log('process.once("uncaughtException", callback): ' + err.message) resolve() })) From c7fef7a4b65c5f46ab0d44f0e9c79f27543b1c70 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 10:47:27 +0800 Subject: [PATCH 08/14] determine work is in queue --- packages/emnapi/src/simple-async-operation.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/emnapi/src/simple-async-operation.ts b/packages/emnapi/src/simple-async-operation.ts index e3b20178..d6a42008 100644 --- a/packages/emnapi/src/simple-async-operation.ts +++ b/packages/emnapi/src/simple-async-operation.ts @@ -35,7 +35,8 @@ mergeInto(LibraryManager.library, { }) function _emnapi_queue_async_work_js (work: number): void { - const tid = HEAP32[(work + 16) >> 2] + const tidAddr = (work + 16) >> 2 + const tid = HEAP32[tidAddr] if (tid === 0) { emnapiAsyncWorkerQueue.push(work) return @@ -52,7 +53,7 @@ function _emnapi_queue_async_work_js (work: number): void { worker.removeEventListener('message', listener, false) } - HEAP32[(work + 16) >> 2] = 0 // tid + HEAP32[tidAddr] = 0 // tid const complete = HEAP32[(work + 8) >> 2] if (complete !== NULL) { const envObject = emnapi.envStore.get(env)! @@ -81,11 +82,12 @@ function napi_cancel_async_work (env: napi_env, work: number): napi_status { return emnapi.checkEnv(env, (envObject) => { return emnapi.checkArgs(envObject, [work], () => { const tid = HEAP32[(work + 16) >> 2] - if (tid !== 0) { + const workQueueIndex = emnapiAsyncWorkerQueue.indexOf(work) + if (tid !== 0 || workQueueIndex === -1) { return envObject.setLastError(napi_status.napi_generic_failure) } - emnapiAsyncWorkerQueue.splice(emnapiAsyncWorkerQueue.indexOf(work), 1) + emnapiAsyncWorkerQueue.splice(workQueueIndex, 1) const complete = HEAP32[(work + 8) >> 2] if (complete !== NULL) { const envObject = emnapi.envStore.get(env)! From cab481ecea8baf8f218f1b7748db48ce658c4144 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 10:56:16 +0800 Subject: [PATCH 09/14] make C++ AsyncWorker class available --- packages/emnapi/include/napi-inl.h | 360 ++++++++++++++--------------- packages/emnapi/include/napi.h | 126 +++++----- 2 files changed, 243 insertions(+), 243 deletions(-) diff --git a/packages/emnapi/include/napi-inl.h b/packages/emnapi/include/napi-inl.h index 4ffaacc0..7dddacd8 100644 --- a/packages/emnapi/include/napi-inl.h +++ b/packages/emnapi/include/napi-inl.h @@ -4759,212 +4759,212 @@ inline Value EscapableHandleScope::Escape(napi_value escapee) { // // AsyncWorker class // //////////////////////////////////////////////////////////////////////////////// -// inline AsyncWorker::AsyncWorker(const Function& callback) -// : AsyncWorker(callback, "generic") { -// } +inline AsyncWorker::AsyncWorker(const Function& callback) + : AsyncWorker(callback, "generic") { +} -// inline AsyncWorker::AsyncWorker(const Function& callback, -// const char* resource_name) -// : AsyncWorker(callback, resource_name, Object::New(callback.Env())) { -// } +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name) + : AsyncWorker(callback, resource_name, Object::New(callback.Env())) { +} -// inline AsyncWorker::AsyncWorker(const Function& callback, -// const char* resource_name, -// const Object& resource) -// : AsyncWorker(Object::New(callback.Env()), -// callback, -// resource_name, -// resource) { -// } +inline AsyncWorker::AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncWorker(Object::New(callback.Env()), + callback, + resource_name, + resource) { +} -// inline AsyncWorker::AsyncWorker(const Object& receiver, -// const Function& callback) -// : AsyncWorker(receiver, callback, "generic") { -// } +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback) + : AsyncWorker(receiver, callback, "generic") { +} -// inline AsyncWorker::AsyncWorker(const Object& receiver, -// const Function& callback, -// const char* resource_name) -// : AsyncWorker(receiver, -// callback, -// resource_name, -// Object::New(callback.Env())) { -// } +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncWorker(receiver, + callback, + resource_name, + Object::New(callback.Env())) { +} -// inline AsyncWorker::AsyncWorker(const Object& receiver, -// const Function& callback, -// const char* resource_name, -// const Object& resource) -// : _env(callback.Env()), -// _receiver(Napi::Persistent(receiver)), -// _callback(Napi::Persistent(callback)), -// _suppress_destruct(false) { -// napi_value resource_id; -// napi_status status = napi_create_string_latin1( -// _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); -// NAPI_THROW_IF_FAILED_VOID(_env, status); +inline AsyncWorker::AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : _env(callback.Env()), + _receiver(Napi::Persistent(receiver)), + _callback(Napi::Persistent(callback)), + _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); -// status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, -// OnAsyncWorkComplete, this, &_work); -// NAPI_THROW_IF_FAILED_VOID(_env, status); -// } + status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, + OnAsyncWorkComplete, this, &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} -// inline AsyncWorker::AsyncWorker(Napi::Env env) -// : AsyncWorker(env, "generic") { -// } +inline AsyncWorker::AsyncWorker(Napi::Env env) + : AsyncWorker(env, "generic") { +} -// inline AsyncWorker::AsyncWorker(Napi::Env env, -// const char* resource_name) -// : AsyncWorker(env, resource_name, Object::New(env)) { -// } +inline AsyncWorker::AsyncWorker(Napi::Env env, + const char* resource_name) + : AsyncWorker(env, resource_name, Object::New(env)) { +} -// inline AsyncWorker::AsyncWorker(Napi::Env env, -// const char* resource_name, -// const Object& resource) -// : _env(env), -// _receiver(), -// _callback(), -// _suppress_destruct(false) { -// napi_value resource_id; -// napi_status status = napi_create_string_latin1( -// _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); -// NAPI_THROW_IF_FAILED_VOID(_env, status); +inline AsyncWorker::AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : _env(env), + _receiver(), + _callback(), + _suppress_destruct(false) { + napi_value resource_id; + napi_status status = napi_create_string_latin1( + _env, resource_name, NAPI_AUTO_LENGTH, &resource_id); + NAPI_THROW_IF_FAILED_VOID(_env, status); -// status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, -// OnAsyncWorkComplete, this, &_work); -// NAPI_THROW_IF_FAILED_VOID(_env, status); -// } + status = napi_create_async_work(_env, resource, resource_id, OnAsyncWorkExecute, + OnAsyncWorkComplete, this, &_work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} -// inline AsyncWorker::~AsyncWorker() { -// if (_work != nullptr) { -// napi_delete_async_work(_env, _work); -// _work = nullptr; -// } -// } +inline AsyncWorker::~AsyncWorker() { + if (_work != nullptr) { + napi_delete_async_work(_env, _work); + _work = nullptr; + } +} -// inline void AsyncWorker::Destroy() { -// delete this; -// } +inline void AsyncWorker::Destroy() { + delete this; +} -// inline AsyncWorker::AsyncWorker(AsyncWorker&& other) { -// _env = other._env; -// other._env = nullptr; -// _work = other._work; -// other._work = nullptr; -// _receiver = std::move(other._receiver); -// _callback = std::move(other._callback); -// _error = std::move(other._error); -// _suppress_destruct = other._suppress_destruct; -// } +inline AsyncWorker::AsyncWorker(AsyncWorker&& other) { + _env = other._env; + other._env = nullptr; + _work = other._work; + other._work = nullptr; + _receiver = std::move(other._receiver); + _callback = std::move(other._callback); + _error = std::move(other._error); + _suppress_destruct = other._suppress_destruct; +} -// inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) { -// _env = other._env; -// other._env = nullptr; -// _work = other._work; -// other._work = nullptr; -// _receiver = std::move(other._receiver); -// _callback = std::move(other._callback); -// _error = std::move(other._error); -// _suppress_destruct = other._suppress_destruct; -// return *this; -// } +inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) { + _env = other._env; + other._env = nullptr; + _work = other._work; + other._work = nullptr; + _receiver = std::move(other._receiver); + _callback = std::move(other._callback); + _error = std::move(other._error); + _suppress_destruct = other._suppress_destruct; + return *this; +} -// inline AsyncWorker::operator napi_async_work() const { -// return _work; -// } +inline AsyncWorker::operator napi_async_work() const { + return _work; +} -// inline Napi::Env AsyncWorker::Env() const { -// return Napi::Env(_env); -// } +inline Napi::Env AsyncWorker::Env() const { + return Napi::Env(_env); +} -// inline void AsyncWorker::Queue() { -// napi_status status = napi_queue_async_work(_env, _work); -// NAPI_THROW_IF_FAILED_VOID(_env, status); -// } +inline void AsyncWorker::Queue() { + napi_status status = napi_queue_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} -// inline void AsyncWorker::Cancel() { -// napi_status status = napi_cancel_async_work(_env, _work); -// NAPI_THROW_IF_FAILED_VOID(_env, status); -// } +inline void AsyncWorker::Cancel() { + napi_status status = napi_cancel_async_work(_env, _work); + NAPI_THROW_IF_FAILED_VOID(_env, status); +} -// inline ObjectReference& AsyncWorker::Receiver() { -// return _receiver; -// } +inline ObjectReference& AsyncWorker::Receiver() { + return _receiver; +} -// inline FunctionReference& AsyncWorker::Callback() { -// return _callback; -// } +inline FunctionReference& AsyncWorker::Callback() { + return _callback; +} -// inline void AsyncWorker::SuppressDestruct() { -// _suppress_destruct = true; -// } +inline void AsyncWorker::SuppressDestruct() { + _suppress_destruct = true; +} -// inline void AsyncWorker::OnOK() { -// if (!_callback.IsEmpty()) { -// _callback.Call(_receiver.Value(), GetResult(_callback.Env())); -// } -// } +inline void AsyncWorker::OnOK() { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), GetResult(_callback.Env())); + } +} -// inline void AsyncWorker::OnError(const Error& e) { -// if (!_callback.IsEmpty()) { -// _callback.Call(_receiver.Value(), std::initializer_list{ e.Value() }); -// } -// } +inline void AsyncWorker::OnError(const Error& e) { + if (!_callback.IsEmpty()) { + _callback.Call(_receiver.Value(), std::initializer_list{ e.Value() }); + } +} -// inline void AsyncWorker::SetError(const std::string& error) { -// _error = error; -// } +inline void AsyncWorker::SetError(const std::string& error) { + _error = error; +} -// inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { -// return {}; -// } -// // The OnAsyncWorkExecute method receives an napi_env argument. However, do NOT -// // use it within this method, as it does not run on the JavaScript thread and -// // must not run any method that would cause JavaScript to run. In practice, -// // this means that almost any use of napi_env will be incorrect. -// inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { -// AsyncWorker* self = static_cast(asyncworker); -// self->OnExecute(env); -// } -// // The OnExecute method receives an napi_env argument. However, do NOT -// // use it within this method, as it does not run on the JavaScript thread and -// // must not run any method that would cause JavaScript to run. In practice, -// // this means that almost any use of napi_env will be incorrect. -// inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { -// #ifdef NAPI_CPP_EXCEPTIONS -// try { -// Execute(); -// } catch (const std::exception& e) { -// SetError(e.what()); -// } -// #else // NAPI_CPP_EXCEPTIONS -// Execute(); -// #endif // NAPI_CPP_EXCEPTIONS -// } +inline std::vector AsyncWorker::GetResult(Napi::Env /*env*/) { + return {}; +} +// The OnAsyncWorkExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnExecute(env); +} +// The OnExecute method receives an napi_env argument. However, do NOT +// use it within this method, as it does not run on the JavaScript thread and +// must not run any method that would cause JavaScript to run. In practice, +// this means that almost any use of napi_env will be incorrect. +inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { +#ifdef NAPI_CPP_EXCEPTIONS + try { + Execute(); + } catch (const std::exception& e) { + SetError(e.what()); + } +#else // NAPI_CPP_EXCEPTIONS + Execute(); +#endif // NAPI_CPP_EXCEPTIONS +} -// inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, -// napi_status status, -// void* asyncworker) { -// AsyncWorker* self = static_cast(asyncworker); -// self->OnWorkComplete(env, status); -// } -// inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { -// if (status != napi_cancelled) { -// HandleScope scope(_env); -// details::WrapCallback([&] { -// if (_error.size() == 0) { -// OnOK(); -// } -// else { -// OnError(Error::New(_env, _error)); -// } -// return nullptr; -// }); -// } -// if (!_suppress_destruct) { -// Destroy(); -// } -// } +inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker) { + AsyncWorker* self = static_cast(asyncworker); + self->OnWorkComplete(env, status); +} +inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { + if (status != napi_cancelled) { + HandleScope scope(_env); + details::WrapCallback([&] { + if (_error.size() == 0) { + OnOK(); + } + else { + OnError(Error::New(_env, _error)); + } + return nullptr; + }); + } + if (!_suppress_destruct) { + Destroy(); + } +} #if (NAPI_VERSION > 3 && !defined(__wasm32__)) //////////////////////////////////////////////////////////////////////////////// diff --git a/packages/emnapi/include/napi.h b/packages/emnapi/include/napi.h index cab10a25..49516d35 100644 --- a/packages/emnapi/include/napi.h +++ b/packages/emnapi/include/napi.h @@ -2335,75 +2335,75 @@ namespace NAPI_CPP_CUSTOM_NAMESPACE { // napi_async_context _context; // }; - // class AsyncWorker { - // public: - // virtual ~AsyncWorker(); + class AsyncWorker { + public: + virtual ~AsyncWorker(); - // // An async worker can be moved but cannot be copied. - // AsyncWorker(AsyncWorker&& other); - // AsyncWorker& operator =(AsyncWorker&& other); - // NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) + // An async worker can be moved but cannot be copied. + AsyncWorker(AsyncWorker&& other); + AsyncWorker& operator =(AsyncWorker&& other); + NAPI_DISALLOW_ASSIGN_COPY(AsyncWorker) - // operator napi_async_work() const; + operator napi_async_work() const; - // Napi::Env Env() const; + Napi::Env Env() const; - // void Queue(); - // void Cancel(); - // void SuppressDestruct(); - - // ObjectReference& Receiver(); - // FunctionReference& Callback(); - - // virtual void OnExecute(Napi::Env env); - // virtual void OnWorkComplete(Napi::Env env, - // napi_status status); - - // protected: - // explicit AsyncWorker(const Function& callback); - // explicit AsyncWorker(const Function& callback, - // const char* resource_name); - // explicit AsyncWorker(const Function& callback, - // const char* resource_name, - // const Object& resource); - // explicit AsyncWorker(const Object& receiver, - // const Function& callback); - // explicit AsyncWorker(const Object& receiver, - // const Function& callback, - // const char* resource_name); - // explicit AsyncWorker(const Object& receiver, - // const Function& callback, - // const char* resource_name, - // const Object& resource); - - // explicit AsyncWorker(Napi::Env env); - // explicit AsyncWorker(Napi::Env env, - // const char* resource_name); - // explicit AsyncWorker(Napi::Env env, - // const char* resource_name, - // const Object& resource); - - // virtual void Execute() = 0; - // virtual void OnOK(); - // virtual void OnError(const Error& e); - // virtual void Destroy(); - // virtual std::vector GetResult(Napi::Env env); - - // void SetError(const std::string& error); + void Queue(); + void Cancel(); + void SuppressDestruct(); - // private: - // static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); - // static inline void OnAsyncWorkComplete(napi_env env, - // napi_status status, - // void* asyncworker); + ObjectReference& Receiver(); + FunctionReference& Callback(); - // napi_env _env; - // napi_async_work _work; - // ObjectReference _receiver; - // FunctionReference _callback; - // std::string _error; - // bool _suppress_destruct; - // }; + virtual void OnExecute(Napi::Env env); + virtual void OnWorkComplete(Napi::Env env, + napi_status status); + + protected: + explicit AsyncWorker(const Function& callback); + explicit AsyncWorker(const Function& callback, + const char* resource_name); + explicit AsyncWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncWorker(const Object& receiver, + const Function& callback); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + + explicit AsyncWorker(Napi::Env env); + explicit AsyncWorker(Napi::Env env, + const char* resource_name); + explicit AsyncWorker(Napi::Env env, + const char* resource_name, + const Object& resource); + + virtual void Execute() = 0; + virtual void OnOK(); + virtual void OnError(const Error& e); + virtual void Destroy(); + virtual std::vector GetResult(Napi::Env env); + + void SetError(const std::string& error); + + private: + static inline void OnAsyncWorkExecute(napi_env env, void* asyncworker); + static inline void OnAsyncWorkComplete(napi_env env, + napi_status status, + void* asyncworker); + + napi_env _env; + napi_async_work _work; + ObjectReference _receiver; + FunctionReference _callback; + std::string _error; + bool _suppress_destruct; + }; #if (NAPI_VERSION > 3 && !defined(__wasm32__)) class ThreadSafeFunction { From fe1985fc7334fd02a0d199d2c7d5f12f40819db0 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 11:25:31 +0800 Subject: [PATCH 10/14] cleanup code --- packages/emnapi/src/emnapi.c | 46 +++++-------------- packages/emnapi/src/simple-async-operation.ts | 4 +- 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/packages/emnapi/src/emnapi.c b/packages/emnapi/src/emnapi.c index 59b5becf..2942f9e1 100644 --- a/packages/emnapi/src/emnapi.c +++ b/packages/emnapi/src/emnapi.c @@ -1,13 +1,13 @@ -#include #include -#include "emnapi.h" -#include "node_api.h" #ifdef __EMSCRIPTEN_PTHREADS__ -// #include #include #endif +#include +#include "emnapi.h" +#include "node_api.h" + #define CHECK_ENV(env) \ do { \ if ((env) == NULL) { \ @@ -61,26 +61,11 @@ const char* emnapi_error_messages[] = { #define EMNAPI_MOD_NAME_X_HELPER(modname) #modname #define EMNAPI_MOD_NAME_X(modname) EMNAPI_MOD_NAME_X_HELPER(modname) -// #ifdef __EMSCRIPTEN_PTHREADS__ -// void* returner_main(void* queue) { -// emscripten_exit_with_live_runtime(); -// } - -// static pthread_t worker_thread = NULL; -// static em_proxying_queue* proxy_queue = NULL; -// #endif - EMSCRIPTEN_KEEPALIVE void _emnapi_runtime_init(int* malloc_p, int* free_p, const char** key, const char*** error_messages) { -// #ifdef __EMSCRIPTEN_PTHREADS__ - // proxy_queue = em_proxying_queue_create(); - // pthread_create(&worker_thread, NULL, returner_main, proxy_queue); - // pthread_detach(worker_thread); -// #endif - if (malloc_p) *malloc_p = (int)(malloc); if (free_p) *free_p = (int)(free); if (key) { @@ -92,10 +77,8 @@ void _emnapi_runtime_init(int* malloc_p, napi_status napi_get_node_version(napi_env env, const napi_node_version** version) { - if (env == NULL) return napi_invalid_arg; - if (version == NULL) { - return napi_set_last_error(env, napi_invalid_arg, 0, NULL); - } + CHECK_ENV(env); + CHECK_ARG(env, version); static napi_node_version node_version = { 16, 15, @@ -109,10 +92,8 @@ napi_get_node_version(napi_env env, napi_status emnapi_get_emscripten_version(napi_env env, const emnapi_emscripten_version** version) { - if (env == NULL) return napi_invalid_arg; - if (version == NULL) { - return napi_set_last_error(env, napi_invalid_arg, 0, NULL); - } + CHECK_ENV(env); + CHECK_ARG(env, version); static emnapi_emscripten_version emscripten_version = { __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, @@ -141,7 +122,7 @@ typedef struct worker_count { // extern void _emnapi_delete_async_work_js(napi_async_work work); extern void _emnapi_queue_async_work_js(napi_async_work work); extern void _emnapi_on_execute_async_work_js(napi_async_work work); -extern int _emnapi_get_worker_count(worker_count* count); +extern int _emnapi_get_worker_count_js(worker_count* count); napi_async_work _emnapi_async_work_init( napi_env env, @@ -165,7 +146,7 @@ void _emnapi_async_work_destroy(napi_async_work work) { void* _emnapi_on_execute_async_work(void* arg) { napi_async_work work = (napi_async_work) arg; work->execute(work->env, work->data); - _emnapi_on_execute_async_work_js(work); // postMessage to main thread + _emnapi_on_execute_async_work_js(work); // postMessage to main thread return NULL; } #endif @@ -222,18 +203,13 @@ napi_status napi_queue_async_work(napi_env env, napi_async_work work) { CHECK_ARG(env, work); worker_count count; - _emnapi_get_worker_count(&count); + _emnapi_get_worker_count_js(&count); if (count.unused > 0 || count.running == 0) { _emnapi_execute_async_work(work); } else { _emnapi_queue_async_work_js(work); // queue work } - // work->tid = worker_thread; - // _emnapi_queue_async_work_js(work); // listen complete event - // emscripten_proxy_async(proxy_queue, worker_thread, - // _emnapi_on_execute_async_work, work); - return napi_clear_last_error(env); #else return napi_set_last_error(env, napi_generic_failure, 0, NULL); diff --git a/packages/emnapi/src/simple-async-operation.ts b/packages/emnapi/src/simple-async-operation.ts index d6a42008..5e6dab58 100644 --- a/packages/emnapi/src/simple-async-operation.ts +++ b/packages/emnapi/src/simple-async-operation.ts @@ -18,8 +18,8 @@ mergeInto(LibraryManager.library, { 'return r;' + '};', - _emnapi_get_worker_count__deps: ['$PThread'], - _emnapi_get_worker_count: function (struct: number) { + _emnapi_get_worker_count_js__deps: ['$PThread'], + _emnapi_get_worker_count_js: function (struct: number) { const address = struct >> 2 HEAP32[address] = PThread.unusedWorkers.length HEAP32[address + 1] = PThread.runningWorkers.length From 60d45eb3a992a10fc1fa1281dcccb5eecd418980 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 12:27:45 +0800 Subject: [PATCH 11/14] readme --- packages/emnapi/README.md | 6 ++++-- packages/emnapi/README_CN.md | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/emnapi/README.md b/packages/emnapi/README.md index 90225889..c34f4bbf 100644 --- a/packages/emnapi/README.md +++ b/packages/emnapi/README.md @@ -4,9 +4,11 @@ [Node-API (version 8)](https://nodejs.org/docs/v16.15.0/api/n-api.html) implementation for [Emscripten](https://emscripten.org/index.html), based on Node.js v16.15.0. -[中文 README](https://github.com/toyobayashi/emnapi/tree/main/README_CN.md). +[中文 README](https://github.com/toyobayashi/emnapi/tree/main/packages/emnapi/README_CN.md). -[See documentation for more details](https://emnapi-docs.vercel.app/) +[See documentation for more details](https://emnapi-docs.vercel.app/guide/) + +[Full API List](https://emnapi-docs.vercel.app/reference/list.html) ## Quick Start diff --git a/packages/emnapi/README_CN.md b/packages/emnapi/README_CN.md index 1d093ae4..df1d35c2 100644 --- a/packages/emnapi/README_CN.md +++ b/packages/emnapi/README_CN.md @@ -4,7 +4,9 @@ 适用于 [Emscripten](https://emscripten.org/index.html) 的 [Node-API (version 8)](https://nodejs.org/dist/latest-v16.x/docs/api/n-api.html) 实现,基于 Node.js v16.15.0 -[查看文档](https://emnapi-docs.vercel.app/) +[查看文档](https://emnapi-docs.vercel.app/zh/guide/) + +[完整的 API 列表](https://emnapi-docs.vercel.app/zh/reference/list.html) ## 快速开始 From b9ba92dcd1bc2d1906776c4e496396a2fa0983b6 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 12:35:00 +0800 Subject: [PATCH 12/14] test async in browser --- packages/test/async/async.html | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 packages/test/async/async.html diff --git a/packages/test/async/async.html b/packages/test/async/async.html new file mode 100644 index 00000000..63aa2ce3 --- /dev/null +++ b/packages/test/async/async.html @@ -0,0 +1,63 @@ + + + + + + + async + + + + + + + From b4ccce20f555ef41f5921676635793f1ff966f7f Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 14:05:08 +0800 Subject: [PATCH 13/14] invoke complete in setTimeout if work is canceled --- packages/emnapi/src/simple-async-operation.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/emnapi/src/simple-async-operation.ts b/packages/emnapi/src/simple-async-operation.ts index 5e6dab58..916755df 100644 --- a/packages/emnapi/src/simple-async-operation.ts +++ b/packages/emnapi/src/simple-async-operation.ts @@ -90,17 +90,19 @@ function napi_cancel_async_work (env: napi_env, work: number): napi_status { emnapiAsyncWorkerQueue.splice(workQueueIndex, 1) const complete = HEAP32[(work + 8) >> 2] if (complete !== NULL) { - const envObject = emnapi.envStore.get(env)! - const scope = envObject.openScope(emnapi.HandleScope) - try { - envObject.callIntoModule((envObject) => { - envObject.call_viii(complete, env, napi_status.napi_cancelled, HEAP32[(work + 12) >> 2]) - }) - } catch (err) { + setTimeout(() => { + const envObject = emnapi.envStore.get(env)! + const scope = envObject.openScope(emnapi.HandleScope) + try { + envObject.callIntoModule((envObject) => { + envObject.call_viii(complete, env, napi_status.napi_cancelled, HEAP32[(work + 12) >> 2]) + }) + } catch (err) { + envObject.closeScope(scope) + throw err + } envObject.closeScope(scope) - throw err - } - envObject.closeScope(scope) + }) } return envObject.clearLastError() From 5cc977a16a4893da5d0e3b8ac97f9fdadaf027a8 Mon Sep 17 00:00:00 2001 From: toyobayashi Date: Fri, 17 Jun 2022 14:32:10 +0800 Subject: [PATCH 14/14] add listener only once for every worker --- packages/emnapi/src/simple-async-operation.ts | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/packages/emnapi/src/simple-async-operation.ts b/packages/emnapi/src/simple-async-operation.ts index 916755df..c3f9ce10 100644 --- a/packages/emnapi/src/simple-async-operation.ts +++ b/packages/emnapi/src/simple-async-operation.ts @@ -35,45 +35,40 @@ mergeInto(LibraryManager.library, { }) function _emnapi_queue_async_work_js (work: number): void { - const tidAddr = (work + 16) >> 2 - const tid = HEAP32[tidAddr] + const tid = HEAP32[(work + 16) >> 2] if (tid === 0) { emnapiAsyncWorkerQueue.push(work) return } - const env = HEAP32[work >> 2] - const worker: Worker = PThread.pthreads[tid].worker - const listener: (this: Worker, ev: MessageEvent) => any = (e) => { - const data = ENVIRONMENT_IS_NODE ? e : e.data - if (data.emnapiAsyncWorkPtr === work) { - // remove listener - if (ENVIRONMENT_IS_NODE) { - (worker as any).off('message', listener) - } else { - worker.removeEventListener('message', listener, false) - } - - HEAP32[tidAddr] = 0 // tid - const complete = HEAP32[(work + 8) >> 2] - if (complete !== NULL) { - const envObject = emnapi.envStore.get(env)! - const scope = envObject.openScope(emnapi.HandleScope) - try { - envObject.callIntoModule((envObject) => { - envObject.call_viii(complete, env, napi_status.napi_ok, HEAP32[(work + 12) >> 2]) - }) - } catch (err) { + const worker = PThread.pthreads[tid].worker + if (!worker._emnapiAsyncWorkListener) { + worker._emnapiAsyncWorkListener = function (this: Worker, e: MessageEvent): any { + const data = ENVIRONMENT_IS_NODE ? e : e.data + const w: number = data.emnapiAsyncWorkPtr + if (w) { + const env = HEAP32[w >> 2] + HEAP32[(w + 16) >> 2] = 0 // tid + const complete = HEAP32[(w + 8) >> 2] + if (complete !== NULL) { + const envObject = emnapi.envStore.get(env)! + const scope = envObject.openScope(emnapi.HandleScope) + try { + envObject.callIntoModule((envObject) => { + envObject.call_viii(complete, env, napi_status.napi_ok, HEAP32[(w + 12) >> 2]) + }) + } catch (err) { + envObject.closeScope(scope) + throw err + } envObject.closeScope(scope) - throw err } - envObject.closeScope(scope) } } - } - if (ENVIRONMENT_IS_NODE) { - (worker as any).on('message', listener) - } else { - worker.addEventListener('message', listener, false) + if (ENVIRONMENT_IS_NODE) { + worker.on('message', worker._emnapiAsyncWorkListener) + } else { + worker.addEventListener('message', worker._emnapiAsyncWorkListener, false) + } } }