Skip to content
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

src,async_hooks,n-api: refactor async callback handling #14697

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3256,17 +3256,27 @@ callback invocation, even when it was cancelled.
### napi_create_async_work
<!-- YAML
added: v8.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/14697
description: Added `async_resource` and `async_resource_name` parameters.
-->
```C
NAPI_EXTERN
napi_status napi_create_async_work(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_execute_callback execute,
napi_async_complete_callback complete,
void* data,
napi_async_work* result);
```

- `[in] env`: The environment that the API is invoked under.
- `[in] async_resource`: An optional object associated with the async work
that will be passed to possible async_hooks [`init` hooks][].
- `[in] async_resource_name`: An identifier for the kind of resource that is
being provided for diagnostic information exposed by the `async_hooks` API.
- `[in] execute`: The native function which should be called to excute
the logic asynchronously.
- `[in] complete`: The native function which will be called when the
Expand All @@ -3282,6 +3292,14 @@ This API allocates a work object that is used to execute logic asynchronously.
It should be freed using [`napi_delete_async_work`][] once the work is no longer
required.

`async_resource_name` should be a null-terminated, UTF-8-encoded string.

*Note*: The `async_resource_name` identifier is provided by the user and should
be representative of the type of async work being performed. It is also
recommended to apply namespacing to the identifier, e.g. by including the
module name. See the [`async_hooks` documentation][async_hooks `type`]
for more information.

### napi_delete_async_work
<!-- YAML
added: v8.0.0
Expand Down Expand Up @@ -3636,3 +3654,5 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env,
[`napi_wrap`]: #n_api_napi_wrap

[`process.release`]: process.html#process_process_release
[`init` hooks]: async_hooks.html#async_hooks_init_asyncid_type_triggerasyncid_resource
[async_hooks `type`]: async_hooks.html#async_hooks_type
5 changes: 0 additions & 5 deletions src/async-wrap-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,7 @@
#include "async-wrap.h"
#include "base-object.h"
#include "base-object-inl.h"
#include "env.h"
#include "env-inl.h"
#include "node_internals.h"
#include "util.h"
#include "util-inl.h"
#include "v8.h"

