From ca852e3f50a2074da9f946519a72a4fa4c4b1777 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 9 Feb 2023 00:34:07 +0100 Subject: [PATCH] timers: use V8 fast API calls --- lib/internal/timers.js | 44 ++++--- lib/timers.js | 10 +- node.gyp | 1 + src/base_object_types.h | 3 +- src/env.cc | 8 +- src/env.h | 2 + src/node_external_reference.h | 9 ++ src/node_snapshotable.cc | 1 + src/timers.cc | 174 +++++++++++++++++++++----- src/timers.h | 68 ++++++++++ test/parallel/test-timers-now.js | 5 +- test/parallel/test-timers-ordering.js | 5 +- 12 files changed, 271 insertions(+), 59 deletions(-) create mode 100644 src/timers.h diff --git a/lib/internal/timers.js b/lib/internal/timers.js index 2b3848e6df7caf..aa7d3ec69f2189 100644 --- a/lib/internal/timers.js +++ b/lib/internal/timers.js @@ -81,14 +81,11 @@ const { Symbol, } = primordials; +const binding = internalBinding('timers'); const { - scheduleTimer, - toggleTimerRef, - getLibuvNow, immediateInfo, timeoutInfo, - toggleImmediateRef, -} = internalBinding('timers'); +} = binding; const { getDefaultTriggerAsyncId, @@ -306,13 +303,17 @@ class ImmediateList { const immediateQueue = new ImmediateList(); function incRefCount() { - if (timeoutInfo[0]++ === 0) - toggleTimerRef(true); + if (timeoutInfo[0]++ === 0) { + // We need to use the binding as the receiver for fast API calls. + binding.toggleTimerRef(true); + } } function decRefCount() { - if (--timeoutInfo[0] === 0) - toggleTimerRef(false); + if (--timeoutInfo[0] === 0) { + // We need to use the binding as the receiver for fast API calls. + binding.toggleTimerRef(false); + } } // Schedule or re-schedule a timer. @@ -356,7 +357,8 @@ function insertGuarded(item, refed, start) { item[kRefed] = refed; } -function insert(item, msecs, start = getLibuvNow()) { +// We need to use the binding as the receiver for fast API calls. +function insert(item, msecs, start = binding.getLibuvNow()) { // Truncate so that accuracy of sub-millisecond timers is not assumed. msecs = MathTrunc(msecs); item._idleStart = start; @@ -370,7 +372,8 @@ function insert(item, msecs, start = getLibuvNow()) { timerListQueue.insert(list); if (nextExpiry > expiry) { - scheduleTimer(msecs); + // We need to use the binding as the receiver for fast API calls. + binding.scheduleTimer(msecs); nextExpiry = expiry; } } @@ -559,8 +562,10 @@ function getTimerCallbacks(runNextTicks) { emitBefore(asyncId, timer[trigger_async_id_symbol], timer); let start; - if (timer._repeat) - start = getLibuvNow(); + if (timer._repeat) { + // We need to use the binding as the receiver for fast API calls. + start = binding.getLibuvNow(); + } try { const args = timer._timerArgs; @@ -627,8 +632,11 @@ class Immediate { ref() { if (this[kRefed] === false) { this[kRefed] = true; - if (immediateInfo[kRefCount]++ === 0) - toggleImmediateRef(true); + + if (immediateInfo[kRefCount]++ === 0) { + // We need to use the binding as the receiver for fast API calls. + binding.toggleImmediateRef(true); + } } return this; } @@ -636,8 +644,10 @@ class Immediate { unref() { if (this[kRefed] === true) { this[kRefed] = false; - if (--immediateInfo[kRefCount] === 0) - toggleImmediateRef(false); + if (--immediateInfo[kRefCount] === 0) { + // We need to use the binding as the receiver for fast API calls. + binding.toggleImmediateRef(false); + } } return this; } diff --git a/lib/timers.js b/lib/timers.js index 4e3e0dc360f859..ddfbeb58bb6a81 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -27,10 +27,10 @@ const { SymbolToPrimitive } = primordials; +const binding = internalBinding('timers'); const { immediateInfo, - toggleImmediateRef -} = internalBinding('timers'); +} = binding; const L = require('internal/linkedlist'); const { async_id_symbol, @@ -323,8 +323,10 @@ function clearImmediate(immediate) { immediateInfo[kCount]--; immediate._destroyed = true; - if (immediate[kRefed] && --immediateInfo[kRefCount] === 0) - toggleImmediateRef(false); + if (immediate[kRefed] && --immediateInfo[kRefCount] === 0) { + // We need to use the binding as the receiver for fast API calls. + binding.toggleImmediateRef(false); + } immediate[kRefed] = null; if (destroyHooksExist() && immediate[async_id_symbol] !== undefined) { diff --git a/node.gyp b/node.gyp index cb46307ef62012..01307a17b3083e 100644 --- a/node.gyp +++ b/node.gyp @@ -677,6 +677,7 @@ 'src/string_decoder-inl.h', 'src/string_search.h', 'src/tcp_wrap.h', + 'src/timers.h', 'src/tracing/agent.h', 'src/tracing/node_trace_buffer.h', 'src/tracing/node_trace_writer.h', diff --git a/src/base_object_types.h b/src/base_object_types.h index f4c70a89177975..db5b5e2f5e9ba9 100644 --- a/src/base_object_types.h +++ b/src/base_object_types.h @@ -13,7 +13,8 @@ namespace node { V(fs_binding_data, fs::BindingData) \ V(v8_binding_data, v8_utils::BindingData) \ V(blob_binding_data, BlobBindingData) \ - V(process_binding_data, process::BindingData) + V(process_binding_data, process::BindingData) \ + V(timers_binding_data, timers::BindingData) #define UNSERIALIZABLE_BINDING_TYPES(V) \ V(http2_binding_data, http2::BindingData) \ diff --git a/src/env.cc b/src/env.cc index c730401c7a7abc..8af50e2f1b0181 100644 --- a/src/env.cc +++ b/src/env.cc @@ -1299,12 +1299,16 @@ void Environment::ToggleImmediateRef(bool ref) { } } - -Local Environment::GetNow() { +uint64_t Environment::GetNowUint64() { uv_update_time(event_loop()); uint64_t now = uv_now(event_loop()); CHECK_GE(now, timer_base()); now -= timer_base(); + return now; +} + +Local Environment::GetNow() { + uint64_t now = GetNowUint64(); if (now <= 0xffffffff) return Integer::NewFromUnsigned(isolate(), static_cast(now)); else diff --git a/src/env.h b/src/env.h index 629b99a9205069..95ffa357ab05b4 100644 --- a/src/env.h +++ b/src/env.h @@ -891,6 +891,8 @@ class Environment : public MemoryRetainer { static inline Environment* ForAsyncHooks(AsyncHooks* hooks); v8::Local GetNow(); + uint64_t GetNowUint64(); + void ScheduleTimer(int64_t duration); void ToggleTimerRef(bool ref); diff --git a/src/node_external_reference.h b/src/node_external_reference.h index 2eb3e3bf3cd458..d55801a119e195 100644 --- a/src/node_external_reference.h +++ b/src/node_external_reference.h @@ -13,6 +13,12 @@ namespace node { using CFunctionCallbackWithOneByteString = uint32_t (*)(v8::Local, const v8::FastOneByteString&); using CFunctionCallback = void (*)(v8::Local receiver); +using CFunctionCallbackReturnDouble = + double (*)(v8::Local receiver); +using CFunctionCallbackWithInt64 = void (*)(v8::Local receiver, + int64_t); +using CFunctionCallbackWithBool = void (*)(v8::Local receiver, + bool); // This class manages the external references from the V8 heap // to the C++ addresses in Node.js. @@ -23,6 +29,9 @@ class ExternalReferenceRegistry { #define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \ V(CFunctionCallback) \ V(CFunctionCallbackWithOneByteString) \ + V(CFunctionCallbackReturnDouble) \ + V(CFunctionCallbackWithInt64) \ + V(CFunctionCallbackWithBool) \ V(const v8::CFunctionInfo*) \ V(v8::FunctionCallback) \ V(v8::AccessorGetterCallback) \ diff --git a/src/node_snapshotable.cc b/src/node_snapshotable.cc index d9d4f162e9cfd2..e6323a128f5ba4 100644 --- a/src/node_snapshotable.cc +++ b/src/node_snapshotable.cc @@ -20,6 +20,7 @@ #include "node_util.h" #include "node_v8.h" #include "node_v8_platform-inl.h" +#include "timers.h" #if HAVE_INSPECTOR #include "inspector/worker_inspector.h" // ParentInspectorHandle diff --git a/src/timers.cc b/src/timers.cc index 39bb749c0724f1..2f10daba12ef3a 100644 --- a/src/timers.cc +++ b/src/timers.cc @@ -1,3 +1,4 @@ +#include "timers.h" #include "env-inl.h" #include "node_external_reference.h" #include "util-inl.h" @@ -6,16 +7,17 @@ #include namespace node { -namespace { +namespace timers { using v8::Context; using v8::Function; using v8::FunctionCallbackInfo; using v8::Local; +using v8::Number; using v8::Object; using v8::Value; -void SetupTimers(const FunctionCallbackInfo& args) { +void BindingData::SetupTimers(const FunctionCallbackInfo& args) { CHECK(args[0]->IsFunction()); CHECK(args[1]->IsFunction()); auto env = Environment::GetCurrent(args); @@ -24,58 +26,168 @@ void SetupTimers(const FunctionCallbackInfo& args) { env->set_timers_callback_function(args[1].As()); } -void GetLibuvNow(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - args.GetReturnValue().Set(env->GetNow()); +void BindingData::SlowGetLibuvNow(const FunctionCallbackInfo& args) { + double now = GetLibuvNowImpl(Realm::GetBindingData(args)); + args.GetReturnValue().Set(Number::New(args.GetIsolate(), now)); } -void ScheduleTimer(const FunctionCallbackInfo& args) { - auto env = Environment::GetCurrent(args); - env->ScheduleTimer(args[0]->IntegerValue(env->context()).FromJust()); +double BindingData::FastGetLibuvNow(Local receiver) { + return GetLibuvNowImpl(FromJSObject(receiver)); +} + +double BindingData::GetLibuvNowImpl(BindingData* data) { + return static_cast(data->env()->GetNowUint64()); +} + +void BindingData::SlowScheduleTimer(const FunctionCallbackInfo& args) { + int64_t duration = + args[0]->IntegerValue(args.GetIsolate()->GetCurrentContext()).FromJust(); + ScheduleTimerImpl(Realm::GetBindingData(args), duration); +} + +void BindingData::FastScheduleTimer(Local receiver, int64_t duration) { + ScheduleTimerImpl(FromJSObject(receiver), duration); +} + +void BindingData::ScheduleTimerImpl(BindingData* data, int64_t duration) { + data->env()->ScheduleTimer(duration); +} + +void BindingData::SlowToggleTimerRef( + const v8::FunctionCallbackInfo& args) { + ToggleTimerRefImpl(Realm::GetBindingData(args), + args[0]->IsTrue()); +} + +void BindingData::FastToggleTimerRef(Local receiver, bool ref) { + ToggleTimerRefImpl(FromJSObject(receiver), ref); +} + +void BindingData::ToggleTimerRefImpl(BindingData* data, bool ref) { + data->env()->ToggleTimerRef(ref); } -void ToggleTimerRef(const FunctionCallbackInfo& args) { - Environment::GetCurrent(args)->ToggleTimerRef(args[0]->IsTrue()); +void BindingData::SlowToggleImmediateRef( + const v8::FunctionCallbackInfo& args) { + ToggleImmediateRefImpl(Realm::GetBindingData(args), + args[0]->IsTrue()); } -void ToggleImmediateRef(const FunctionCallbackInfo& args) { - Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue()); +void BindingData::FastToggleImmediateRef(Local receiver, bool ref) { + ToggleImmediateRefImpl(FromJSObject(receiver), ref); } -void Initialize(Local target, - Local unused, - Local context, - void* priv) { - Environment* env = Environment::GetCurrent(context); +void BindingData::ToggleImmediateRefImpl(BindingData* data, bool ref) { + data->env()->ToggleImmediateRef(ref); +} + +BindingData::BindingData(Realm* realm, Local object) + : SnapshotableObject(realm, object, type_int) {} + +bool BindingData::PrepareForSerialization(Local context, + v8::SnapshotCreator* creator) { + // Return true because we need to maintain the reference to the binding from + // JS land. + return true; +} + +InternalFieldInfoBase* BindingData::Serialize(int index) { + DCHECK_EQ(index, BaseObject::kEmbedderType); + InternalFieldInfo* info = + InternalFieldInfoBase::New(type()); + return info; +} + +void BindingData::Deserialize(Local context, + Local holder, + int index, + InternalFieldInfoBase* info) { + DCHECK_EQ(index, BaseObject::kEmbedderType); + v8::HandleScope scope(context->GetIsolate()); + Realm* realm = Realm::GetCurrent(context); + // Recreate the buffer in the constructor. + BindingData* binding = realm->AddBindingData(context, holder); + CHECK_NOT_NULL(binding); +} + +v8::CFunction BindingData::fast_get_libuv_now_( + v8::CFunction::Make(FastGetLibuvNow)); +v8::CFunction BindingData::fast_schedule_timers_( + v8::CFunction::Make(FastScheduleTimer)); +v8::CFunction BindingData::fast_toggle_timer_ref_( + v8::CFunction::Make(FastToggleTimerRef)); +v8::CFunction BindingData::fast_toggle_immediate_ref_( + v8::CFunction::Make(FastToggleImmediateRef)); + +void BindingData::Initialize(Local target, + Local unused, + Local context, + void* priv) { + Realm* realm = Realm::GetCurrent(context); + Environment* env = realm->env(); + BindingData* const binding_data = + realm->AddBindingData(context, target); + if (binding_data == nullptr) return; - SetMethod(context, target, "getLibuvNow", GetLibuvNow); SetMethod(context, target, "setupTimers", SetupTimers); - SetMethod(context, target, "scheduleTimer", ScheduleTimer); - SetMethod(context, target, "toggleTimerRef", ToggleTimerRef); - SetMethod(context, target, "toggleImmediateRef", ToggleImmediateRef); + SetFastMethod( + context, target, "getLibuvNow", SlowGetLibuvNow, &fast_get_libuv_now_); + SetFastMethod(context, + target, + "scheduleTimer", + SlowScheduleTimer, + &fast_schedule_timers_); + SetFastMethod(context, + target, + "toggleTimerRef", + SlowToggleTimerRef, + &fast_toggle_timer_ref_); + SetFastMethod(context, + target, + "toggleImmediateRef", + SlowToggleImmediateRef, + &fast_toggle_immediate_ref_); + // TODO(joyeecheung): move these into BindingData. target ->Set(context, - FIXED_ONE_BYTE_STRING(env->isolate(), "immediateInfo"), + FIXED_ONE_BYTE_STRING(realm->isolate(), "immediateInfo"), env->immediate_info()->fields().GetJSArray()) .Check(); target ->Set(context, - FIXED_ONE_BYTE_STRING(env->isolate(), "timeoutInfo"), + FIXED_ONE_BYTE_STRING(realm->isolate(), "timeoutInfo"), env->timeout_info().GetJSArray()) .Check(); } -} // anonymous namespace -void RegisterTimerExternalReferences(ExternalReferenceRegistry* registry) { - registry->Register(GetLibuvNow); + +void BindingData::RegisterTimerExternalReferences( + ExternalReferenceRegistry* registry) { registry->Register(SetupTimers); - registry->Register(ScheduleTimer); - registry->Register(ToggleTimerRef); - registry->Register(ToggleImmediateRef); + + registry->Register(SlowGetLibuvNow); + registry->Register(FastGetLibuvNow); + registry->Register(fast_get_libuv_now_.GetTypeInfo()); + + registry->Register(SlowScheduleTimer); + registry->Register(FastScheduleTimer); + registry->Register(fast_schedule_timers_.GetTypeInfo()); + + registry->Register(SlowToggleTimerRef); + registry->Register(FastToggleTimerRef); + registry->Register(fast_toggle_timer_ref_.GetTypeInfo()); + + registry->Register(SlowToggleImmediateRef); + registry->Register(FastToggleImmediateRef); + registry->Register(fast_toggle_immediate_ref_.GetTypeInfo()); } +} // namespace timers + } // namespace node -NODE_BINDING_CONTEXT_AWARE_INTERNAL(timers, node::Initialize) -NODE_BINDING_EXTERNAL_REFERENCE(timers, node::RegisterTimerExternalReferences) +NODE_BINDING_CONTEXT_AWARE_INTERNAL(timers, + node::timers::BindingData::Initialize) +NODE_BINDING_EXTERNAL_REFERENCE( + timers, node::timers::BindingData::RegisterTimerExternalReferences) diff --git a/src/timers.h b/src/timers.h new file mode 100644 index 00000000000000..7f2ac45e71af6d --- /dev/null +++ b/src/timers.h @@ -0,0 +1,68 @@ +#ifndef SRC_TIMERS_H_ +#define SRC_TIMERS_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include +#include "node_snapshotable.h" + +namespace node { +class ExternalReferenceRegistry; + +namespace timers { +class BindingData : public SnapshotableObject { + public: + BindingData(Realm* env, v8::Local obj); + + using InternalFieldInfo = InternalFieldInfoBase; + + SET_BINDING_ID(timers_binding_data) + SERIALIZABLE_OBJECT_METHODS() + + SET_NO_MEMORY_INFO() + SET_SELF_SIZE(BindingData) + SET_MEMORY_INFO_NAME(BindingData) + + static void SetupTimers(const v8::FunctionCallbackInfo& args); + + static void SlowGetLibuvNow(const v8::FunctionCallbackInfo& args); + static double FastGetLibuvNow(v8::Local receiver); + static double GetLibuvNowImpl(BindingData* data); + + static void SlowScheduleTimer( + const v8::FunctionCallbackInfo& args); + static void FastScheduleTimer(v8::Local receiver, + int64_t duration); + static void ScheduleTimerImpl(BindingData* data, int64_t duration); + + static void SlowToggleTimerRef( + const v8::FunctionCallbackInfo& args); + static void FastToggleTimerRef(v8::Local receiver, bool ref); + static void ToggleTimerRefImpl(BindingData* data, bool ref); + + static void SlowToggleImmediateRef( + const v8::FunctionCallbackInfo& args); + static void FastToggleImmediateRef(v8::Local receiver, bool ref); + static void ToggleImmediateRefImpl(BindingData* data, bool ref); + + static void Initialize(v8::Local target, + v8::Local unused, + v8::Local context, + void* priv); + static void RegisterTimerExternalReferences( + ExternalReferenceRegistry* registry); + + private: + static v8::CFunction fast_get_libuv_now_; + static v8::CFunction fast_schedule_timers_; + static v8::CFunction fast_toggle_timer_ref_; + static v8::CFunction fast_toggle_immediate_ref_; +}; + +} // namespace timers + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_TIMERS_H_ diff --git a/test/parallel/test-timers-now.js b/test/parallel/test-timers-now.js index 91b3f63496da2b..e5e97521f1b18f 100644 --- a/test/parallel/test-timers-now.js +++ b/test/parallel/test-timers-now.js @@ -4,7 +4,8 @@ require('../common'); const assert = require('assert'); const { internalBinding } = require('internal/test/binding'); -const { getLibuvNow } = internalBinding('timers'); +const binding = internalBinding('timers'); // Return value of getLibuvNow() should easily fit in a SMI after start-up. -assert(getLibuvNow() < 0x3ffffff); +// We need to use the binding as the receiver for fast API calls. +assert(binding.getLibuvNow() < 0x3ffffff); diff --git a/test/parallel/test-timers-ordering.js b/test/parallel/test-timers-ordering.js index 6c6daecef3a563..3942f358a4db17 100644 --- a/test/parallel/test-timers-ordering.js +++ b/test/parallel/test-timers-ordering.js @@ -25,7 +25,7 @@ require('../common'); const assert = require('assert'); const { internalBinding } = require('internal/test/binding'); -const { getLibuvNow } = internalBinding('timers'); +const binding = internalBinding('timers'); const N = 30; @@ -39,7 +39,8 @@ function f(i) { last_i = i; // Check that this iteration is fired at least 1ms later than the previous - const now = getLibuvNow(); + // We need to use the binding as the receiver for fast API calls. + const now = binding.getLibuvNow(); assert(now >= last_ts + 1, `current ts ${now} < prev ts ${last_ts} + 1`); last_ts = now;