New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

domain: support promises #12489

Closed
wants to merge 10 commits into
from
View
@@ -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
View
@@ -188,4 +188,20 @@ void Environment::AtExit(void (*cb)(void* arg), void* arg) {
at_exit_functions_.push_back(AtExitCallback{cb, arg});
}
void Environment::AddPromiseHook(promise_hook_func fn, void* arg) {
promise_hooks_.push_back(PromiseHookCallback{fn, arg});
if (promise_hooks_.size() == 1) {
isolate_->SetPromiseHook(EnvPromiseHook);
}
}
void Environment::EnvPromiseHook(v8::PromiseHookType type,
v8::Local<v8::Promise> promise,
v8::Local<v8::Value> parent) {
Environment* env = Environment::GetCurrent(promise->CreationContext());
for (const PromiseHookCallback& hook : env->promise_hooks_) {
hook.cb_(type, promise, parent, hook.arg_);
}
}
} // namespace node
View
@@ -35,6 +35,7 @@
#include "util.h"
#include "uv.h"
#include "v8.h"
#include "node.h"
#include <list>
#include <stdint.h>
@@ -572,6 +573,8 @@ class Environment {
static const int kContextEmbedderDataIndex = NODE_CONTEXT_EMBEDDER_DATA_INDEX;
void AddPromiseHook(promise_hook_func fn, void* arg);
private:
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
const char* errmsg);
@@ -620,6 +623,16 @@ class Environment {
};
std::list<AtExitCallback> at_exit_functions_;
struct PromiseHookCallback {
promise_hook_func cb_;
void* arg_;
};
std::vector<PromiseHookCallback> promise_hooks_;
static void EnvPromiseHook(v8::PromiseHookType type,
v8::Local<v8::Promise> promise,
v8::Local<v8::Value> parent);
#define V(PropertyName, TypeName) \
v8::Persistent<TypeName> PropertyName ## _;
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
View
@@ -142,6 +142,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;
@@ -1113,6 +1114,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);
@@ -1152,9 +1205,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();
}
@@ -1232,6 +1288,12 @@ void SetupPromises(const FunctionCallbackInfo<Value>& args) {
} // anonymous namespace
void AddPromiseHook(v8::Isolate* isolate, promise_hook_func fn, void* arg) {
Environment* env = Environment::GetCurrent(isolate);
env->AddPromiseHook(fn, arg);
}
Local<Value> MakeCallback(Environment* env,
Local<Value> recv,
const Local<Function> callback,
View
@@ -517,6 +517,17 @@ NODE_EXTERN void AtExit(void (*cb)(void* arg), void* arg = 0);
*/
NODE_EXTERN void AtExit(Environment* env, void (*cb)(void* arg), void* arg = 0);
typedef void (*promise_hook_func) (v8::PromiseHookType type,
v8::Local<v8::Promise> promise,
v8::Local<v8::Value> parent,
void* arg);
/* Registers an additional v8::PromiseHook wrapper. This API exists because V8
* itself supports only a single PromiseHook. */
NODE_EXTERN void AddPromiseHook(v8::Isolate* isolate,
promise_hook_func fn,
void* arg);
} // namespace node
#endif // SRC_NODE_H_
View
@@ -675,3 +675,9 @@ exports.getArrayBufferViews = function getArrayBufferViews(buf) {
}
return out;
};
// Crash the process on unhandled rejections.
exports.crashOnUnhandledRejection = function() {

This comment has been minimized.

@Trott

Trott Apr 27, 2017

Member

Documentation for this should be added to test/README.md.

@Trott

Trott Apr 27, 2017

Member

Documentation for this should be added to test/README.md.

process.on('unhandledRejection',
(err) => process.nextTick(() => { throw err; }));
};
@@ -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);
}));
}));
}));
}