diff --git a/doc/api/n-api.md b/doc/api/n-api.md index df9038182fbf95..0e94396665ca25 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -3686,6 +3686,23 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env, - `[in] script`: A JavaScript string containing the script to execute. - `[out] result`: The value resulting from having executed the script. +## libuv event loop + +N-API provides a function for getting the current event loop associated with +a specific `napi_env`. + +### napi_get_uv_event_loop + +```C +NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, + uv_loop_t** loop); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[out] loop`: The current libuv loop instance. + [Promises]: #n_api_promises [Simple Asynchronous Operations]: #n_api_simple_asynchronous_operations [Custom Asynchronous Operations]: #n_api_custom_asynchronous_operations diff --git a/src/node_api.cc b/src/node_api.cc index ad922d9ad195db..84e57d03d6237d 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -18,7 +18,7 @@ #include "node_api.h" #include "node_internals.h" -#define NAPI_VERSION 1 +#define NAPI_VERSION 2 static napi_status napi_set_last_error(napi_env env, napi_status error_code, @@ -28,8 +28,10 @@ static napi_status napi_clear_last_error(napi_env env); struct napi_env__ { - explicit napi_env__(v8::Isolate* _isolate): isolate(_isolate), - last_error() {} + explicit napi_env__(v8::Isolate* _isolate, uv_loop_t* _loop) + : isolate(_isolate), + last_error(), + loop(_loop) {} ~napi_env__() { last_exception.Reset(); wrap_template.Reset(); @@ -43,6 +45,7 @@ struct napi_env__ { v8::Persistent accessor_data_template; napi_extended_error_info last_error; int open_handle_scopes = 0; + uv_loop_t* loop = nullptr; }; #define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \ @@ -771,7 +774,7 @@ napi_env GetEnv(v8::Local context) { if (value->IsExternal()) { result = static_cast(value.As()->Value()); } else { - result = new napi_env__(isolate); + result = new napi_env__(isolate, node::GetCurrentEventLoop(isolate)); auto external = v8::External::New(isolate, result); // We must also stop hard if the result of assigning the env to the global @@ -3401,15 +3404,22 @@ napi_status napi_delete_async_work(napi_env env, napi_async_work work) { return napi_clear_last_error(env); } +napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) { + CHECK_ENV(env); + CHECK_ARG(env, loop); + *loop = env->loop; + return napi_clear_last_error(env); +} + napi_status napi_queue_async_work(napi_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); - // Consider: Encapsulate the uv_loop_t into an opaque pointer parameter. - // Currently the environment event loop is the same as the UV default loop. - // Someday (if node ever supports multiple isolates), it may be better to get - // the loop from node::Environment::GetCurrent(env->isolate)->event_loop(); - uv_loop_t* event_loop = uv_default_loop(); + napi_status status; + uv_loop_t* event_loop = nullptr; + status = napi_get_uv_event_loop(env, &event_loop); + if (status != napi_ok) + return napi_set_last_error(env, status); uvimpl::Work* w = reinterpret_cast(work); diff --git a/src/node_api.h b/src/node_api.h index a3a07a64673366..8e5eef8a47728f 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -14,6 +14,8 @@ #include #include "node_api_types.h" +struct uv_loop_s; // Forward declaration. + #ifdef _WIN32 #ifdef BUILDING_NODE_EXTENSION #ifdef EXTERNAL_NAPI @@ -581,6 +583,10 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env, napi_value script, napi_value* result); +// Return the current libuv event loop for a given environment +NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, + struct uv_loop_s** loop); + EXTERN_C_END #endif // SRC_NODE_API_H_ diff --git a/test/addons-napi/test_general/test.js b/test/addons-napi/test_general/test.js index 3ff57922c5372b..ee6618c8121289 100644 --- a/test/addons-napi/test_general/test.js +++ b/test/addons-napi/test_general/test.js @@ -34,7 +34,7 @@ assert.ok(test_general.testGetPrototype(baseObject) !== // test version management functions // expected version is currently 1 -assert.strictEqual(test_general.testGetVersion(), 1); +assert.strictEqual(test_general.testGetVersion(), 2); const [ major, minor, patch, release ] = test_general.testGetNodeVersion(); assert.strictEqual(process.version.split('-')[0], diff --git a/test/addons-napi/test_uv_loop/binding.gyp b/test/addons-napi/test_uv_loop/binding.gyp new file mode 100644 index 00000000000000..81fcfdc592a523 --- /dev/null +++ b/test/addons-napi/test_uv_loop/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_uv_loop", + "sources": [ "test_uv_loop.cc" ] + } + ] +} diff --git a/test/addons-napi/test_uv_loop/test.js b/test/addons-napi/test_uv_loop/test.js new file mode 100644 index 00000000000000..4efc3c6fcd7001 --- /dev/null +++ b/test/addons-napi/test_uv_loop/test.js @@ -0,0 +1,5 @@ +'use strict'; +const common = require('../../common'); +const { SetImmediate } = require(`./build/${common.buildType}/test_uv_loop`); + +SetImmediate(common.mustCall()); diff --git a/test/addons-napi/test_uv_loop/test_uv_loop.cc b/test/addons-napi/test_uv_loop/test_uv_loop.cc new file mode 100644 index 00000000000000..44819f72bb6b9d --- /dev/null +++ b/test/addons-napi/test_uv_loop/test_uv_loop.cc @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include "../common.h" + +template +void* SetImmediate(napi_env env, T&& cb) { + T* ptr = new T(std::move(cb)); + uv_loop_t* loop = nullptr; + uv_check_t* check = new uv_check_t; + check->data = ptr; + NAPI_ASSERT(env, + napi_get_uv_event_loop(env, &loop) == napi_ok, + "can get event loop"); + uv_check_init(loop, check); + uv_check_start(check, [](uv_check_t* check) { + std::unique_ptr ptr {static_cast(check->data)}; + T cb = std::move(*ptr); + uv_close(reinterpret_cast(check), [](uv_handle_t* handle) { + delete reinterpret_cast(handle); + }); + + assert(cb() != nullptr); + }); + return nullptr; +} + +static char dummy; + +napi_value SetImmediateBinding(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value argv[1]; + napi_value _this; + void* data; + NAPI_CALL(env, + napi_get_cb_info(env, info, &argc, argv, &_this, &data)); + NAPI_ASSERT(env, argc >= 1, "Not enough arguments, expected 1."); + + napi_valuetype t; + NAPI_CALL(env, napi_typeof(env, argv[0], &t)); + NAPI_ASSERT(env, t == napi_function, + "Wrong first argument, function expected."); + + napi_ref cbref; + NAPI_CALL(env, + napi_create_reference(env, argv[0], 1, &cbref)); + + SetImmediate(env, [=]() -> char* { + napi_value undefined; + napi_value callback; + napi_handle_scope scope; + NAPI_CALL(env, napi_open_handle_scope(env, &scope)); + NAPI_CALL(env, napi_get_undefined(env, &undefined)); + NAPI_CALL(env, napi_get_reference_value(env, cbref, &callback)); + NAPI_CALL(env, napi_delete_reference(env, cbref)); + NAPI_CALL(env, + napi_call_function(env, undefined, callback, 0, nullptr, nullptr)); + NAPI_CALL(env, napi_close_handle_scope(env, scope)); + return &dummy; + }); + + return nullptr; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("SetImmediate", SetImmediateBinding) + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)