Skip to content

Commit 77584c5

Browse files
committed
src: add MakeCallback() taking an Environment
node::MakeCallback() looks up the Environment from the creation context of the callback on every call. An addon that calls into the same Environment many times, a server calling one handler per request for example, can now take it once with GetCurrentEnvironment() and pass it. The callback must belong to that Environment. Add a benchmark, napi/make_callback_env, comparing the two overloads. Refs: nodejs/performance#24 Signed-off-by: Nigro Simone <nigro.simone@gmail.com>
1 parent 1a639d6 commit 77584c5

5 files changed

Lines changed: 127 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <node.h>
2+
#include <uv.h>
3+
#include <v8.h>
4+
5+
using v8::Context;
6+
using v8::Function;
7+
using v8::FunctionCallbackInfo;
8+
using v8::Global;
9+
using v8::HandleScope;
10+
using v8::Isolate;
11+
using v8::Local;
12+
using v8::Object;
13+
using v8::Value;
14+
15+
// Same order as the types in index.js.
16+
enum Type { kMakeCallback, kMakeCallbackEnv };
17+
18+
struct State {
19+
uv_timer_t timer;
20+
Isolate* isolate;
21+
node::Environment* env;
22+
int64_t n;
23+
Type type;
24+
Global<Function> fn;
25+
Global<Function> done;
26+
};
27+
28+
static void OnTimer(uv_timer_t* handle) {
29+
State* state = static_cast<State*>(handle->data);
30+
Isolate* isolate = state->isolate;
31+
HandleScope handle_scope(isolate);
32+
Local<Function> fn = state->fn.Get(isolate);
33+
Local<Context> context = fn->GetCreationContextChecked(isolate);
34+
Context::Scope context_scope(context);
35+
Local<Object> recv = context->Global();
36+
for (int64_t i = 0; i < state->n; i++) {
37+
HandleScope inner_scope(isolate);
38+
if (state->type == kMakeCallbackEnv) {
39+
(void)node::MakeCallback(state->env, recv, fn, 0, nullptr, {0, 0});
40+
} else {
41+
(void)node::MakeCallback(isolate, recv, fn, 0, nullptr, {0, 0});
42+
}
43+
}
44+
Local<Function> done = state->done.Get(isolate);
45+
(void)node::MakeCallback(isolate, recv, done, 0, nullptr, {0, 0});
46+
uv_close(reinterpret_cast<uv_handle_t*>(&state->timer),
47+
[](uv_handle_t* h) { delete static_cast<State*>(h->data); });
48+
}
49+
50+
// run(n, fn, done, type): calls fn n times from a timer, then calls done.
51+
static void Run(const FunctionCallbackInfo<Value>& args) {
52+
Isolate* isolate = args.GetIsolate();
53+
Local<Context> context = isolate->GetCurrentContext();
54+
State* state = new State;
55+
state->isolate = isolate;
56+
// Found once, the point of the MakeCallbackEnv type.
57+
state->env = node::GetCurrentEnvironment(context);
58+
state->n = args[0]->IntegerValue(context).FromJust();
59+
state->fn.Reset(isolate, args[1].As<Function>());
60+
state->done.Reset(isolate, args[2].As<Function>());
61+
state->type = static_cast<Type>(args[3]->Int32Value(context).FromJust());
62+
state->timer.data = state;
63+
uv_timer_init(node::GetCurrentEventLoop(isolate), &state->timer);
64+
uv_timer_start(&state->timer, OnTimer, 0, 0);
65+
}
66+
67+
static void Initialize(Local<Object> target, Local<Value> module, void* data) {
68+
NODE_SET_METHOD(target, "run", Run);
69+
}
70+
71+
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ]
6+
}
7+
]
8+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const common = require('../../common.js');
4+
5+
// node::MakeCallback from a libuv timer, the Environment looked up from the
6+
// callback on every call (MakeCallback) or passed in (MakeCallbackEnv).
7+
8+
let binding;
9+
try {
10+
binding = require(`./build/${common.buildType}/binding`);
11+
} catch {
12+
console.error('napi/make_callback_env/index.js Binding failed to load');
13+
process.exit(0);
14+
}
15+
16+
const types = ['MakeCallback', 'MakeCallbackEnv'];
17+
18+
const bench = common.createBenchmark(main, {
19+
type: types,
20+
n: [1e6, 1e7],
21+
});
22+
23+
function main({ type, n }) {
24+
bench.start();
25+
binding.run(n, () => {}, () => bench.end(n), types.indexOf(type));
26+
}

‎src/api/callback.cc‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,17 @@ MaybeLocal<Value> InternalMakeCallback(Isolate* isolate,
354354
env, recv, callback, argc, argv, asyncContext, context_frame);
355355
}
356356

357+
MaybeLocal<Value> MakeCallback(Environment* env,
358+
Local<Object> recv,
359+
Local<Function> callback,
360+
int argc,
361+
Local<Value> argv[],
362+
async_context asyncContext) {
363+
CHECK_NOT_NULL(env);
364+
return MakeCallbackInEnvironment(
365+
env, recv, callback, argc, argv, asyncContext, Undefined(env->isolate()));
366+
}
367+
357368
MaybeLocal<Value> MakeCallbackInEnvironment(Environment* env,
358369
Local<Object> recv,
359370
const Local<Function> callback,

‎src/node.h‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,17 @@ v8::MaybeLocal<v8::Value> MakeCallback(v8::Isolate* isolate,
16741674
int argc,
16751675
v8::Local<v8::Value>* argv,
16761676
async_context asyncContext);
1677+
/* The same, for an addon that calls into the same Environment many times:
1678+
* `env` is passed in, for example from GetCurrentEnvironment() once, instead
1679+
* of being looked up from the creation context of `callback` on every call.
1680+
* `callback` must belong to `env`. */
1681+
NODE_EXTERN
1682+
v8::MaybeLocal<v8::Value> MakeCallback(Environment* env,
1683+
v8::Local<v8::Object> recv,
1684+
v8::Local<v8::Function> callback,
1685+
int argc,
1686+
v8::Local<v8::Value>* argv,
1687+
async_context asyncContext);
16771688
NODE_EXTERN
16781689
v8::MaybeLocal<v8::Value> MakeCallback(v8::Isolate* isolate,
16791690
v8::Local<v8::Object> recv,

0 commit comments

Comments
 (0)