From 2930bd1317d15d12738a4896c0a6c05700411b47 Mon Sep 17 00:00:00 2001 From: Anatoli Papirovski Date: Sun, 13 May 2018 17:42:22 +0200 Subject: src: refactor timers to remove TimerWrap Refactor Timers to behave more similarly to Immediates by having a single uv_timer_t handle which is stored on the Environment. No longer expose timers in a public binding and instead make it part of the internalBinding. PR-URL: https://github.com/nodejs/node/pull/20894 Fixes: https://github.com/nodejs/node/issues/10154 Reviewed-By: Anna Henningsen Reviewed-By: Ben Noordhuis Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Ruben Bridgewater Reviewed-By: Matteo Collina Reviewed-By: Jeremiah Senkpiel Reviewed-By: Tiancheng "Timothy" Gu Reviewed-By: Gus Caplan --- src/async_wrap.h | 1 - src/env-inl.h | 8 ++++++ src/env.cc | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/env.h | 8 ++++++ src/node_internals.h | 2 +- src/timers.cc | 64 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/timers.cc (limited to 'src') diff --git a/src/async_wrap.h b/src/async_wrap.h index 82c5791092..64b74ac209 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -63,7 +63,6 @@ namespace node { V(TCPCONNECTWRAP) \ V(TCPSERVERWRAP) \ V(TCPWRAP) \ - V(TIMERWRAP) \ V(TTYWRAP) \ V(UDPSENDWRAP) \ V(UDPWRAP) \ diff --git a/src/env-inl.h b/src/env-inl.h index bbb80c6f7a..40fa5dfa68 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -334,6 +334,14 @@ inline tracing::Agent* Environment::tracing_agent() const { return tracing_agent_; } +inline Environment* Environment::from_timer_handle(uv_timer_t* handle) { + return ContainerOf(&Environment::timer_handle_, handle); +} + +inline uv_timer_t* Environment::timer_handle() { + return &timer_handle_; +} + inline Environment* Environment::from_immediate_check_handle( uv_check_t* handle) { return ContainerOf(&Environment::immediate_check_handle_, handle); diff --git a/src/env.cc b/src/env.cc index 2236893dd7..f940c18d16 100644 --- a/src/env.cc +++ b/src/env.cc @@ -13,6 +13,7 @@ namespace node { using v8::Context; +using v8::Function; using v8::FunctionTemplate; using v8::HandleScope; using v8::Integer; @@ -25,6 +26,7 @@ using v8::StackFrame; using v8::StackTrace; using v8::String; using v8::Symbol; +using v8::TryCatch; using v8::Value; using worker::Worker; @@ -173,6 +175,9 @@ void Environment::Start(int argc, HandleScope handle_scope(isolate()); Context::Scope context_scope(context()); + CHECK_EQ(0, uv_timer_init(event_loop(), timer_handle())); + uv_unref(reinterpret_cast(timer_handle())); + uv_check_init(event_loop(), immediate_check_handle()); uv_unref(reinterpret_cast(immediate_check_handle())); @@ -227,6 +232,10 @@ void Environment::RegisterHandleCleanups() { env->CloseHandle(handle, [](uv_handle_t* handle) {}); }; + RegisterHandleCleanup( + reinterpret_cast(timer_handle()), + close_and_finish, + nullptr); RegisterHandleCleanup( reinterpret_cast(immediate_check_handle()), close_and_finish, @@ -470,6 +479,78 @@ void Environment::RunAndClearNativeImmediates() { } +void Environment::ScheduleTimer(int64_t duration_ms) { + uv_timer_start(timer_handle(), RunTimers, duration_ms, 0); +} + +void Environment::ToggleTimerRef(bool ref) { + if (ref) { + uv_ref(reinterpret_cast(timer_handle())); + } else { + uv_unref(reinterpret_cast(timer_handle())); + } +} + +void Environment::RunTimers(uv_timer_t* handle) { + Environment* env = Environment::from_timer_handle(handle); + + if (!env->can_call_into_js()) + return; + + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local process = env->process_object(); + InternalCallbackScope scope(env, process, {0, 0}); + + Local cb = env->timers_callback_function(); + MaybeLocal ret; + Local arg = env->GetNow(); + // This code will loop until all currently due timers will process. It is + // impossible for us to end up in an infinite loop due to how the JS-side + // is structured. + do { + TryCatch try_catch(env->isolate()); + try_catch.SetVerbose(true); + ret = cb->Call(env->context(), process, 1, &arg); + } while (ret.IsEmpty() && env->can_call_into_js()); + + // NOTE(apapirovski): If it ever becomes possibble that `call_into_js` above + // is reset back to `true` after being previously set to `false` then this + // code becomes invalid and needs to be rewritten. Otherwise catastrophic + // timers corruption will occurr and all timers behaviour will become + // entirely unpredictable. + if (ret.IsEmpty()) + return; + + // To allow for less JS-C++ boundary crossing, the value returned from JS + // serves a few purposes: + // 1. If it's 0, no more timers exist and the handle should be unrefed + // 2. If it's > 0, the value represents the next timer's expiry and there + // is at least one timer remaining that is refed. + // 3. If it's < 0, the absolute value represents the next timer's expiry + // and there are no timers that are refed. + int64_t expiry_ms = + ret.ToLocalChecked()->IntegerValue(env->context()).FromJust(); + + uv_handle_t* h = reinterpret_cast(handle); + + if (expiry_ms != 0) { + int64_t duration_ms = + llabs(expiry_ms) - (uv_now(env->event_loop()) - env->timer_base()); + + env->ScheduleTimer(duration_ms > 0 ? duration_ms : 1); + + if (expiry_ms > 0) + uv_ref(h); + else + uv_unref(h); + } else { + uv_unref(h); + } +} + + void Environment::CheckImmediate(uv_check_t* handle) { Environment* env = Environment::from_immediate_check_handle(handle); diff --git a/src/env.h b/src/env.h index 786f0846f4..8f4c5ea1a6 100644 --- a/src/env.h +++ b/src/env.h @@ -628,6 +628,9 @@ class Environment { inline uv_loop_t* event_loop() const; inline uint32_t watched_providers() const; + static inline Environment* from_timer_handle(uv_timer_t* handle); + inline uv_timer_t* timer_handle(); + static inline Environment* from_immediate_check_handle(uv_check_t* handle); inline uv_check_t* immediate_check_handle(); inline uv_idle_t* immediate_idle_handle(); @@ -840,6 +843,8 @@ class Environment { static inline Environment* ForAsyncHooks(AsyncHooks* hooks); v8::Local GetNow(); + void ScheduleTimer(int64_t duration); + void ToggleTimerRef(bool ref); inline void AddCleanupHook(void (*fn)(void*), void* arg); inline void RemoveCleanupHook(void (*fn)(void*), void* arg); @@ -857,6 +862,7 @@ class Environment { v8::Isolate* const isolate_; IsolateData* const isolate_data_; tracing::Agent* const tracing_agent_; + uv_timer_t timer_handle_; uv_check_t immediate_check_handle_; uv_idle_t immediate_idle_handle_; uv_prepare_t idle_prepare_handle_; @@ -919,6 +925,8 @@ class Environment { worker::Worker* worker_context_ = nullptr; + static void RunTimers(uv_timer_t* handle); + struct ExitCallback { void (*cb_)(void* arg); void* arg_; diff --git a/src/node_internals.h b/src/node_internals.h index cd791f8c05..860566e314 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -129,7 +129,7 @@ struct sockaddr; V(string_decoder) \ V(symbols) \ V(tcp_wrap) \ - V(timer_wrap) \ + V(timers) \ V(trace_events) \ V(tty_wrap) \ V(types) \ diff --git a/src/timers.cc b/src/timers.cc new file mode 100644 index 0000000000..e9daf8d37c --- /dev/null +++ b/src/timers.cc @@ -0,0 +1,64 @@ +#include "node_internals.h" + +#include + +namespace node { +namespace { + +using v8::Array; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Integer; +using v8::Local; +using v8::Object; +using v8::Value; + +void SetupTimers(const FunctionCallbackInfo& args) { + CHECK(args[0]->IsFunction()); + CHECK(args[1]->IsFunction()); + auto env = Environment::GetCurrent(args); + + env->set_immediate_callback_function(args[0].As()); + env->set_timers_callback_function(args[1].As()); +} + +void GetLibuvNow(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + args.GetReturnValue().Set(env->GetNow()); +} + +void ScheduleTimer(const FunctionCallbackInfo& args) { + auto env = Environment::GetCurrent(args); + env->ScheduleTimer(args[0]->IntegerValue(env->context()).FromJust()); +} + +void ToggleTimerRef(const FunctionCallbackInfo& args) { + Environment::GetCurrent(args)->ToggleTimerRef(args[0]->IsTrue()); +} + +void ToggleImmediateRef(const FunctionCallbackInfo& args) { + Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue()); +} + +void Initialize(Local target, + Local unused, + Local context) { + Environment* env = Environment::GetCurrent(context); + + env->SetMethod(target, "getLibuvNow", GetLibuvNow); + env->SetMethod(target, "setupTimers", SetupTimers); + env->SetMethod(target, "scheduleTimer", ScheduleTimer); + env->SetMethod(target, "toggleTimerRef", ToggleTimerRef); + env->SetMethod(target, "toggleImmediateRef", ToggleImmediateRef); + + target->Set(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "immediateInfo"), + env->immediate_info()->fields().GetJSArray()).FromJust(); +} + + +} // anonymous namespace +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(timers, node::Initialize) -- cgit v1.2.3