Skip to content

Commit 92adb18

Browse files
committed
Initial version of node/py await.
1 parent b8667af commit 92adb18

File tree

3 files changed

+184
-130
lines changed

3 files changed

+184
-130
lines changed

source/loaders/node_loader/include/node_loader/node_loader_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ NODE_LOADER_NO_EXPORT void node_loader_impl_exception(napi_env env, napi_status
5555

5656
NODE_LOADER_NO_EXPORT void node_loader_impl_finalizer(napi_env env, napi_value v, void *data);
5757

58-
NODE_LOADER_NO_EXPORT void node_loader_impl_handle_promise(loader_impl_node node_impl, napi_env env, napi_deferred deferred, void *result, napi_status (*deferred_fn)(napi_env, napi_deferred, napi_value), const char error_str[]);
58+
NODE_LOADER_NO_EXPORT napi_value node_loader_impl_promise_await(loader_impl_node node_impl, napi_env env, const char *name, value *args, size_t size);
5959

6060
NODE_LOADER_NO_EXPORT value node_loader_impl_napi_to_value(loader_impl_node node_impl, napi_env env, napi_value recv, napi_value v);
6161

source/loaders/node_loader/source/node_loader_impl.cpp

Lines changed: 181 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -284,12 +284,37 @@ void node_loader_impl_func_call_js_safe(napi_env env, napi_value js_callback, vo
284284
// async_safe->args->node_impl->env = nullptr;
285285
}
286286

287+
template <typename T>
288+
void node_loader_impl_func_call_js_async_safe(napi_env env, napi_value js_callback, void *context, void *data)
289+
{
290+
(void)js_callback;
291+
292+
if (env == nullptr || context == nullptr || data == nullptr)
293+
{
294+
log_write("metacall", LOG_LEVEL_ERROR, "Invalid arguments passed to js thread async safe function");
295+
return;
296+
}
297+
298+
T *args = static_cast<T *>(data);
299+
node_loader_impl_func_call_js_safe_cast<T> safe_cast(context);
300+
301+
/* Store environment for reentrant calls */
302+
args->node_impl->env = env;
303+
304+
/* Call to the implementation function */
305+
safe_cast.func_ptr(env, args);
306+
307+
/* Clear environment */
308+
// async_safe->args->node_impl->env = nullptr;
309+
}
310+
287311
static napi_value node_loader_impl_async_threadsafe_empty(napi_env, napi_callback_info)
288312
{
289313
/* This is a dirty hack in order to make the threadsafe API work properly,
290314
* as soon as possible it will be good to return to the old method we used in NodeJS 8,
291315
* it was better than this API
292316
*/
317+
293318
return nullptr;
294319
}
295320

@@ -513,18 +538,6 @@ struct loader_impl_async_future_delete_safe_type
513538
node_impl(node_impl), f(f), node_future(node_future) {}
514539
};
515540

516-
struct loader_impl_async_handle_promise_safe_type
517-
{
518-
loader_impl_node node_impl;
519-
napi_deferred deferred;
520-
void *result;
521-
napi_status (*deferred_fn)(napi_env, napi_deferred, napi_value);
522-
const char *error_str;
523-
524-
loader_impl_async_handle_promise_safe_type(loader_impl_node node_impl, napi_deferred deferred, void *result, napi_status (*deferred_fn)(napi_env, napi_deferred, napi_value), const char error_str[]) :
525-
node_impl(node_impl), deferred(deferred), result(result), deferred_fn(deferred_fn), error_str(error_str) {}
526-
};
527-
528541
struct loader_impl_async_destroy_safe_type
529542
{
530543
loader_impl_node node_impl;
@@ -552,7 +565,6 @@ struct loader_impl_node_type
552565
loader_impl_threadsafe_type<loader_impl_async_func_destroy_safe_type> threadsafe_func_destroy;
553566
loader_impl_threadsafe_type<loader_impl_async_future_await_safe_type> threadsafe_future_await;
554567
loader_impl_threadsafe_type<loader_impl_async_future_delete_safe_type> threadsafe_future_delete;
555-
loader_impl_threadsafe_type<loader_impl_async_handle_promise_safe_type> threadsafe_handle_promise;
556568
loader_impl_threadsafe_type<loader_impl_async_destroy_safe_type> threadsafe_destroy;
557569

558570
uv_thread_t thread;
@@ -584,6 +596,73 @@ struct loader_impl_node_type
584596
loader_impl impl;
585597
};
586598

