summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoyee Cheung <joyeec9h3@gmail.com>2023-02-28 10:37:24 +0100
committerGitHub <noreply@github.com>2023-02-28 09:37:24 +0000
commit1101713cc2dd20a141781083aa385c1294df6708 (patch)
tree815180aa4ca84204704b5aa3ba499521f17825c3
parent9562c20bc9609d3a00e03cbd5654ece4ec863773 (diff)
downloadnode-new-1101713cc2dd20a141781083aa385c1294df6708.tar.gz
timers: use V8 fast API calls
PR-URL: https://github.com/nodejs/node/pull/46579 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
-rw-r--r--lib/internal/timers.js44
-rw-r--r--lib/timers.js10
-rw-r--r--node.gyp1
-rw-r--r--src/base_object_types.h3
-rw-r--r--src/env.cc8
-rw-r--r--src/env.h2
-rw-r--r--src/node_external_reference.h9
-rw-r--r--src/node_snapshotable.cc1
-rw-r--r--src/timers.cc174
-rw-r--r--src/timers.h68
-rw-r--r--test/parallel/test-timers-now.js5
-rw-r--r--test/parallel/test-timers-ordering.js5
12 files changed, 271 insertions, 59 deletions
diff --git a/lib/internal/timers.js b/lib/internal/timers.js
index 2b3848e6df..aa7d3ec69f 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 4e3e0dc360..ddfbeb58bb 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 cb46307ef6..01307a17b3 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 f4c70a8917..db5b5e2f5e 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 c730401c7a..8af50e2f1b 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -1299,12 +1299,16 @@ void Environment::ToggleImmediateRef(bool ref) {
}
}
-
-Local<Value> 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<Value> Environment::GetNow() {
+ uint64_t now = GetNowUint64();
if (now <= 0xffffffff)
return Integer::NewFromUnsigned(isolate(), static_cast<uint32_t>(now));
else
diff --git a/src/env.h b/src/env.h
index 629b99a920..95ffa357ab 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<v8::Value> 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 2eb3e3bf3c..d55801a119 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<v8::Value>, const v8::FastOneByteString&);
using CFunctionCallback = void (*)(v8::Local<v8::Value> receiver);
+using CFunctionCallbackReturnDouble =
+ double (*)(v8::Local<v8::Object> receiver);
+using CFunctionCallbackWithInt64 = void (*)(v8::Local<v8::Object> receiver,
+ int64_t);
+using CFunctionCallbackWithBool = void (*)(v8::Local<v8::Object> 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 d9d4f162e9..e6323a128f 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 39bb749c07..2f10daba12 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 <cstdint>
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<Value>& args) {
+void BindingData::SetupTimers(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsFunction());
CHECK(args[1]->IsFunction());
auto env = Environment::GetCurrent(args);
@@ -24,58 +26,168 @@ void SetupTimers(const FunctionCallbackInfo<Value>& args) {
env->set_timers_callback_function(args[1].As<Function>());
}
-void GetLibuvNow(const FunctionCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- args.GetReturnValue().Set(env->GetNow());
+void BindingData::SlowGetLibuvNow(const FunctionCallbackInfo<Value>& args) {
+ double now = GetLibuvNowImpl(Realm::GetBindingData<BindingData>(args));
+ args.GetReturnValue().Set(Number::New(args.GetIsolate(), now));
}
-void ScheduleTimer(const FunctionCallbackInfo<Value>& args) {
- auto env = Environment::GetCurrent(args);
- env->ScheduleTimer(args[0]->IntegerValue(env->context()).FromJust());
+double BindingData::FastGetLibuvNow(Local<Object> receiver) {
+ return GetLibuvNowImpl(FromJSObject<BindingData>(receiver));
+}
+
+double BindingData::GetLibuvNowImpl(BindingData* data) {
+ return static_cast<double>(data->env()->GetNowUint64());
+}
+
+void BindingData::SlowScheduleTimer(const FunctionCallbackInfo<Value>& args) {
+ int64_t duration =
+ args[0]->IntegerValue(args.GetIsolate()->GetCurrentContext()).FromJust();
+ ScheduleTimerImpl(Realm::GetBindingData<BindingData>(args), duration);
+}
+
+void BindingData::FastScheduleTimer(Local<Object> receiver, int64_t duration) {
+ ScheduleTimerImpl(FromJSObject<BindingData>(receiver), duration);
+}
+
+void BindingData::ScheduleTimerImpl(BindingData* data, int64_t duration) {
+ data->env()->ScheduleTimer(duration);
+}
+
+void BindingData::SlowToggleTimerRef(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ToggleTimerRefImpl(Realm::GetBindingData<BindingData>(args),
+ args[0]->IsTrue());
+}
+
+void BindingData::FastToggleTimerRef(Local<Object> receiver, bool ref) {
+ ToggleTimerRefImpl(FromJSObject<BindingData>(receiver), ref);
+}
+
+void BindingData::ToggleTimerRefImpl(BindingData* data, bool ref) {
+ data->env()->ToggleTimerRef(ref);
}
-void ToggleTimerRef(const FunctionCallbackInfo<Value>& args) {
- Environment::GetCurrent(args)->ToggleTimerRef(args[0]->IsTrue());
+void BindingData::SlowToggleImmediateRef(
+ const v8::FunctionCallbackInfo<v8::Value>& args) {
+ ToggleImmediateRefImpl(Realm::GetBindingData<BindingData>(args),
+ args[0]->IsTrue());
}
-void ToggleImmediateRef(const FunctionCallbackInfo<Value>& args) {
- Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
+void BindingData::FastToggleImmediateRef(Local<Object> receiver, bool ref) {
+ ToggleImmediateRefImpl(FromJSObject<BindingData>(receiver), ref);
}
-void Initialize(Local<Object> target,
- Local<Value> unused,
- Local<Context> 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> object)
+ : SnapshotableObject(realm, object, type_int) {}
+
+bool BindingData::PrepareForSerialization(Local<Context> 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<InternalFieldInfo>(type());
+ return info;
+}
+
+void BindingData::Deserialize(Local<Context> context,
+ Local<Object> 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<BindingData>(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<Object> target,
+ Local<Value> unused,
+ Local<Context> context,
+ void* priv) {
+ Realm* realm = Realm::GetCurrent(context);
+ Environment* env = realm->env();
+ BindingData* const binding_data =
+ realm->AddBindingData<BindingData>(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 0000000000..7f2ac45e71
--- /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 <cinttypes>
+#include "node_snapshotable.h"
+
+namespace node {
+class ExternalReferenceRegistry;
+
+namespace timers {
+class BindingData : public SnapshotableObject {
+ public:
+ BindingData(Realm* env, v8::Local<v8::Object> 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<v8::Value>& args);
+
+ static void SlowGetLibuvNow(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static double FastGetLibuvNow(v8::Local<v8::Object> receiver);
+ static double GetLibuvNowImpl(BindingData* data);
+
+ static void SlowScheduleTimer(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void FastScheduleTimer(v8::Local<v8::Object> receiver,
+ int64_t duration);
+ static void ScheduleTimerImpl(BindingData* data, int64_t duration);
+
+ static void SlowToggleTimerRef(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void FastToggleTimerRef(v8::Local<v8::Object> receiver, bool ref);
+ static void ToggleTimerRefImpl(BindingData* data, bool ref);
+
+ static void SlowToggleImmediateRef(
+ const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void FastToggleImmediateRef(v8::Local<v8::Object> receiver, bool ref);
+ static void ToggleImmediateRefImpl(BindingData* data, bool ref);
+
+ static void Initialize(v8::Local<v8::Object> target,
+ v8::Local<v8::Value> unused,
+ v8::Local<v8::Context> 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 91b3f63496..e5e97521f1 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 6c6daecef3..3942f358a4 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;