Skip to content

Commit 84dabe8

Browse files
committed
domain: support promises
Fixes: #10724 PR-URL: #12489 Reviewed-By: Matthew Loring <[email protected]> Reviewed-By: Julien Gilli <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent e5a25cb commit 84dabe8

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

doc/api/domain.md

+50
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
# Domain
2+
<!-- YAML
3+
changes:
4+
- version: REPLACEME
5+
pr-url: https://github.com/nodejs/node/pull/12489
6+
description: Handlers for `Promise`s are now invoked in the domain in which
7+
the first promise of a chain was created.
8+
-->
29

310
> Stability: 0 - Deprecated
411
@@ -444,6 +451,49 @@ d.run(() => {
444451
In this example, the `d.on('error')` handler will be triggered, rather
445452
than crashing the program.
446453

454+
## Domains and Promises
455+
456+
As of Node REPLACEME, the handlers of Promises are run inside the domain in
457+
which the call to `.then` or `.catch` itself was made:
458+
459+
```js
460+
const d1 = domain.create();
461+
const d2 = domain.create();
462+
463+
let p;
464+
d1.run(() => {
465+
p = Promise.resolve(42);
466+
});
467+
468+
d2.run(() => {
469+
p.then((v) => {
470+
// running in d2
471+
});
472+
});
473+
```
474+
475+
A callback may be bound to a specific domain using [`domain.bind(callback)`][]:
476+
477+
```js
478+
const d1 = domain.create();
479+
const d2 = domain.create();
480+
481+
let p;
482+
d1.run(() => {
483+
p = Promise.resolve(42);
484+
});
485+
486+
d2.run(() => {
487+
p.then(p.domain.bind((v) => {
488+
// running in d1
489+
}));
490+
});
491+
```
492+
493+
Note that domains will not interfere with the error handling mechanisms for
494+
Promises, i.e. no `error` event will be emitted for unhandled Promise
495+
rejections.
496+
447497
[`domain.add(emitter)`]: #domain_domain_add_emitter
448498
[`domain.bind(callback)`]: #domain_domain_bind_callback
449499
[`domain.dispose()`]: #domain_domain_dispose

src/node.cc

+56
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ using v8::Number;
143143
using v8::Object;
144144
using v8::ObjectTemplate;
145145
using v8::Promise;
146+
using v8::PromiseHookType;
146147
using v8::PromiseRejectMessage;
147148
using v8::PropertyCallbackInfo;
148149
using v8::ScriptOrigin;
@@ -1114,6 +1115,58 @@ bool ShouldAbortOnUncaughtException(Isolate* isolate) {
11141115
}
11151116

11161117

1118+
void DomainPromiseHook(PromiseHookType type,
1119+
Local<Promise> promise,
1120+
Local<Value> parent,
1121+
void* arg) {
1122+
Environment* env = static_cast<Environment*>(arg);
1123+
Local<Context> context = env->context();
1124+
1125+
if (type == PromiseHookType::kResolve) return;
1126+
if (type == PromiseHookType::kInit && env->in_domain()) {
1127+
promise->Set(context,
1128+
env->domain_string(),
1129+
env->domain_array()->Get(context,
1130+
0).ToLocalChecked()).FromJust();
1131+
return;
1132+
}
1133+
1134+
// Loosely based on node::MakeCallback().
1135+
Local<Value> domain_v =
1136+
promise->Get(context, env->domain_string()).ToLocalChecked();
1137+
if (!domain_v->IsObject())
1138+
return;
1139+
1140+
Local<Object> domain = domain_v.As<Object>();
1141+
if (domain->Get(context, env->disposed_string())
1142+
.ToLocalChecked()->IsTrue()) {
1143+
return;
1144+
}
1145+
1146+
if (type == PromiseHookType::kBefore) {
1147+
Local<Value> enter_v =
1148+
domain->Get(context, env->enter_string()).ToLocalChecked();
1149+
if (enter_v->IsFunction()) {
1150+
if (enter_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
1151+
FatalError("node::PromiseHook",
1152+
"domain enter callback threw, please report this "
1153+
"as a bug in Node.js");
1154+
}
1155+
}
1156+
} else {
1157+
Local<Value> exit_v =
1158+
domain->Get(context, env->exit_string()).ToLocalChecked();
1159+
if (exit_v->IsFunction()) {
1160+
if (exit_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
1161+
FatalError("node::MakeCallback",
1162+
"domain exit callback threw, please report this "
1163+
"as a bug in Node.js");
1164+
}
1165+
}
1166+
}
1167+
}
1168+
1169+
11171170
void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
11181171
Environment* env = Environment::GetCurrent(args);
11191172

@@ -1153,9 +1206,12 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
11531206
Local<ArrayBuffer> array_buffer =
11541207
ArrayBuffer::New(env->isolate(), fields, sizeof(*fields) * fields_count);
11551208

1209+
env->AddPromiseHook(DomainPromiseHook, static_cast<void*>(env));
1210+
11561211
args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, fields_count));
11571212
}
11581213