599+
template <typename T>
600+
struct loader_impl_threadsafe_async_type
601+
{
602+
uv_async_t async_handle;
603+
bool initialized;
604+
605+
int initialize(loader_impl_node node_impl, void (*async_cb)(uv_async_t *))
606+
{
607+
int result = uv_async_init(node_impl->thread_loop, &async_handle, async_cb);
608+
609+
initialized = (result == 0);
610+
611+
return result;
612+
}
613+
614+
void invoke(T *data)
615+
{
616+
if (initialized)
617+
{
618+
async_handle.data = static_cast<T *>(data);
619+
uv_async_send(&async_handle);
620+
}
621+
}
622+
623+
void close(void (*close_cb)(uv_handle_t *handle))
624+
{
625+
if (initialized)
626+
{
627+
union
628+
{
629+
uv_handle_t *handle;
630+
uv_async_t *async;
631+
} handle_cast;
632+
633+
handle_cast.async = &async_handle;
634+
635+
uv_close(handle_cast.handle, close_cb);
636+
}
637+
}
638+
};
639+
640+
struct loader_impl_async_handle_promise_safe_type
641+
{
642+
loader_impl_node node_impl;
643+
napi_env env;
644+
napi_deferred deferred;
645+
void *result;
646+
napi_status (*deferred_fn)(napi_env, napi_deferred, napi_value);
647+
const char *error_str;
648+
loader_impl_threadsafe_async_type<loader_impl_async_handle_promise_safe_type> threadsafe_async;
649+
650+
loader_impl_async_handle_promise_safe_type(loader_impl_node node_impl, napi_env env) :
651+
node_impl(node_impl), env(env), result(NULL) {}
652+
653+
~loader_impl_async_handle_promise_safe_type()
654+
{
655+
threadsafe_async.close([](uv_handle_t *handle) {
656+
loader_impl_async_handle_promise_safe_type *handle_promise_safe = static_cast<loader_impl_async_handle_promise_safe_type *>(handle->data);
657+
658+
if (handle_promise_safe->result != NULL)
659+
{
660+
metacall_value_destroy(handle_promise_safe->result);
661+
}
662+
});
663+
}
664+
};
665+
587666
typedef napi_value (*function_resolve_trampoline)(loader_impl_node, napi_env, function_resolve_callback, napi_value, napi_value, void *);
588667
typedef napi_value (*function_reject_trampoline)(loader_impl_node, napi_env, function_reject_callback, napi_value, napi_value, void *);
589668

@@ -3305,6 +3384,9 @@ void node_loader_impl_handle_promise_safe(napi_env env, loader_impl_async_handle
33053384
napi_throw_error(env, nullptr, handle_promise_safe->error_str);
33063385
}
33073386

3387+
/* Close the handle */
3388+
delete handle_promise_safe;
3389+
33083390
/* Close scope */
33093391
status = napi_close_handle_scope(env, handle_scope);
33103392

@@ -3401,7 +3483,6 @@ void *node_loader_impl_register(void *node_impl_ptr, void *env_ptr, void *functi
34013483
node_impl->threadsafe_func_destroy.initialize(env, "node_loader_impl_async_func_destroy_safe", &node_loader_impl_func_destroy_safe);
34023484
node_impl->threadsafe_future_await.initialize(env, "node_loader_impl_async_future_await_safe", &node_loader_impl_future_await_safe);
34033485
node_impl->threadsafe_future_delete.initialize(env, "node_loader_impl_async_future_delete_safe", &node_loader_impl_future_delete_safe);
3404-
node_impl->threadsafe_handle_promise.initialize(env, "node_loader_impl_async_handle_promise_safe", &node_loader_impl_handle_promise_safe);
34053486
node_impl->threadsafe_destroy.initialize(env, "node_loader_impl_async_destroy_safe", &node_loader_impl_destroy_safe);
34063487
}
34073488

@@ -4071,23 +4152,95 @@ int node_loader_impl_discover(loader_impl impl, loader_handle handle, context ct
40714152
return discover_safe.result;
40724153
}
40734154

