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>
This commit is contained in:
Joyee Cheung 2023-02-28 10:37:24 +01:00 committed by GitHub
parent 9562c20bc9
commit 1101713cc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 271 additions and 59 deletions

View File

@ -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;
}

View File

@ -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) {

View File

@ -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',

View File

@ -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) \

View File

@ -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

View File

@ -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);

View File

@ -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) \

View File

@ -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

View File

@ -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));
}
void ToggleTimerRef(const FunctionCallbackInfo<Value>& args) {
Environment::GetCurrent(args)->ToggleTimerRef(args[0]->IsTrue());
double BindingData::GetLibuvNowImpl(BindingData* data) {
return static_cast<double>(data->env()->GetNowUint64());
}
void ToggleImmediateRef(const FunctionCallbackInfo<Value>& args) {
Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
void BindingData::SlowScheduleTimer(const FunctionCallbackInfo<Value>& args) {
int64_t duration =
args[0]->IntegerValue(args.GetIsolate()->GetCurrentContext()).FromJust();
ScheduleTimerImpl(Realm::GetBindingData<BindingData>(args), duration);
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
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 BindingData::SlowToggleImmediateRef(
const v8::FunctionCallbackInfo<v8::Value>& args) {
ToggleImmediateRefImpl(Realm::GetBindingData<BindingData>(args),
args[0]->IsTrue());
}
void BindingData::FastToggleImmediateRef(Local<Object> receiver, bool ref) {
ToggleImmediateRefImpl(FromJSObject<BindingData>(receiver), ref);
}
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)

68
src/timers.h Normal file
View File

@ -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_

View File

@ -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);

View File

@ -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;