1214+
11591215
void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
11601216
args.GetIsolate()->RunMicrotasks();
11611217
}

test/parallel/test-domain-promise.js

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const domain = require('domain');
5+
const fs = require('fs');
6+
const vm = require('vm');
7+
8+
common.crashOnUnhandledRejection();
9+
10+
{
11+
const d = domain.create();
12+
13+
d.run(common.mustCall(() => {
14+
Promise.resolve().then(common.mustCall(() => {
15+
assert.strictEqual(process.domain, d);
16+
}));
17+
}));
18+
}
19+
20+
{
21+
const d = domain.create();
22+
23+
d.run(common.mustCall(() => {
24+
Promise.resolve().then(() => {}).then(() => {}).then(common.mustCall(() => {
25+
assert.strictEqual(process.domain, d);
26+
}));
27+
}));
28+
}
29+
30+
{
31+
const d = domain.create();
32+
33+
d.run(common.mustCall(() => {
34+
vm.runInNewContext(`Promise.resolve().then(common.mustCall(() => {
35+
assert.strictEqual(process.domain, d);
36+
}));`, { common, assert, process, d });
37+
}));
38+
}
39+
40+
{
41+
const d1 = domain.create();
42+
const d2 = domain.create();
43+
let p;
44+
d1.run(common.mustCall(() => {
45+
p = Promise.resolve(42);
46+
}));
47+
48+
d2.run(common.mustCall(() => {
49+
p.then(common.mustCall((v) => {
50+
assert.strictEqual(process.domain, d2);
51+
assert.strictEqual(p.domain, d1);
52+
}));
53+
}));
54+
}
55+
56+
{
57+
const d1 = domain.create();
58+
const d2 = domain.create();
59+
let p;
60+
d1.run(common.mustCall(() => {
61+
p = Promise.resolve(42);
62+
}));
63+
64+
d2.run(common.mustCall(() => {
65+
p.then(p.domain.bind(common.mustCall((v) => {
66+
assert.strictEqual(process.domain, d1);
67+
assert.strictEqual(p.domain, d1);
68+
})));
69+
}));
70+
}
71+
72+
{
73+
const d1 = domain.create();
74+
const d2 = domain.create();
75+
let p;
76+
d1.run(common.mustCall(() => {
77+
p = Promise.resolve(42);
78+
}));
79+
80+
d1.run(common.mustCall(() => {
81+
d2.run(common.mustCall(() => {
82+
p.then(common.mustCall((v) => {
83+
assert.strictEqual(process.domain, d2);
84+
assert.strictEqual(p.domain, d1);
85+
}));
86+
}));
87+
}));
88+
}
89+
90+
{
91+
const d1 = domain.create();
92+
const d2 = domain.create();
93+
let p;
94+
d1.run(common.mustCall(() => {
95+
p = Promise.reject(new Error('foobar'));
96+
}));
97+
98+
d2.run(common.mustCall(() => {
99+
p.catch(common.mustCall((v) => {
100+
assert.strictEqual(process.domain, d2);
101+
assert.strictEqual(p.domain, d1);
102+
}));
103+
}));
104+
}
105+
106+
{
107+
const d = domain.create();
108+
109+
d.run(common.mustCall(() => {
110+
Promise.resolve().then(common.mustCall(() => {
111+
setTimeout(common.mustCall(() => {
112+
assert.strictEqual(process.domain, d);
113+
}), 0);
114+
}));
115+
}));
116+
}
117+
118+
{
119+
const d = domain.create();
120+
121+
d.run(common.mustCall(() => {
122+
Promise.resolve().then(common.mustCall(() => {
123+
fs.readFile(__filename, common.mustCall(() => {
124+
assert.strictEqual(process.domain, d);
125+
}));
126+
}));
127+
}));
128+
}

0 commit comments

Comments
 (0)