4074-
void node_loader_impl_handle_promise(loader_impl_node node_impl, napi_env env, napi_deferred deferred, void *result, napi_status (*deferred_fn)(napi_env, napi_deferred, napi_value), const char error_str[])
4155+
void node_loader_impl_handle_promise(loader_impl_async_handle_promise_safe_type *handle_promise_safe, void *result, napi_status (*deferred_fn)(napi_env, napi_deferred, napi_value), const char error_str[])
40754156
{
4076-
loader_impl_async_handle_promise_safe_type handle_promise_safe(node_impl, deferred, result, deferred_fn, error_str);
4157+
handle_promise_safe->result = metacall_value_copy(result);
4158+
handle_promise_safe->deferred_fn = deferred_fn;
4159+
handle_promise_safe->error_str = error_str;
40774160

40784161
/* Check if we are in the JavaScript thread */
4079-
if (node_impl->js_thread_id == std::this_thread::get_id())
4162+
if (handle_promise_safe->node_impl->js_thread_id == std::this_thread::get_id())
40804163
{
40814164
/* We are already in the V8 thread, we can call safely */
4082-
node_loader_impl_handle_promise_safe(env, &handle_promise_safe);
4165+
node_loader_impl_handle_promise_safe(handle_promise_safe->env, handle_promise_safe);
40834166
}
40844167
else
40854168
{
40864169
/* Submit the task to the async queue */
4087-
loader_impl_threadsafe_invoke_type<loader_impl_async_handle_promise_safe_type> invoke(node_impl->threadsafe_handle_promise, handle_promise_safe);
4170+
if (handle_promise_safe->threadsafe_async.initialize(handle_promise_safe->node_impl, [](uv_async_t *handle) {
4171+
loader_impl_async_handle_promise_safe_type *handle_promise_safe = static_cast<loader_impl_async_handle_promise_safe_type *>(handle->data);
4172+
node_loader_impl_handle_promise_safe(handle_promise_safe->env, handle_promise_safe);
4173+
}) != 0)
4174+
{
4175+
log_write("metacall", LOG_LEVEL_ERROR, "Filed to initialize promise safe async handle");
4176+
}
4177+
else
4178+
{
4179+
handle_promise_safe->threadsafe_async.invoke(handle_promise_safe);
4180+
}
40884181
}
40894182
}
40904183

4184+
napi_value node_loader_impl_promise_await(loader_impl_node node_impl, napi_env env, const char *name, value *args, size_t size)
4185+
{
4186+
loader_impl_async_handle_promise_safe_type *handle_promise_safe = new loader_impl_async_handle_promise_safe_type(node_impl, env);
4187+
4188+
if (handle_promise_safe == nullptr)
4189+
{
4190+
napi_throw_error(env, nullptr, "Failed to allocate the promise context");
4191+
4192+
return nullptr;
4193+
}
4194+
4195+
napi_value promise;
4196+
4197+
/* Create the promise */
4198+
napi_status status = napi_create_promise(env, &handle_promise_safe->deferred, &promise);
4199+
4200+
if (status != napi_ok)
4201+
{
4202+
napi_throw_error(env, nullptr, "Failed to create the promise");
4203+
4204+
delete handle_promise_safe;
4205+
4206+
return nullptr;
4207+
}
4208+
4209+
auto resolve = [](void *result, void *data) -> void * {
4210+
static const char promise_error_str[] = "Failed to resolve the promise";
4211+
4212+
loader_impl_async_handle_promise_safe_type *handle_promise_safe = static_cast<loader_impl_async_handle_promise_safe_type *>(data);
4213+
4214+
node_loader_impl_handle_promise(handle_promise_safe, result, &napi_resolve_deferred, promise_error_str);
4215+
4216+
return NULL;
4217+
};
4218+
4219+
auto reject = [](void *result, void *data) -> void * {
4220+
static const char promise_error_str[] = "Failed to reject the promise";
4221+
4222+
loader_impl_async_handle_promise_safe_type *handle_promise_safe = static_cast<loader_impl_async_handle_promise_safe_type *>(data);
4223+
4224+
node_loader_impl_handle_promise(handle_promise_safe, result, &napi_reject_deferred, promise_error_str);
4225+
4226+
return NULL;
4227+
};
4228+
4229+
/* Await to the function */
4230+
void *ret = metacall_await_s(name, args, size, resolve, reject, handle_promise_safe);
4231+
4232+
if (metacall_value_id(ret) == METACALL_THROWABLE)
4233+
{
4234+
napi_value result = node_loader_impl_value_to_napi(node_impl, env, ret);
4235+
4236+
napi_throw(env, result);
4237+
}
4238+
4239+
node_loader_impl_finalizer(env, promise, ret);
4240+
4241+
return promise;
4242+
}
4243+
40914244
#define container_of(ptr, type, member) \
40924245
(type *)((char *)(ptr) - (char *)&((type *)0)->member)
40934246

