domain: support promises
Fixes: https://github.com/nodejs/node/issues/10724 PR-URL: https://github.com/nodejs/node/pull/12489 Reviewed-By: Matthew Loring <mattloring@google.com> Reviewed-By: Julien Gilli <jgilli@nodejs.org> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
This commit is contained in:
parent
e5a25cbc85
commit
84dabe8373
@ -1,4 +1,11 @@
|
|||||||
# Domain
|
# Domain
|
||||||
|
<!-- YAML
|
||||||
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/12489
|
||||||
|
description: Handlers for `Promise`s are now invoked in the domain in which
|
||||||
|
the first promise of a chain was created.
|
||||||
|
-->
|
||||||
|
|
||||||
> Stability: 0 - Deprecated
|
> Stability: 0 - Deprecated
|
||||||
|
|
||||||
@ -444,6 +451,49 @@ d.run(() => {
|
|||||||
In this example, the `d.on('error')` handler will be triggered, rather
|
In this example, the `d.on('error')` handler will be triggered, rather
|
||||||
than crashing the program.
|
than crashing the program.
|
||||||
|
|
||||||
|
## Domains and Promises
|
||||||
|
|
||||||
|
As of Node REPLACEME, the handlers of Promises are run inside the domain in
|
||||||
|
which the call to `.then` or `.catch` itself was made:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const d1 = domain.create();
|
||||||
|
const d2 = domain.create();
|
||||||
|
|
||||||
|
let p;
|
||||||
|
d1.run(() => {
|
||||||
|
p = Promise.resolve(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
d2.run(() => {
|
||||||
|
p.then((v) => {
|
||||||
|
// running in d2
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
A callback may be bound to a specific domain using [`domain.bind(callback)`][]:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const d1 = domain.create();
|
||||||
|
const d2 = domain.create();
|
||||||
|
|
||||||
|
let p;
|
||||||
|
d1.run(() => {
|
||||||
|
p = Promise.resolve(42);
|
||||||
|
});
|
||||||
|
|
||||||
|
d2.run(() => {
|
||||||
|
p.then(p.domain.bind((v) => {
|
||||||
|
// running in d1
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that domains will not interfere with the error handling mechanisms for
|
||||||
|
Promises, i.e. no `error` event will be emitted for unhandled Promise
|
||||||
|
rejections.
|
||||||
|
|
||||||
[`domain.add(emitter)`]: #domain_domain_add_emitter
|
[`domain.add(emitter)`]: #domain_domain_add_emitter
|
||||||
[`domain.bind(callback)`]: #domain_domain_bind_callback
|
[`domain.bind(callback)`]: #domain_domain_bind_callback
|
||||||
[`domain.dispose()`]: #domain_domain_dispose
|
[`domain.dispose()`]: #domain_domain_dispose
|
||||||
|
56
src/node.cc
56
src/node.cc
@ -143,6 +143,7 @@ using v8::Number;
|
|||||||
using v8::Object;
|
using v8::Object;
|
||||||
using v8::ObjectTemplate;
|
using v8::ObjectTemplate;
|
||||||
using v8::Promise;
|
using v8::Promise;
|
||||||
|
using v8::PromiseHookType;
|
||||||
using v8::PromiseRejectMessage;
|
using v8::PromiseRejectMessage;
|
||||||
using v8::PropertyCallbackInfo;
|
using v8::PropertyCallbackInfo;
|
||||||
using v8::ScriptOrigin;
|
using v8::ScriptOrigin;
|
||||||
@ -1114,6 +1115,58 @@ bool ShouldAbortOnUncaughtException(Isolate* isolate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DomainPromiseHook(PromiseHookType type,
|
||||||
|
Local<Promise> promise,
|
||||||
|
Local<Value> parent,
|
||||||
|
void* arg) {
|
||||||
|
Environment* env = static_cast<Environment*>(arg);
|
||||||
|
Local<Context> context = env->context();
|
||||||
|
|
||||||
|
if (type == PromiseHookType::kResolve) return;
|
||||||
|
if (type == PromiseHookType::kInit && env->in_domain()) {
|
||||||
|
promise->Set(context,
|
||||||
|
env->domain_string(),
|
||||||
|
env->domain_array()->Get(context,
|
||||||
|
0).ToLocalChecked()).FromJust();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loosely based on node::MakeCallback().
|
||||||
|
Local<Value> domain_v =
|
||||||
|
promise->Get(context, env->domain_string()).ToLocalChecked();
|
||||||
|
if (!domain_v->IsObject())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Local<Object> domain = domain_v.As<Object>();
|
||||||
|
if (domain->Get(context, env->disposed_string())
|
||||||
|
.ToLocalChecked()->IsTrue()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == PromiseHookType::kBefore) {
|
||||||
|
Local<Value> enter_v =
|
||||||
|
domain->Get(context, env->enter_string()).ToLocalChecked();
|
||||||
|
if (enter_v->IsFunction()) {
|
||||||
|
if (enter_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
|
||||||
|
FatalError("node::PromiseHook",
|
||||||
|
"domain enter callback threw, please report this "
|
||||||
|
"as a bug in Node.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Local<Value> exit_v =
|
||||||
|
domain->Get(context, env->exit_string()).ToLocalChecked();
|
||||||
|
if (exit_v->IsFunction()) {
|
||||||
|
if (exit_v.As<Function>()->Call(context, domain, 0, nullptr).IsEmpty()) {
|
||||||
|
FatalError("node::MakeCallback",
|
||||||
|
"domain exit callback threw, please report this "
|
||||||
|
"as a bug in Node.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
|
void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment* env = Environment::GetCurrent(args);
|
Environment* env = Environment::GetCurrent(args);
|
||||||
|
|
||||||
@ -1153,9 +1206,12 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
|
|||||||
Local<ArrayBuffer> array_buffer =
|
Local<ArrayBuffer> array_buffer =
|
||||||
ArrayBuffer::New(env->isolate(), fields, sizeof(*fields) * fields_count);
|
ArrayBuffer::New(env->isolate(), fields, sizeof(*fields) * fields_count);
|
||||||
|
|
||||||
|
env->AddPromiseHook(DomainPromiseHook, static_cast<void*>(env));
|
||||||
|
|
||||||
args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, fields_count));
|
args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, fields_count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
|
void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
|
||||||
args.GetIsolate()->RunMicrotasks();
|
args.GetIsolate()->RunMicrotasks();
|
||||||
}
|
}
|
||||||
|
128
test/parallel/test-domain-promise.js
Normal file
128
test/parallel/test-domain-promise.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const domain = require('domain');
|
||||||
|
const fs = require('fs');
|
||||||
|
const vm = require('vm');
|
||||||
|
|
||||||
|
common.crashOnUnhandledRejection();
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = domain.create();
|
||||||
|
|
||||||
|
d.run(common.mustCall(() => {
|
||||||
|
Promise.resolve().then(common.mustCall(() => {
|
||||||
|
assert.strictEqual(process.domain, d);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = domain.create();
|
||||||
|
|
||||||
|
d.run(common.mustCall(() => {
|
||||||
|
Promise.resolve().then(() => {}).then(() => {}).then(common.mustCall(() => {
|
||||||
|
assert.strictEqual(process.domain, d);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = domain.create();
|
||||||
|
|
||||||
|
d.run(common.mustCall(() => {
|
||||||
|
vm.runInNewContext(`Promise.resolve().then(common.mustCall(() => {
|
||||||
|
assert.strictEqual(process.domain, d);
|
||||||
|
}));`, { common, assert, process, d });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d1 = domain.create();
|
||||||
|
const d2 = domain.create();
|
||||||
|
let p;
|
||||||
|
d1.run(common.mustCall(() => {
|
||||||
|
p = Promise.resolve(42);
|
||||||
|
}));
|
||||||
|
|
||||||
|
d2.run(common.mustCall(() => {
|
||||||
|
p.then(common.mustCall((v) => {
|
||||||
|
assert.strictEqual(process.domain, d2);
|
||||||
|
assert.strictEqual(p.domain, d1);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d1 = domain.create();
|
||||||
|
const d2 = domain.create();
|
||||||
|
let p;
|
||||||
|
d1.run(common.mustCall(() => {
|
||||||
|
p = Promise.resolve(42);
|
||||||
|
}));
|
||||||
|
|
||||||
|
d2.run(common.mustCall(() => {
|
||||||
|
p.then(p.domain.bind(common.mustCall((v) => {
|
||||||
|
assert.strictEqual(process.domain, d1);
|
||||||
|
assert.strictEqual(p.domain, d1);
|
||||||
|
})));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d1 = domain.create();
|
||||||
|
const d2 = domain.create();
|
||||||
|
let p;
|
||||||
|
d1.run(common.mustCall(() => {
|
||||||
|
p = Promise.resolve(42);
|
||||||
|
}));
|
||||||
|
|
||||||
|
d1.run(common.mustCall(() => {
|
||||||
|
d2.run(common.mustCall(() => {
|
||||||
|
p.then(common.mustCall((v) => {
|
||||||
|
assert.strictEqual(process.domain, d2);
|
||||||
|
assert.strictEqual(p.domain, d1);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d1 = domain.create();
|
||||||
|
const d2 = domain.create();
|
||||||
|
let p;
|
||||||
|
d1.run(common.mustCall(() => {
|
||||||
|
p = Promise.reject(new Error('foobar'));
|
||||||
|
}));
|
||||||
|
|
||||||
|
d2.run(common.mustCall(() => {
|
||||||
|
p.catch(common.mustCall((v) => {
|
||||||
|
assert.strictEqual(process.domain, d2);
|
||||||
|
assert.strictEqual(p.domain, d1);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = domain.create();
|
||||||
|
|
||||||
|
d.run(common.mustCall(() => {
|
||||||
|
Promise.resolve().then(common.mustCall(() => {
|
||||||
|
setTimeout(common.mustCall(() => {
|
||||||
|
assert.strictEqual(process.domain, d);
|
||||||
|
}), 0);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const d = domain.create();
|
||||||
|
|
||||||
|
d.run(common.mustCall(() => {
|
||||||
|
Promise.resolve().then(common.mustCall(() => {
|
||||||
|
fs.readFile(__filename, common.mustCall(() => {
|
||||||
|
assert.strictEqual(process.domain, d);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user