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
|
||||
<!-- 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
|
||||
|
||||
@ -444,6 +451,49 @@ d.run(() => {
|
||||
In this example, the `d.on('error')` handler will be triggered, rather
|
||||
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.bind(callback)`]: #domain_domain_bind_callback
|
||||
[`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::ObjectTemplate;
|
||||
using v8::Promise;
|
||||
using v8::PromiseHookType;
|
||||
using v8::PromiseRejectMessage;
|
||||
using v8::PropertyCallbackInfo;
|
||||
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) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
|
||||
@ -1153,9 +1206,12 @@ void SetupDomainUse(const FunctionCallbackInfo<Value>& args) {
|
||||
Local<ArrayBuffer> array_buffer =
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
void RunMicrotasks(const FunctionCallbackInfo<Value>& args) {
|
||||
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