Skip to content

Commit

Permalink
n-api: add helper for addons to get the event loop
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
addaleax committed Nov 21, 2017
1 parent c0f3bc2 commit 9cf3525
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 10 deletions.
17 changes: 17 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<!-- YAML
added: REPLACEME
-->
```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
Expand Down
28 changes: 19 additions & 9 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
Expand All @@ -43,6 +45,7 @@ struct napi_env__ {
v8::Persistent<v8::ObjectTemplate> 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) \
Expand Down Expand Up @@ -771,7 +774,7 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
if (value->IsExternal()) {
result = static_cast<napi_env>(value.As<v8::External>()->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
Expand Down Expand Up @@ -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<uvimpl::Work*>(work);

Expand Down
6 changes: 6 additions & 0 deletions src/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <stdbool.h>
#include "node_api_types.h"

struct uv_loop_s; // Forward declaration.

#ifdef _WIN32
#ifdef BUILDING_NODE_EXTENSION
#ifdef EXTERNAL_NAPI
Expand Down Expand Up @@ -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_
2 changes: 1 addition & 1 deletion test/addons-napi/test_general/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down
8 changes: 8 additions & 0 deletions test/addons-napi/test_uv_loop/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_uv_loop",
"sources": [ "test_uv_loop.cc" ]
}
]
}
5 changes: 5 additions & 0 deletions test/addons-napi/test_uv_loop/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';
const common = require('../../common');
const { SetImmediate } = require(`./build/${common.buildType}/test_uv_loop`);

SetImmediate(common.mustCall());
78 changes: 78 additions & 0 deletions test/addons-napi/test_uv_loop/test_uv_loop.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#include <node_api.h>
#include <uv.h>
#include <utility>
#include <memory>
#include <assert.h>
#include "../common.h"

template <typename T>
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<T> ptr {static_cast<T*>(check->data)};
T cb = std::move(*ptr);
uv_close(reinterpret_cast<uv_handle_t*>(check), [](uv_handle_t* handle) {
delete reinterpret_cast<uv_check_t*>(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)

0 comments on commit 9cf3525

Please sign in to comment.