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

[v8.x] backport timers perf improvement & C++ SetImmediate() #18086

Closed
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions lib/timers.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ const { kInit, kDestroy, kAsyncIdCounter } = async_wrap.constants;
const async_id_symbol = Symbol('asyncId');
const trigger_async_id_symbol = Symbol('triggerAsyncId');

/* This is an Uint32Array for easier sharing with C++ land. */
const scheduledImmediateCount = process._scheduledImmediateCount;
delete process._scheduledImmediateCount;
/* Kick off setImmediate processing */
const activateImmediateCheck = process._activateImmediateCheck;
delete process._activateImmediateCheck;

// Timeout values > TIMEOUT_MAX are set to 1.
const TIMEOUT_MAX = 2147483647; // 2^31-1

Expand Down Expand Up @@ -731,15 +738,9 @@ function processImmediate() {
else
immediate = next;
}

// Only round-trip to C++ land if we have to. Calling clearImmediate() on an
// immediate that's in |queue| is okay. Worst case is we make a superfluous
// call to NeedImmediateCallbackSetter().
if (!immediateQueue.head) {
process._needImmediateCallback = false;
}
}

process._immediateCallback = processImmediate;

// An optimization so that the try/finally only de-optimizes (since at least v8
// 4.7) what is in this smaller function.
Expand All @@ -751,13 +752,17 @@ function tryOnImmediate(immediate, oldTail) {
runCallback(immediate);
threw = false;
} finally {
// clearImmediate checks _callback === null for kDestroy hooks.
immediate._callback = null;
if (!threw)
emitAfter(immediate[async_id_symbol]);
if (async_hook_fields[kDestroy] > 0 && !immediate._destroyed) {
emitDestroy(immediate[async_id_symbol]);

if (!immediate._destroyed) {
immediate._destroyed = true;
scheduledImmediateCount[0]--;

if (async_hook_fields[kDestroy] > 0) {
emitDestroy(immediate[async_id_symbol]);
}
}

if (threw && immediate._idleNext) {
Expand Down Expand Up @@ -860,10 +865,9 @@ function createImmediate(args, callback) {
immediate._argv = args;
immediate._onImmediate = callback;

if (!process._needImmediateCallback) {
process._needImmediateCallback = true;
process._immediateCallback = processImmediate;
}
if (scheduledImmediateCount[0] === 0)
activateImmediateCheck();
scheduledImmediateCount[0]++;

immediateQueue.append(immediate);

Expand All @@ -874,18 +878,16 @@ function createImmediate(args, callback) {
exports.clearImmediate = function(immediate) {
if (!immediate) return;

if (async_hook_fields[kDestroy] > 0 &&
immediate._callback !== null &&
!immediate._destroyed) {
emitDestroy(immediate[async_id_symbol]);
if (!immediate._destroyed) {
scheduledImmediateCount[0]--;
immediate._destroyed = true;

if (async_hook_fields[kDestroy] > 0) {
emitDestroy(immediate[async_id_symbol]);
}
}

immediate._onImmediate = null;

immediateQueue.remove(immediate);

if (!immediateQueue.head) {
process._needImmediateCallback = false;
}
};
9 changes: 2 additions & 7 deletions src/async-wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,7 @@ RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local<Value> wrapper) {
// end RetainedAsyncInfo


static void DestroyAsyncIdsCallback(uv_timer_t* handle) {
Environment* env = Environment::from_destroy_async_ids_timer_handle(handle);

HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
static void DestroyAsyncIdsCallback(Environment* env, void* data) {
Local<Function> fn = env->async_hooks_destroy_function();

TryCatch try_catch(env->isolate());
Expand Down Expand Up @@ -173,8 +169,7 @@ static void PushBackDestroyAsyncId(Environment* env, double id) {
return;

if (env->destroy_async_id_list()->empty())
uv_timer_start(env->destroy_async_ids_timer_handle(),
DestroyAsyncIdsCallback, 0, 0);
env->SetImmediate(DestroyAsyncIdsCallback, nullptr);

env->destroy_async_id_list()->push_back(id);
}
Expand Down
22 changes: 13 additions & 9 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ inline Environment::Environment(IsolateData* isolate_data,
abort_on_uncaught_exception_(false),
emit_napi_warning_(true),
makecallback_cntr_(0),
scheduled_immediate_count_(isolate_, 1),
#if HAVE_INSPECTOR
inspector_agent_(new inspector::Agent(this)),
#endif
Expand Down Expand Up @@ -387,15 +388,6 @@ inline uv_idle_t* Environment::immediate_idle_handle() {
return &immediate_idle_handle_;
}

inline Environment* Environment::from_destroy_async_ids_timer_handle(
uv_timer_t* handle) {
return ContainerOf(&Environment::destroy_async_ids_timer_handle_, handle);
}

inline uv_timer_t* Environment::destroy_async_ids_timer_handle() {
return &destroy_async_ids_timer_handle_;
}

inline void Environment::RegisterHandleCleanup(uv_handle_t* handle,
HandleCleanupCb cb,
void *arg) {
Expand Down Expand Up @@ -533,6 +525,18 @@ inline void Environment::set_fs_stats_field_array(double* fields) {
fs_stats_field_array_ = fields;
}

inline AliasedBuffer<uint32_t, v8::Uint32Array>&
Environment::scheduled_immediate_count() {
return scheduled_immediate_count_;
}

void Environment::SetImmediate(native_immediate_callback cb, void* data) {
native_immediate_callbacks_.push_back({ cb, data });
if (scheduled_immediate_count_[0] == 0)
ActivateImmediateCheck();
scheduled_immediate_count_[0] = scheduled_immediate_count_[0] + 1;
}

inline performance::performance_state* Environment::performance_state() {
return performance_state_;
}
Expand Down
58 changes: 52 additions & 6 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ void Environment::Start(int argc,
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));

uv_timer_init(event_loop(), destroy_async_ids_timer_handle());

auto close_and_finish = [](Environment* env, uv_handle_t* handle, void* arg) {
handle->data = env;

Expand All @@ -68,10 +66,6 @@ void Environment::Start(int argc,
reinterpret_cast<uv_handle_t*>(&idle_check_handle_),
close_and_finish,
nullptr);
RegisterHandleCleanup(
reinterpret_cast<uv_handle_t*>(&destroy_async_ids_timer_handle_),
close_and_finish,
nullptr);

if (start_profiler_idle_notifier) {
StartProfilerIdleNotifier();
Expand Down Expand Up @@ -223,4 +217,56 @@ void Environment::EnvPromiseHook(v8::PromiseHookType type,
}
}

void Environment::RunAndClearNativeImmediates() {
size_t count = native_immediate_callbacks_.size();
if (count > 0) {
std::vector<NativeImmediateCallback> list;
native_immediate_callbacks_.swap(list);
for (const auto& cb : list) {
cb.cb_(this, cb.data_);
}

#ifdef DEBUG
CHECK_GE(scheduled_immediate_count_[0], count);
#endif
scheduled_immediate_count_[0] = scheduled_immediate_count_[0] - count;
}
}

static bool MaybeStopImmediate(Environment* env) {
if (env->scheduled_immediate_count()[0] == 0) {
uv_check_stop(env->immediate_check_handle());
uv_idle_stop(env->immediate_idle_handle());
return true;
}
return false;
}


void Environment::CheckImmediate(uv_check_t* handle) {
Environment* env = Environment::from_immediate_check_handle(handle);
HandleScope scope(env->isolate());
Context::Scope context_scope(env->context());

if (MaybeStopImmediate(env))
return;

env->RunAndClearNativeImmediates();

MakeCallback(env->isolate(),
env->process_object(),
env->immediate_callback_string(),
0,
nullptr,
{0, 0}).ToLocalChecked();

MaybeStopImmediate(env);
}

void Environment::ActivateImmediateCheck() {
uv_check_start(&immediate_check_handle_, CheckImmediate);
// Idle handle is needed only to stop the event loop from blocking in poll.
uv_idle_start(&immediate_idle_handle_, [](uv_idle_t*){ });
}

} // namespace node
21 changes: 17 additions & 4 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,11 +560,8 @@ class Environment {
inline uint32_t watched_providers() const;

static inline Environment* from_immediate_check_handle(uv_check_t* handle);
static inline Environment* from_destroy_async_ids_timer_handle(
uv_timer_t* handle);
inline uv_check_t* immediate_check_handle();
inline uv_idle_t* immediate_idle_handle();
inline uv_timer_t* destroy_async_ids_timer_handle();

// Register clean-up cb to be called on environment destruction.
inline void RegisterHandleCleanup(uv_handle_t* handle,
Expand Down Expand Up @@ -618,6 +615,8 @@ class Environment {
inline double* fs_stats_field_array() const;
inline void set_fs_stats_field_array(double* fields);

inline AliasedBuffer<uint32_t, v8::Uint32Array>& scheduled_immediate_count();

inline performance::performance_state* performance_state();
inline std::map<std::string, uint64_t>* performance_marks();

Expand Down Expand Up @@ -690,6 +689,11 @@ class Environment {
bool RemovePromiseHook(promise_hook_func fn, void* arg);
bool EmitNapiWarning();

typedef void (*native_immediate_callback)(Environment* env, void* data);
inline void SetImmediate(native_immediate_callback cb, void* data);
// This needs to be available for the JS-land setImmediate().
void ActivateImmediateCheck();

private:
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
const char* errmsg);
Expand All @@ -698,7 +702,6 @@ class Environment {
IsolateData* const isolate_data_;
uv_check_t immediate_check_handle_;
uv_idle_t immediate_idle_handle_;
uv_timer_t destroy_async_ids_timer_handle_;
uv_prepare_t idle_prepare_handle_;
uv_check_t idle_check_handle_;

Expand All @@ -714,6 +717,8 @@ class Environment {
size_t makecallback_cntr_;
std::vector<double> destroy_async_id_list_;

AliasedBuffer<uint32_t, v8::Uint32Array> scheduled_immediate_count_;

performance::performance_state* performance_state_ = nullptr;
std::map<std::string, uint64_t> performance_marks_;

Expand Down Expand Up @@ -748,6 +753,14 @@ class Environment {
};
std::vector<PromiseHookCallback> promise_hooks_;

struct NativeImmediateCallback {
native_immediate_callback cb_;
void* data_;
};
std::vector<NativeImmediateCallback> native_immediate_callbacks_;
void RunAndClearNativeImmediates();
static void CheckImmediate(uv_check_t* handle);

static void EnvPromiseHook(v8::PromiseHookType type,
v8::Local<v8::Promise> promise,
v8::Local<v8::Value> parent);
Expand Down
69 changes: 11 additions & 58 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -371,25 +371,6 @@ static void PrintErrorString(const char* format, ...) {
}


static void CheckImmediate(uv_check_t* handle) {
Environment* env = Environment::from_immediate_check_handle(handle);
HandleScope scope(env->isolate());
Context::Scope context_scope(env->context());
MakeCallback(env->isolate(),
env->process_object(),
env->immediate_callback_string(),
0,
nullptr,
{0, 0}).ToLocalChecked();
}


static void IdleImmediateDummy(uv_idle_t* handle) {
// Do nothing. Only for maintaining event loop.
// TODO(bnoordhuis) Maybe make libuv accept nullptr idle callbacks.
}


static inline const char *errno_string(int errorno) {
#define ERRNO_CASE(e) case e: return #e;
switch (errorno) {
Expand Down Expand Up @@ -3262,39 +3243,9 @@ static void DebugEnd(const FunctionCallbackInfo<Value>& args);

namespace {

void NeedImmediateCallbackGetter(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
Environment* env = Environment::GetCurrent(info);
const uv_check_t* immediate_check_handle = env->immediate_check_handle();
bool active = uv_is_active(
reinterpret_cast<const uv_handle_t*>(immediate_check_handle));
info.GetReturnValue().Set(active);
}


void NeedImmediateCallbackSetter(
Local<Name> property,
Local<Value> value,
const PropertyCallbackInfo<void>& info) {
Environment* env = Environment::GetCurrent(info);

uv_check_t* immediate_check_handle = env->immediate_check_handle();
bool active = uv_is_active(
reinterpret_cast<const uv_handle_t*>(immediate_check_handle));

if (active == value->BooleanValue())
return;

uv_idle_t* immediate_idle_handle = env->immediate_idle_handle();

if (active) {
uv_check_stop(immediate_check_handle);
uv_idle_stop(immediate_idle_handle);
} else {
uv_check_start(immediate_check_handle, CheckImmediate);
// Idle handle is needed only to stop the event loop from blocking in poll.
uv_idle_start(immediate_idle_handle, IdleImmediateDummy);
}
void ActivateImmediateCheck(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
env->ActivateImmediateCheck();
}


Expand Down Expand Up @@ -3517,12 +3468,11 @@ void SetupProcessObject(Environment* env,
Integer::New(env->isolate(), GetProcessId()));
READONLY_PROPERTY(process, "features", GetFeatures(env));

auto need_immediate_callback_string =
FIXED_ONE_BYTE_STRING(env->isolate(), "_needImmediateCallback");
CHECK(process->SetAccessor(env->context(), need_immediate_callback_string,
NeedImmediateCallbackGetter,
NeedImmediateCallbackSetter,
env->as_external()).FromJust());
auto scheduled_immediate_count =
FIXED_ONE_BYTE_STRING(env->isolate(), "_scheduledImmediateCount");
CHECK(process->Set(env->context(),
scheduled_immediate_count,
env->scheduled_immediate_count().GetJSArray()).FromJust());

// -e, --eval
if (eval_string) {
Expand Down Expand Up @@ -3648,6 +3598,9 @@ void SetupProcessObject(Environment* env,
env->as_external()).FromJust());

// define various internal methods
env->SetMethod(process,
"_activateImmediateCheck",
ActivateImmediateCheck);
env->SetMethod(process,
"_startProfilerIdleNotifier",
StartProfilerIdleNotifier);
Expand Down
Loading