Skip to content

Commit 2930bd1

Browse files
committed
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: #20894 Fixes: #10154 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Gus Caplan <me@gus.host>
1 parent 6f63f8d commit 2930bd1

36 files changed

+218
-320
lines changed

doc/api/async_hooks.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ resource's constructor.
238238
```text
239239
FSEVENTWRAP, FSREQWRAP, GETADDRINFOREQWRAP, GETNAMEINFOREQWRAP, HTTPPARSER,
240240
JSSTREAM, PIPECONNECTWRAP, PIPEWRAP, PROCESSWRAP, QUERYWRAP, SHUTDOWNWRAP,
241-
SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVER, TCPWRAP, TIMERWRAP, TTYWRAP,
241+
SIGNALWRAP, STATWATCHER, TCPCONNECTWRAP, TCPSERVER, TCPWRAP, TTYWRAP,
242242
UDPSENDWRAP, UDPWRAP, WRITEWRAP, ZLIB, SSLCONNECTION, PBKDF2REQUEST,
243243
RANDOMBYTESREQUEST, TLSWRAP, Timeout, Immediate, TickObject
244244
```

lib/internal/timers.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
initHooksExist,
77
emitInit
88
} = require('internal/async_hooks');
9+
const { internalBinding } = require('internal/bootstrap/loaders');
910
// Symbols for storing async id state.
1011
const async_id_symbol = Symbol('asyncId');
1112
const trigger_async_id_symbol = Symbol('triggerId');
@@ -30,7 +31,8 @@ module.exports = {
3031
kRefed,
3132
initAsyncResource,
3233
setUnrefTimeout,
33-
validateTimerDuration
34+
validateTimerDuration,
35+
getLibuvNow: internalBinding('timers').getLibuvNow,
3436
};
3537

3638
var timers;

lib/timers.js

+25-39
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@
2121

2222
'use strict';
2323