namespace node {

Expand Down
120 changes: 13 additions & 107 deletions src/async-wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,42 +181,6 @@ static void PushBackDestroyId(Environment* env, double id) {
}


bool DomainEnter(Environment* env, Local<Object> object) {
Local<Value> domain_v = object->Get(env->domain_string());
if (domain_v->IsObject()) {
Local<Object> domain = domain_v.As<Object>();
if (domain->Get(env->disposed_string())->IsTrue())
return true;
Local<Value> enter_v = domain->Get(env->enter_string());
if (enter_v->IsFunction()) {
if (enter_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
FatalError("node::AsyncWrap::MakeCallback",
"domain enter callback threw, please report this");
}
}
}
return false;
}


bool DomainExit(Environment* env, v8::Local<v8::Object> object) {
Local<Value> domain_v = object->Get(env->domain_string());
if (domain_v->IsObject()) {
Local<Object> domain = domain_v.As<Object>();
if (domain->Get(env->disposed_string())->IsTrue())
return true;
Local<Value> exit_v = domain->Get(env->exit_string());
if (exit_v->IsFunction()) {
if (exit_v.As<Function>()->Call(domain, 0, nullptr).IsEmpty()) {
FatalError("node::AsyncWrap::MakeCallback",
"domain exit callback threw, please report this");
}
}
}
return false;
}


void AsyncWrap::EmitBefore(Environment* env, double async_id) {
AsyncHooks* async_hooks = env->async_hooks();

Expand Down Expand Up @@ -657,73 +621,8 @@ void AsyncWrap::EmitAsyncInit(Environment* env,
MaybeLocal<Value> AsyncWrap::MakeCallback(const Local<Function> cb,
int argc,
Local<Value>* argv) {
CHECK(env()->context() == env()->isolate()->GetCurrentContext());

Environment::AsyncCallbackScope callback_scope(env());

Environment::AsyncHooks::ExecScope exec_scope(env(),
get_id(),
get_trigger_id());

// Return v8::Undefined() because returning an empty handle will cause
// ToLocalChecked() to abort.
if (env()->using_domains() && DomainEnter(env(), object())) {
return Undefined(env()->isolate());
}

// No need to check a return value because the application will exit if an
// exception occurs.
AsyncWrap::EmitBefore(env(), get_id());

MaybeLocal<Value> ret = cb->Call(env()->context(), object(), argc, argv);

if (ret.IsEmpty()) {
return ret;
}

AsyncWrap::EmitAfter(env(), get_id());

// Return v8::Undefined() because returning an empty handle will cause
// ToLocalChecked() to abort.
if (env()->using_domains() && DomainExit(env(), object())) {
return Undefined(env()->isolate());
}

exec_scope.Dispose();

if (callback_scope.in_makecallback()) {
return ret;
}

Environment::TickInfo* tick_info = env()->tick_info();

if (tick_info->length() == 0) {
env()->isolate()->RunMicrotasks();
}

// Make sure the stack unwound properly. If there are nested MakeCallback's
// then it should return early and not reach this code.
CHECK_EQ(env()->current_async_id(), 0);
CHECK_EQ(env()->trigger_id(), 0);

Local<Object> process = env()->process_object();

if (tick_info->length() == 0) {
tick_info->set_index(0);
return ret;
}

MaybeLocal<Value> rcheck =
env()->tick_callback_function()->Call(env()->context(),
process,
0,
nullptr);

// Make sure the stack unwound properly.
CHECK_EQ(env()->current_async_id(), 0);
CHECK_EQ(env()->trigger_id(), 0);

return rcheck.IsEmpty() ? MaybeLocal<Value>() : ret;
async_context context { get_id(), get_trigger_id() };
return InternalMakeCallback(env(), object(), cb, argc, argv, context);
}


Expand All @@ -744,6 +643,16 @@ async_context EmitAsyncInit(Isolate* isolate,
Local<Object> resource,
const char* name,
async_id trigger_async_id) {
Local<String> type =
String::NewFromUtf8(isolate, name, v8::NewStringType::kInternalized)
.ToLocalChecked();
return EmitAsyncInit(isolate, resource, type, trigger_async_id);
}

async_context EmitAsyncInit(Isolate* isolate,
Local<Object> resource,
v8::Local<v8::String> name,
async_id trigger_async_id) {
Environment* env = Environment::GetCurrent(isolate);

// Initialize async context struct
Expand All @@ -756,10 +665,7 @@ async_context EmitAsyncInit(Isolate* isolate,
};

// Run init hooks
Local<String> type =
String::NewFromUtf8(isolate, name, v8::NewStringType::kInternalized)
.ToLocalChecked();
AsyncWrap::EmitAsyncInit(env, resource, type, context.async_id,
AsyncWrap::EmitAsyncInit(env, resource, name, context.async_id,
context.trigger_async_id);

return context;
Expand Down
4 changes: 0 additions & 4 deletions src/async-wrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,6 @@ class AsyncWrap : public BaseObject {

void LoadAsyncWrapperInfo(Environment* env);

// Return value is an indicator whether the domain was disposed.
bool DomainEnter(Environment* env, v8::Local<v8::Object> object);
bool DomainExit(Environment* env, v8::Local<v8::Object> object);

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Expand Down
2 changes: 1 addition & 1 deletion src/backtrace_posix.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "node.h"
#include "node_internals.h"

#if defined(__linux__)
#include <features.h>
Expand Down
23 changes: 2 additions & 21 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "util-inl.h"
#include "uv.h"
#include "v8.h"
#include "node_perf_common.h"

#include <stddef.h>
#include <stdint.h>
Expand Down Expand Up @@ -193,26 +194,6 @@ inline Environment::AsyncHooks::InitScope::~InitScope() {
env_->async_hooks()->pop_ids(uid_fields_ref_[AsyncHooks::kCurrentAsyncId]);
}

inline Environment::AsyncHooks::ExecScope::ExecScope(
Environment* env, double async_id, double trigger_id)
: env_(env),
async_id_(async_id),
disposed_(false) {
CHECK_GE(async_id, -1);
CHECK_GE(trigger_id, -1);
env->async_hooks()->push_ids(async_id, trigger_id);
}

inline Environment::AsyncHooks::ExecScope::~ExecScope() {
if (disposed_) return;
Dispose();
}

inline void Environment::AsyncHooks::ExecScope::Dispose() {
disposed_ = true;
env_->async_hooks()->pop_ids(async_id_);
}

inline Environment::AsyncCallbackScope::AsyncCallbackScope(Environment* env)
: env_(env) {
env_->makecallback_cntr_++;
Expand All @@ -222,7 +203,7 @@ inline Environment::AsyncCallbackScope::~AsyncCallbackScope() {
env_->makecallback_cntr_--;
}

inline bool Environment::AsyncCallbackScope::in_makecallback() {
inline bool Environment::AsyncCallbackScope::in_makecallback() const {
return env_->makecallback_cntr_ > 1;
}

Expand Down
4 changes: 1 addition & 3 deletions src/env.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "env.h"
#include "env-inl.h"
#include "node_internals.h"
#include "async-wrap.h"
#include "v8.h"
#include "v8-profiler.h"

#if defined(_MSC_VER)
Expand Down
25 changes: 5 additions & 20 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ struct nghttp2_rcbuf;

namespace node {

namespace performance {
struct performance_state;
}

// Pick an index that's hopefully out of the way when we're embedded inside
// another application. Performance-wise or memory-wise it doesn't matter:
// Context::SetAlignedPointerInEmbedderData() is backed by a FixedArray,
Expand Down Expand Up @@ -417,25 +421,6 @@ class Environment {
DISALLOW_COPY_AND_ASSIGN(InitScope);
};

// Used to manage the stack of async and trigger ids as calls are made into
// JS. Mainly used in MakeCallback().
class ExecScope {
public:
ExecScope() = delete;
explicit ExecScope(Environment* env, double async_id, double trigger_id);
~ExecScope();
void Dispose();

private:
Environment* env_;
double async_id_;
// Manually track if the destructor has run so it isn't accidentally run
// twice on RAII cleanup.
bool disposed_;

DISALLOW_COPY_AND_ASSIGN(ExecScope);
};

private:
friend class Environment; // So we can call the constructor.
inline explicit AsyncHooks(v8::Isolate* isolate);
Expand All @@ -459,7 +444,7 @@ class Environment {
AsyncCallbackScope() = delete;
explicit AsyncCallbackScope(Environment* env);
~AsyncCallbackScope();
inline bool in_makecallback();
inline bool in_makecallback() const;

private:
Environment* env_;
Expand Down
5 changes: 1 addition & 4 deletions src/inspector_agent.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#include "inspector_agent.h"

#include "inspector_io.h"
#include "env.h"
#include "env-inl.h"
#include "node.h"
#include "node_internals.h"
#include "v8-inspector.h"
#include "v8-platform.h"
#include "util.h"
#include "zlib.h"

#include "libplatform/libplatform.h"
Expand Down