Skip to content

Commit 8d222d4

Browse files
addaleaxMylesBorins
authored andcommitted
n-api: add helper for addons to get the event loop
Add a utility functions for addons to use when they need a reference to the current event loop. While the libuv API is not directly part of N-API, it provides a quite stable C API as well, and is tightly integrated with Node itself. As a particular use case, without access to the event loop it is hard to do something interesting from inside a N-API finalizer function, since calls into JS and therefore virtually all other N-API functions are not allowed. PR-URL: #17109 Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent 84b7070 commit 8d222d4

File tree

7 files changed

+134
-10
lines changed

7 files changed

+134
-10
lines changed

doc/api/n-api.md

+17
Original file line numberDiff line numberDiff line change
@@ -3686,6 +3686,23 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env,
36863686
- `[in] script`: A JavaScript string containing the script to execute.
36873687
- `[out] result`: The value resulting from having executed the script.
36883688

3689+
## libuv event loop
3690+
3691+
N-API provides a function for getting the current event loop associated with
3692+
a specific `napi_env`.
3693+
3694+
### napi_get_uv_event_loop
3695+
<!-- YAML
3696+
added: REPLACEME
3697+
-->
3698+
```C
3699+
NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env,
3700+
uv_loop_t** loop);
3701+
```
3702+
3703+
- `[in] env`: The environment that the API is invoked under.
3704+
- `[out] loop`: The current libuv loop instance.
3705+
36893706
[Promises]: #n_api_promises
36903707
[Simple Asynchronous Operations]: #n_api_simple_asynchronous_operations
36913708
[Custom Asynchronous Operations]: #n_api_custom_asynchronous_operations

src/node_api.cc

+19-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include "node_api.h"
1919
#include "node_internals.h"
2020

21-
#define NAPI_VERSION 1
21+
#define NAPI_VERSION 2
2222

2323
static
2424
napi_status napi_set_last_error(napi_env env, napi_status error_code,
@@ -28,8 +28,10 @@ static
2828
napi_status napi_clear_last_error(napi_env env);
2929

3030
struct napi_env__ {
31-
explicit napi_env__(v8::Isolate* _isolate): isolate(_isolate),
32-
last_error() {}
31+
explicit napi_env__(v8::Isolate* _isolate, uv_loop_t* _loop)
32+
: isolate(_isolate),
33+
last_error(),
34+
loop(_loop) {}
3335
~napi_env__() {
3436
last_exception.Reset();
3537
wrap_template.Reset();
@@ -43,6 +45,7 @@ struct napi_env__ {
4345
v8::Persistent<v8::ObjectTemplate> accessor_data_template;
4446
napi_extended_error_info last_error;
4547
int open_handle_scopes = 0;
48+
uv_loop_t* loop = nullptr;
4649
};
4750

4851
#define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \
@@ -771,7 +774,7 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
771774
if (value->IsExternal()) {
772775
result = static_cast<napi_env>(value.As<v8::External>()->Value());
773776
} else {
774-
result = new napi_env__(isolate);
777+
result = new napi_env__(isolate, node::GetCurrentEventLoop(isolate));
775778
auto external = v8::External::New(isolate, result);
776779

777780
// 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) {
34013404
return napi_clear_last_error(env);
34023405
}
34033406

3407+
napi_status napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) {
3408+
CHECK_ENV(env);
3409+
CHECK_ARG(env, loop);
3410+
*loop = env->loop;
3411+
return napi_clear_last_error(env);
3412+
}
3413+
34043414
napi_status napi_queue_async_work(napi_env env, napi_async_work work) {
34053415
CHECK_ENV(env);
34063416
CHECK_ARG(env, work);
34073417

3408-
// Consider: Encapsulate the uv_loop_t into an opaque pointer parameter.
3409-
// Currently the environment event loop is the same as the UV default loop.
3410-
// Someday (if node ever supports multiple isolates), it may be better to get
3411-
// the loop from node::Environment::GetCurrent(env->isolate)->event_loop();
3412-
uv_loop_t* event_loop = uv_default_loop();
3418+
napi_status status;
3419+
uv_loop_t* event_loop = nullptr;
3420+
status = napi_get_uv_event_loop(env, &event_loop);
3421+
if (status != napi_ok)
3422+
return napi_set_last_error(env, status);
34133423

34143424
uvimpl::Work* w = reinterpret_cast<uvimpl::Work*>(work);
34153425

src/node_api.h

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <stdbool.h>
1515
#include "node_api_types.h"
1616

17+
struct uv_loop_s; // Forward declaration.
18+
1719
#ifdef _WIN32
1820
#ifdef BUILDING_NODE_EXTENSION
1921
#ifdef EXTERNAL_NAPI
@@ -581,6 +583,10 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env,
581583
napi_value script,
582584
napi_value* result);
583585

586+
// Return the current libuv event loop for a given environment
587+
NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env,
588+
struct uv_loop_s** loop);
589+
584590
EXTERN_C_END
585591

586592
#endif // SRC_NODE_API_H_

test/addons-napi/test_general/test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ assert.ok(test_general.testGetPrototype(baseObject) !==
3434

3535
// test version management functions
3636
// expected version is currently 1
37-
assert.strictEqual(test_general.testGetVersion(), 1);
37+
assert.strictEqual(test_general.testGetVersion(), 2);
3838

3939
const [ major, minor, patch, release ] = test_general.testGetNodeVersion();
4040
assert.strictEqual(process.version.split('-')[0],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "test_uv_loop",
5+
"sources": [ "test_uv_loop.cc" ]
6+
}
7+
]
8+
}