24+
const { internalBinding } = require('internal/bootstrap/loaders');
2425
const {
25-
Timer: TimerWrap,
26+
getLibuvNow,
2627
setupTimers,
27-
} = process.binding('timer_wrap');
28+
scheduleTimer,
29+
toggleTimerRef,
30+
immediateInfo,
31+
toggleImmediateRef
32+
} = internalBinding('timers');
2833
const L = require('internal/linkedlist');
2934
const PriorityQueue = require('internal/priority_queue');
3035
const {
@@ -53,8 +58,9 @@ const kCount = 0;
5358
const kRefCount = 1;
5459
const kHasOutstanding = 2;
5560

56-
const [immediateInfo, toggleImmediateRef] =
57-
setupTimers(processImmediate, processTimers);
61+
// Call into C++ to assign callbacks that are responsible for processing
62+
// Immediates and TimerLists.
63+
setupTimers(processImmediate, processTimers);
5864

5965
// HOW and WHY the timers implementation works the way it does.
6066
//
@@ -156,47 +162,38 @@ function setPosition(node, pos) {
156162
node.priorityQueuePosition = pos;
157163
}
158164

159-
let handle = null;
160165
let nextExpiry = Infinity;
161166

162167
let timerListId = Number.MIN_SAFE_INTEGER;
163168
let refCount = 0;
164169

165170
function incRefCount() {
166171
if (refCount++ === 0)
167-
handle.ref();
172+
toggleTimerRef(true);
168173
}
169174

170175
function decRefCount() {
171176
if (--refCount === 0)
172-
handle.unref();
173-
}
174-
175-
function createHandle(refed) {
176-
debug('initial run, creating TimerWrap handle');
177-
handle = new TimerWrap();
178-
if (!refed)
179-
handle.unref();
177+
toggleTimerRef(false);
180178
}
181179

182180
// Schedule or re-schedule a timer.
183181
// The item must have been enroll()'d first.
184182
const active = exports.active = function(item) {
185-
insert(item, true, TimerWrap.now());
183+
insert(item, true, getLibuvNow());
186184
};
187185

188186
// Internal APIs that need timeouts should use `_unrefActive()` instead of
189187
// `active()` so that they do not unnecessarily keep the process open.
190188
exports._unrefActive = function(item) {
191-
insert(item, false, TimerWrap.now());
189+
insert(item, false, getLibuvNow());
192190
};
193191

194192

195193
// The underlying logic for scheduling or re-scheduling a timer.
196194
//
197195
// Appends a timer onto the end of an existing timers list, or creates a new
198-
// TimerWrap backed list if one does not already exist for the specified timeout
199-
// duration.
196+
// list if one does not already exist for the specified timeout duration.
200197
function insert(item, refed, start) {
201198
const msecs = item._idleTimeout;
202199
if (msecs < 0 || msecs === undefined)
@@ -213,9 +210,7 @@ function insert(item, refed, start) {
213210
queue.insert(list);
214211

215212
if (nextExpiry > expiry) {
216-
if (handle === null)
217-
createHandle(refed);
218-
handle.start(msecs);
213+
scheduleTimer(msecs);
219214
nextExpiry = expiry;
220215
}
221216
}
@@ -252,32 +247,23 @@ function processTimers(now) {
252247

253248
let list, ran;
254249
while (list = queue.peek()) {
255-
if (list.expiry > now)
256-
break;
250+
if (list.expiry > now) {
251+
nextExpiry = list.expiry;
252+
return refCount > 0 ? nextExpiry : -nextExpiry;
253+
}
257254
if (ran)
258255
runNextTicks();
256+
else
257+
ran = true;
259258
listOnTimeout(list, now);
260-
ran = true;
261259
}
262-
263-
if (refCount > 0)
264-
handle.ref();
265-
else
266-
handle.unref();
267-
268-
if (list !== undefined) {
269-
nextExpiry = list.expiry;
270-
handle.start(Math.max(nextExpiry - TimerWrap.now(), 1));
271-
}
272-
273-
return true;
260+
return 0;
274261
}
275262

276263
function listOnTimeout(list, now) {
277264
const msecs = list.msecs;
278265

279266
debug('timeout callback %d', msecs);
280-
debug('now: %d', now);
281267

282268
var diff, timer;
283269
while (timer = L.peek(list)) {
@@ -336,7 +322,7 @@ function listOnTimeout(list, now) {
336322
// 4.7) what is in this smaller function.
337323
function tryOnTimeout(timer, start) {
338324
if (start === undefined && timer._repeat)
339-
start = TimerWrap.now();
325+
start = getLibuvNow();
340326
try {
341327
ontimeout(timer);
342328
} finally {
@@ -474,7 +460,7 @@ function ontimeout(timer) {
474460
}
475461

476462
function rearm(timer, start) {
477-
// // Do not re-arm unenroll'd or closed timers.
463+
// Do not re-arm unenroll'd or closed timers.
478464
if (timer._idleTimeout === -1)
479465
return;
480466

node.gyp

+1-1
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@
371371
'src/stream_pipe.cc',
372372
'src/stream_wrap.cc',
373373
'src/tcp_wrap.cc',
374-
'src/timer_wrap.cc',
374+
'src/timers.cc',
375375
'src/tracing/agent.cc',
376376
'src/tracing/node_trace_buffer.cc',
377377
'src/tracing/node_trace_writer.cc',

src/async_wrap.h

-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ namespace node {
6363
V(TCPCONNECTWRAP) \
6464
V(TCPSERVERWRAP) \
6565
V(TCPWRAP) \
66-
V(TIMERWRAP) \
6766
V(TTYWRAP) \
6867
V(UDPSENDWRAP) \
6968
V(UDPWRAP) \

src/env-inl.h

+8
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,14 @@ inline tracing::Agent* Environment::tracing_agent() const {
334334
return tracing_agent_;
335335
}
336336

337+
inline Environment* Environment::from_timer_handle(uv_timer_t* handle) {
338+
return ContainerOf(&Environment::timer_handle_, handle);
339+
}
340+
341+
inline uv_timer_t* Environment::timer_handle() {
342+
return &timer_handle_;
343+
}
344+
337345
inline Environment* Environment::from_immediate_check_handle(
338346
uv_check_t* handle) {
339347
return ContainerOf(&Environment::immediate_check_handle_, handle);

src/env.cc

+81
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace node {
1414

1515
using v8::Context;
16+
using v8::Function;
1617
using v8::FunctionTemplate;
1718
using v8::HandleScope;
1819
using v8::Integer;
@@ -25,6 +26,7 @@ using v8::StackFrame;
2526
using v8::StackTrace;
2627
using v8::String;
2728
using v8::Symbol;
29+
using v8::TryCatch;
2830
using v8::Value;
2931
using worker::Worker;
3032

@@ -173,6 +175,9 @@ void Environment::Start(int argc,
173175
HandleScope handle_scope(isolate());
174176
Context::Scope context_scope(context());
175177

178+
CHECK_EQ(0, uv_timer_init(event_loop(), timer_handle()));
179+
uv_unref(reinterpret_cast<uv_handle_t*>(timer_handle()));
180+
176181
uv_check_init(event_loop(), immediate_check_handle());
177182
uv_unref(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));
178183

@@ -227,6 +232,10 @@ void Environment::RegisterHandleCleanups() {
227232
env->CloseHandle(handle, [](uv_handle_t* handle) {});
228233
};
229234

235+
RegisterHandleCleanup(
236+
reinterpret_cast<uv_handle_t*>(timer_handle()),
237+
close_and_finish,
238+
nullptr);
230239
RegisterHandleCleanup(
231240
reinterpret_cast<uv_handle_t*>(immediate_check_handle()),
232241
close_and_finish,
@@ -470,6 +479,78 @@ void Environment::RunAndClearNativeImmediates() {
470479
}
471480

472481

482+
void Environment::ScheduleTimer(int64_t duration_ms) {
483+
uv_timer_start(timer_handle(), RunTimers, duration_ms, 0);
484+
}
485+
486+
void Environment::ToggleTimerRef(bool ref) {
487+
if (ref) {
488+
uv_ref(reinterpret_cast<uv_handle_t*>(timer_handle()));
489+
} else {
490+
uv_unref(reinterpret_cast<uv_handle_t*>(timer_handle()));
491+
}
492+
}
493+
494+
void Environment::RunTimers(uv_timer_t* handle) {
495+
Environment* env = Environment::from_timer_handle(handle);
496+
497+
if (!env->can_call_into_js())
498+
return;
499+
500+
HandleScope handle_scope(env->isolate());
501+
Context::Scope context_scope(env->context());
502+
503+
Local<Object> process = env->process_object();
504+
InternalCallbackScope scope(env, process, {0, 0});
505+
506+
Local<Function> cb = env->timers_callback_function();
507+
MaybeLocal<Value> ret;
508+
Local<Value> arg = env->GetNow();
509+
// This code will loop until all currently due timers will process. It is
510+
// impossible for us to end up in an infinite loop due to how the JS-side
511+
// is structured.
512+
do {
513+
TryCatch try_catch(env->isolate());
514+
try_catch.SetVerbose(true);
515+
ret = cb->Call(env->context(), process, 1, &arg);
516+
} while (ret.IsEmpty() && env->can_call_into_js());
517+
518+
// NOTE(apapirovski): If it ever becomes possibble that `call_into_js` above
519+
// is reset back to `true` after being previously set to `false` then this
520+
// code becomes invalid and needs to be rewritten. Otherwise catastrophic
521+
// timers corruption will occurr and all timers behaviour will become
522+
// entirely unpredictable.
523+
if (ret.IsEmpty())
524+
return;
525+
526+
// To allow for less JS-C++ boundary crossing, the value returned from JS
527+
// serves a few purposes:
528+
// 1. If it's 0, no more timers exist and the handle should be unrefed
529+
// 2. If it's > 0, the value represents the next timer's expiry and there
530+
// is at least one timer remaining that is refed.
531+
// 3. If it's < 0, the absolute value represents the next timer's expiry
532+
// and there are no timers that are refed.
533+
int64_t expiry_ms =
534+
ret.ToLocalChecked()->IntegerValue(env->context()).FromJust();
535+
536+
uv_handle_t* h = reinterpret_cast<uv_handle_t*>(handle);
537+
538+
if (expiry_ms != 0) {
539+
int64_t duration_ms =
540+
llabs(expiry_ms) - (uv_now(env->event_loop()) - env->timer_base());
541+
542+
env->ScheduleTimer(duration_ms > 0 ? duration_ms : 1);
543+
544+
if (expiry_ms > 0)
545+
uv_ref(h);
546+
else
547+
uv_unref(h);
548+
} else {
549+
uv_unref(h);
550+
}
551+
}
552+
553+
473554
void Environment::CheckImmediate(uv_check_t* handle) {
474555
Environment* env = Environment::from_immediate_check_handle(handle);
475556

src/env.h

+8
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,9 @@ class Environment {
628628
inline uv_loop_t* event_loop() const;
629629
inline uint32_t watched_providers() const;
630630

631+
static inline Environment* from_timer_handle(uv_timer_t* handle);
632+
inline uv_timer_t* timer_handle();
633+
631634
static inline Environment* from_immediate_check_handle(uv_check_t* handle);
632635
inline uv_check_t* immediate_check_handle();
633636
inline uv_idle_t* immediate_idle_handle();
@@ -840,6 +843,8 @@ class Environment {
840843
static inline Environment* ForAsyncHooks(AsyncHooks* hooks);
841844

842845
v8::Local<v8::Value> GetNow();
846+
void ScheduleTimer(int64_t duration);
847+
void ToggleTimerRef(bool ref);
843848

844849
inline void AddCleanupHook(void (*fn)(void*), void* arg);
845850
inline void RemoveCleanupHook(void (*fn)(void*), void* arg);
@@ -857,6 +862,7 @@ class Environment {
857862
v8::Isolate* const isolate_;
858863
IsolateData* const isolate_data_;
859864
tracing::Agent* const tracing_agent_;
865+
uv_timer_t timer_handle_;
860866
uv_check_t immediate_check_handle_;
861867
uv_idle_t immediate_idle_handle_;
862868
uv_prepare_t idle_prepare_handle_;
@@ -919,6 +925,8 @@ class Environment {
919925

920926
worker::Worker* worker_context_ = nullptr;
921927

928+
static void RunTimers(uv_timer_t* handle);
929+
922930
struct ExitCallback {
923931
void (*cb_)(void* arg);
924932
void* arg_;

src/node_internals.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ struct sockaddr;
129129
V(string_decoder) \
130130
V(symbols) \
131131
V(tcp_wrap) \
132-
V(timer_wrap) \
132+
V(timers) \
133133
V(trace_events) \
134134
V(tty_wrap) \
135135
V(types) \

0 commit comments

Comments
 (0)