Browse Source

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>
v6
Anna Henningsen 8 years ago
parent
commit
84dabe8373
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  1. 50
      doc/api/domain.md
  2. 56
      src/node.cc
  3. 128
      test/parallel/test-domain-promise.js

50
doc/api/domain.md

@ -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

@ -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

@ -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…
Cancel
Save