test/addons-napi/test_uv_loop/test.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const { SetImmediate } = require(`./build/${common.buildType}/test_uv_loop`);
4+
5+
SetImmediate(common.mustCall());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include <node_api.h>
2+
#include <uv.h>
3+
#include <utility>
4+
#include <memory>
5+
#include <assert.h>
6+
#include "../common.h"
7+
8+
template <typename T>
9+
void* SetImmediate(napi_env env, T&& cb) {
10+
T* ptr = new T(std::move(cb));
11+
uv_loop_t* loop = nullptr;
12+
uv_check_t* check = new uv_check_t;
13+
check->data = ptr;
14+
NAPI_ASSERT(env,
15+
napi_get_uv_event_loop(env, &loop) == napi_ok,
16+
"can get event loop");
17+
uv_check_init(loop, check);
18+
uv_check_start(check, [](uv_check_t* check) {
19+
std::unique_ptr<T> ptr {static_cast<T*>(check->data)};
20+
T cb = std::move(*ptr);
21+
uv_close(reinterpret_cast<uv_handle_t*>(check), [](uv_handle_t* handle) {
22+
delete reinterpret_cast<uv_check_t*>(handle);
23+
});
24+
25+
assert(cb() != nullptr);
26+
});
27+
return nullptr;
28+
}
29+
30+
static char dummy;
31+
32+
napi_value SetImmediateBinding(napi_env env, napi_callback_info info) {
33+
size_t argc = 1;
34+
napi_value argv[1];
35+
napi_value _this;
36+
void* data;
37+
NAPI_CALL(env,
38+
napi_get_cb_info(env, info, &argc, argv, &_this, &data));
39+
NAPI_ASSERT(env, argc >= 1, "Not enough arguments, expected 1.");
40+
41+
napi_valuetype t;
42+
NAPI_CALL(env, napi_typeof(env, argv[0], &t));
43+
NAPI_ASSERT(env, t == napi_function,
44+
"Wrong first argument, function expected.");
45+
46+
napi_ref cbref;
47+
NAPI_CALL(env,
48+
napi_create_reference(env, argv[0], 1, &cbref));
49+
50+
SetImmediate(env, [=]() -> char* {
51+
napi_value undefined;
52+
napi_value callback;
53+
napi_handle_scope scope;
54+
NAPI_CALL(env, napi_open_handle_scope(env, &scope));
55+
NAPI_CALL(env, napi_get_undefined(env, &undefined));
56+
NAPI_CALL(env, napi_get_reference_value(env, cbref, &callback));
57+
NAPI_CALL(env, napi_delete_reference(env, cbref));
58+
NAPI_CALL(env,
59+
napi_call_function(env, undefined, callback, 0, nullptr, nullptr));
60+
NAPI_CALL(env, napi_close_handle_scope(env, scope));
61+
return &dummy;
62+
});
63+
64+
return nullptr;
65+
}
66+
67+
napi_value Init(napi_env env, napi_value exports) {
68+
napi_property_descriptor properties[] = {
69+
DECLARE_NAPI_PROPERTY("SetImmediate", SetImmediateBinding)
70+
};
71+
72+
NAPI_CALL(env, napi_define_properties(
73+
env, exports, sizeof(properties) / sizeof(*properties), properties));
74+
75+
return exports;
76+
}
77+
78+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

0 commit comments

Comments
 (0)