Skip to content

Commit 1186b7a

Browse files
trevnorrisMyles Borins
authored and
Myles Borins
committed
test: add addons test for MakeCallback
Make sure that calling MakeCallback multiple times within the same stack does not allow the nextTickQueue or MicrotaskQueue to be processed in any more than the first MakeCallback call. Check that domains enter/exit poperly with multiple MakeCallback calls and that errors are handled as expected Ref: #7048 PR-URL: #4507 Reviewed-By: Fedor Indutny <[email protected]>
1 parent 981bbcd commit 1186b7a

File tree

3 files changed

+209
-0
lines changed

3 files changed

+209
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#include "node.h"
2+
#include "v8.h"
3+
4+
#include "../../../src/util.h"
5+
6+
using v8::Function;
7+
using v8::FunctionCallbackInfo;
8+
using v8::Isolate;
9+
using v8::Local;
10+
using v8::Object;
11+
using v8::Value;
12+
13+
namespace {
14+
15+
void MakeCallback(const FunctionCallbackInfo<Value>& args) {
16+
CHECK(args[0]->IsObject());
17+
CHECK(args[1]->IsFunction());
18+
Isolate* isolate = args.GetIsolate();
19+
Local<Object> recv = args[0].As<Object>();
20+
Local<Function> method = args[1].As<Function>();
21+
22+
node::MakeCallback(isolate, recv, method, 0, nullptr);
23+
}
24+
25+
void Initialize(Local<Object> target) {
26+
NODE_SET_METHOD(target, "makeCallback", MakeCallback);
27+
}
28+
29+
} // namespace anonymous
30+
31+
NODE_MODULE(binding, Initialize)
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+
}
+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const assert = require('assert');
5+
const domain = require('domain');
6+
const binding = require('./build/Release/binding');
7+
const makeCallback = binding.makeCallback;
8+
9+
// Make sure this is run in the future.
10+
const mustCallCheckDomains = common.mustCall(checkDomains);
11+
12+
13+
// Make sure that using MakeCallback allows the error to propagate.
14+
assert.throws(function() {
15+
makeCallback({}, function() {
16+
throw new Error('hi from domain error');
17+
});
18+
});
19+
20+
21+
// Check the execution order of the nextTickQueue and MicrotaskQueue in
22+
// relation to running multiple MakeCallback's from bootstrap,
23+
// node::MakeCallback() and node::AsyncWrap::MakeCallback().
24+
// TODO(trevnorris): Is there a way to verify this is being run during
25+
// bootstrap?
26+
(function verifyExecutionOrder(arg) {
27+
const results_arr = [];
28+
29+
// Processing of the MicrotaskQueue is manually handled by node. They are not
30+
// processed until after the nextTickQueue has been processed.
31+
Promise.resolve(1).then(common.mustCall(function() {
32+
results_arr.push(7);
33+
}));
34+
35+
// The nextTick should run after all immediately invoked calls.
36+
process.nextTick(common.mustCall(function() {
37+
results_arr.push(3);
38+
39+
// Run same test again but while processing the nextTickQueue to make sure
40+
// the following MakeCallback call breaks in the middle of processing the
41+
// queue and allows the script to run normally.
42+
process.nextTick(common.mustCall(function() {
43+
results_arr.push(6);
44+
}));
45+
46+
makeCallback({}, common.mustCall(function() {
47+
results_arr.push(4);
48+
}));
49+
50+
results_arr.push(5);
51+
}));
52+
53+
results_arr.push(0);
54+
55+
// MakeCallback is calling the function immediately, but should then detect
56+
// that a script is already in the middle of execution and return before
57+
// either the nextTickQueue or MicrotaskQueue are processed.
58+
makeCallback({}, common.mustCall(function() {
59+
results_arr.push(1);
60+
}));
61+
62+
// This should run before either the nextTickQueue or MicrotaskQueue are
63+
// processed. Previously MakeCallback would not detect this circumstance
64+
// and process them immediately.
65+
results_arr.push(2);
66+
67+
setImmediate(common.mustCall(function() {
68+
for (var i = 0; i < results_arr.length; i++) {
69+
assert.equal(results_arr[i],
70+
i,
71+
`verifyExecutionOrder(${arg}) results: ${results_arr}`);
72+
}
73+
if (arg === 1) {
74+
// The tests are first run on bootstrap during LoadEnvironment() in
75+
// src/node.cc. Now run the tests through node::MakeCallback().
76+
setImmediate(function() {
77+
makeCallback({}, common.mustCall(function() {
78+
verifyExecutionOrder(2);
79+
}));
80+
});
81+
} else if (arg === 2) {
82+
// setTimeout runs via the TimerWrap, which runs through
83+
// AsyncWrap::MakeCallback(). Make sure there are no conflicts using
84+
// node::MakeCallback() within it.
85+
setTimeout(common.mustCall(function() {
86+
verifyExecutionOrder(3);
87+
}), 10);
88+
} else if (arg === 3) {
89+
mustCallCheckDomains();
90+
} else {
91+
throw new Error('UNREACHABLE');
92+
}
93+
}));
94+
}(1));
95+
96+
97+
function checkDomains() {
98+
// Check that domains are properly entered/exited when called in multiple
99+
// levels from both node::MakeCallback() and AsyncWrap::MakeCallback
100+
setImmediate(common.mustCall(function() {
101+
const d1 = domain.create();
102+
const d2 = domain.create();
103+
const d3 = domain.create();
104+
105+
makeCallback({ domain: d1 }, common.mustCall(function() {
106+
assert.equal(d1, process.domain);
107+
makeCallback({ domain: d2 }, common.mustCall(function() {
108+
assert.equal(d2, process.domain);
109+
makeCallback({ domain: d3 }, common.mustCall(function() {
110+
assert.equal(d3, process.domain);
111+
}));
112+
assert.equal(d2, process.domain);
113+
}));
114+
assert.equal(d1, process.domain);
115+
}));
116+
}));
117+
118+
setTimeout(common.mustCall(function() {
119+
const d1 = domain.create();
120+
const d2 = domain.create();
121+
const d3 = domain.create();
122+
123+
makeCallback({ domain: d1 }, common.mustCall(function() {
124+
assert.equal(d1, process.domain);
125+
makeCallback({ domain: d2 }, common.mustCall(function() {
126+
assert.equal(d2, process.domain);
127+
makeCallback({ domain: d3 }, common.mustCall(function() {
128+
assert.equal(d3, process.domain);
129+
}));
130+
assert.equal(d2, process.domain);
131+
}));
132+
assert.equal(d1, process.domain);
133+
}));
134+
}), 1);
135+
136+
// Make sure nextTick, setImmediate and setTimeout can all recover properly
137+
// after a thrown makeCallback call.
138+
process.nextTick(common.mustCall(function() {
139+
const d = domain.create();
140+
d.on('error', common.mustCall(function(e) {
141+
assert.equal(e.message, 'throw from domain 3');
142+
}));
143+
makeCallback({ domain: d }, function() {
144+
throw new Error('throw from domain 3');
145+
});
146+
throw new Error('UNREACHABLE');
147+
}));
148+
149+
setImmediate(common.mustCall(function() {
150+
const d = domain.create();
151+
d.on('error', common.mustCall(function(e) {
152+
assert.equal(e.message, 'throw from domain 2');
153+
}));
154+
makeCallback({ domain: d }, function() {
155+
throw new Error('throw from domain 2');
156+
});
157+
throw new Error('UNREACHABLE');
158+
}));
159+
160+
setTimeout(common.mustCall(function() {
161+
const d = domain.create();
162+
d.on('error', common.mustCall(function(e) {
163+
assert.equal(e.message, 'throw from domain 1');
164+
}));
165+
makeCallback({ domain: d }, function() {
166+
throw new Error('throw from domain 1');
167+
});
168+
throw new Error('UNREACHABLE');
169+
}));
170+
}

0 commit comments

Comments
 (0)