@@ -4311,10 +4464,16 @@ void node_loader_impl_destroy_safe_impl(loader_impl_node node_impl, napi_env env
43114464
{
43124465
uint32_t ref_count = 0;
43134466
napi_status status;
4467+
napi_handle_scope handle_scope;
43144468

43154469
/* Destroy children loaders */
43164470
loader_unload_children(node_impl->impl);
43174471

4472+
/* Create scope */
4473+
status = napi_open_handle_scope(env, &handle_scope);
4474+
4475+
node_loader_impl_exception(env, status);
4476+
43184477
/* Clear thread safe functions except by destroy one */
43194478
{
43204479
node_impl->threadsafe_initialize.abort(env);
@@ -4328,7 +4487,6 @@ void node_loader_impl_destroy_safe_impl(loader_impl_node node_impl, napi_env env
43284487
node_impl->threadsafe_func_destroy.abort(env);
43294488
node_impl->threadsafe_future_await.abort(env);
43304489
node_impl->threadsafe_future_delete.abort(env);
4331-
node_impl->threadsafe_handle_promise.abort(env);
43324490
}
43334491

43344492
/* Clear persistent references */
@@ -4358,48 +4516,10 @@ void node_loader_impl_destroy_safe_impl(loader_impl_node node_impl, napi_env env
43584516

43594517
node_loader_impl_exception(env, status);
43604518

4361-
/* Clear event loop */
4362-
{
4363-
/* Stop event loop */
4364-
uv_stop(node_impl->thread_loop);
4365-
4366-
/* Clear event loop */
4367-
/* uv_walk(node_impl->thread_loop, node_loader_impl_walk, NULL); */
4368-
4369-
#if 0
4370-
/* TODO: For some reason, this deadlocks in NodeJS benchmark when mixing sync and async calls.
4371-
* It should be reviewed carefully and detect if NodeJS is finalizing properly on multiple cases.
4372-
* Disable it for now in order to make tests pass.
4373-
*/
4374-
while (uv_run(node_impl->thread_loop, UV_RUN_DEFAULT) != 0)
4375-
#if (!defined(NDEBUG) || defined(DEBUG) || defined(_DEBUG) || defined(__DEBUG) || defined(__DEBUG__))
4376-
{
4377-
node_loader_impl_print_handles(node_impl);
4378-
}
4379-
#else
4380-
;
4381-
#endif
4382-
4383-
/* Destroy node loop */
4384-
if (uv_loop_alive(node_impl->thread_loop) != 0)
4385-
{
4386-
/* TODO: Make logs thread safe */
4387-
/* log_write("metacall", LOG_LEVEL_ERROR, "NodeJS event loop should not be alive"); */
4388-
printf("NodeJS Loader Error: NodeJS event loop should not be alive\n");
4389-
fflush(stdout);
4390-
}
4391-
#endif
4519+
/* Close scope */
4520+
status = napi_close_handle_scope(env, handle_scope);
43924521

4393-
/* This evaluates to true always due to stdin and stdout handles,
4394-
which are closed anyway on thread join. So it is removed by now. */
4395-
if (uv_loop_close(node_impl->thread_loop) != UV_EBUSY)
4396-
{
4397-
/* TODO: Make logs thread safe */
4398-
/* log_write("metacall", LOG_LEVEL_ERROR, "NodeJS event loop should not be busy"); */
4399-
printf("NodeJS Loader Error: NodeJS event loop should be busy\n");
4400-
fflush(stdout);
4401-
}
4402-
}
4522+
node_loader_impl_exception(env, status);
44034523

44044524
/* NodeJS Loader needs to register that it is destroyed, because after this step
44054525
* some destructors can be still triggered, before the node_loader->destroy() has

0 commit comments

Comments
 (0)