Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f2dd17b

Browse filesBrowse files
apapirovskiMylesBorins
authored andcommitted
timers: allow Immediates to be unrefed
Refactor Immediates handling to allow for them to be unrefed, similar to setTimeout, but without extra handles. Document the new `immediate.ref()` and `immediate.unref()` methods. Add SetImmediateUnref on the C++ side. Backport-PR-URL: #19006 PR-URL: #18139 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 37f253e commit f2dd17b
Copy full SHA for f2dd17b

File tree

Expand file treeCollapse file tree

10 files changed

+249
-102
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

10 files changed

+249
-102
lines changed
Open diff view settings
Collapse file

‎doc/api/timers.md‎

Copy file name to clipboardExpand all lines: doc/api/timers.md
+32Lines changed: 32 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,38 @@ This object is created internally and is returned from [`setImmediate()`][]. It
1818
can be passed to [`clearImmediate()`][] in order to cancel the scheduled
1919
actions.
2020

21+
By default, when an immediate is scheduled, the Node.js event loop will continue
22+
running as long as the immediate is active. The `Immediate` object returned by
23+
[`setImmediate()`][] exports both `immediate.ref()` and `immediate.unref()`
24+
functions that can be used to control this default behavior.
25+
26+
### immediate.ref()
27+
<!-- YAML
28+
added: REPLACEME
29+
-->
30+
31+
When called, requests that the Node.js event loop *not* exit so long as the
32+
`Immediate` is active. Calling `immediate.ref()` multiple times will have no
33+
effect.
34+
35+
*Note*: By default, all `Immediate` objects are "ref'd", making it normally
36+
unnecessary to call `immediate.ref()` unless `immediate.unref()` had been called
37+
previously.
38+
39+
Returns a reference to the `Immediate`.
40+
41+
### immediate.unref()
42+
<!-- YAML
43+
added: REPLACEME
44+
-->
45+
46+
When called, the active `Immediate` object will not require the Node.js event
47+
loop to remain active. If there is no other activity keeping the event loop
48+
running, the process may exit before the `Immediate` object's callback is
49+
invoked. Calling `immediate.unref()` multiple times will have no effect.
50+
51+
Returns a reference to the `Immediate`.
52+
2153
## Class: Timeout
2254

2355
This object is created internally and is returned from [`setTimeout()`][] and
Collapse file

‎lib/timers.js‎

Copy file name to clipboardExpand all lines: lib/timers.js
+84-59Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,14 @@ const trigger_async_id_symbol = Symbol('triggerAsyncId');
5252

5353
// *Must* match Environment::ImmediateInfo::Fields in src/env.h.
5454
const kCount = 0;
55-
const kHasOutstanding = 1;
55+
const kRefCount = 1;
56+
const kHasOutstanding = 2;
5657

57-
const [activateImmediateCheck, immediateInfo] =
58+
const [immediateInfo, toggleImmediateRef] =
5859
setImmediateCallback(processImmediate);
5960

61+
const kRefed = Symbol('refed');
62+
6063
// Timeout values > TIMEOUT_MAX are set to 1.
6164
const TIMEOUT_MAX = 2 ** 31 - 1;
6265

@@ -690,42 +693,41 @@ function processImmediate() {
690693
const queue = outstandingQueue.head !== null ?
691694
outstandingQueue : immediateQueue;
692695
var immediate = queue.head;
693-
var tail = queue.tail;
696+
const tail = queue.tail;
694697

695698
// Clear the linked list early in case new `setImmediate()` calls occur while
696699
// immediate callbacks are executed
697700
queue.head = queue.tail = null;
698701

699-
while (immediate !== null) {
700-
if (!immediate._onImmediate) {
701-
immediate = immediate._idleNext;
702-
continue;
703-
}
702+
let count = 0;
703+
let refCount = 0;
704704

705-
// Save next in case `clearImmediate(immediate)` is called from callback
706-
const next = immediate._idleNext;
705+
while (immediate !== null) {
706+
immediate._destroyed = true;
707707

708708
const asyncId = immediate[async_id_symbol];
709709
emitBefore(asyncId, immediate[trigger_async_id_symbol]);
710710

711-
tryOnImmediate(immediate, next, tail);
711+
count++;
712+
if (immediate[kRefed])
713+
refCount++;
714+
immediate[kRefed] = undefined;
715+
716+
tryOnImmediate(immediate, tail, count, refCount);
712717

713718
emitAfter(asyncId);
714719

715-
// If `clearImmediate(immediate)` wasn't called from the callback, use the
716-
// `immediate`'s next item
717-
if (immediate._idleNext !== null)
718-
immediate = immediate._idleNext;
719-
else
720-
immediate = next;
720+
immediate = immediate._idleNext;
721721
}
722722

723+
immediateInfo[kCount] -= count;
724+
immediateInfo[kRefCount] -= refCount;
723725
immediateInfo[kHasOutstanding] = 0;
724726
}
725727

726728
// An optimization so that the try/finally only de-optimizes (since at least v8
727729
// 4.7) what is in this smaller function.
728-
function tryOnImmediate(immediate, next, oldTail) {
730+
function tryOnImmediate(immediate, oldTail, count, refCount) {
729731
var threw = true;
730732
try {
731733
// make the actual call outside the try/finally to allow it to be optimized
@@ -734,21 +736,21 @@ function tryOnImmediate(immediate, next, oldTail) {
734736
} finally {
735737
immediate._onImmediate = null;
736738

737-
if (!immediate._destroyed) {
738-
immediate._destroyed = true;
739-
immediateInfo[kCount]--;
740-
741-
if (async_hook_fields[kDestroy] > 0) {
742-
emitDestroy(immediate[async_id_symbol]);
743-
}
739+
if (async_hook_fields[kDestroy] > 0) {
740+
emitDestroy(immediate[async_id_symbol]);
744741
}
745742

746-
if (threw && (immediate._idleNext !== null || next !== null)) {
747-
// Handle any remaining Immediates after error handling has resolved,
748-
// assuming we're still alive to do so.
749-
outstandingQueue.head = immediate._idleNext || next;
750-
outstandingQueue.tail = oldTail;
751-
immediateInfo[kHasOutstanding] = 1;
743+
if (threw) {
744+
immediateInfo[kCount] -= count;
745+
immediateInfo[kRefCount] -= refCount;
746+
747+
if (immediate._idleNext !== null) {
748+
// Handle any remaining Immediates after error handling has resolved,
749+
// assuming we're still alive to do so.
750+
outstandingQueue.head = immediate._idleNext;
751+
outstandingQueue.tail = oldTail;
752+
immediateInfo[kHasOutstanding] = 1;
753+
}
752754
}
753755
}
754756
}
@@ -763,31 +765,51 @@ function runCallback(timer) {
763765
}
764766

765767

766-
function Immediate(callback, args) {
767-
this._idleNext = null;
768-
this._idlePrev = null;
769-
// this must be set to null first to avoid function tracking
770-
// on the hidden class, revisit in V8 versions after 6.2
771-
this._onImmediate = null;
772-
this._onImmediate = callback;
773-
this._argv = args;
774-
this._destroyed = false;
768+
const Immediate = class Immediate {
769+
constructor(callback, args) {
770+
this._idleNext = null;
771+
this._idlePrev = null;
772+
// this must be set to null first to avoid function tracking
773+
// on the hidden class, revisit in V8 versions after 6.2
774+
this._onImmediate = null;
775+
this._onImmediate = callback;
776+
this._argv = args;
777+
this._destroyed = false;
778+
this[kRefed] = false;
779+
780+
this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
781+
this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
782+
if (async_hook_fields[kInit] > 0) {
783+
emitInit(this[async_id_symbol],
784+
'Immediate',
785+
this[trigger_async_id_symbol],
786+
this);
787+
}
775788

776-
this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
777-
this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
778-
if (async_hook_fields[kInit] > 0) {
779-
emitInit(this[async_id_symbol],
780-
'Immediate',
781-
this[trigger_async_id_symbol],
782-
this);
789+
this.ref();
790+
immediateInfo[kCount]++;
791+
792+
immediateQueue.append(this);
783793
}
784794

785-
if (immediateInfo[kCount] === 0)
786-
activateImmediateCheck();
787-
immediateInfo[kCount]++;
795+
ref() {
796+
if (this[kRefed] === false) {
797+
this[kRefed] = true;
798+
if (immediateInfo[kRefCount]++ === 0)
799+
toggleImmediateRef(true);
800+
}
801+
return this;
802+
}
788803

789-
immediateQueue.append(this);
790-
}
804+
unref() {
805+
if (this[kRefed] === true) {
806+
this[kRefed] = false;
807+
if (--immediateInfo[kRefCount] === 0)
808+
toggleImmediateRef(false);
809+
}
810+
return this;
811+
}
812+
};
791813

792814
function setImmediate(callback, arg1, arg2, arg3) {
793815
if (typeof callback !== 'function') {
@@ -827,15 +849,18 @@ exports.setImmediate = setImmediate;
827849

828850

829851
exports.clearImmediate = function(immediate) {
830-
if (!immediate) return;
852+
if (!immediate || immediate._destroyed)
853+
return;
831854

832-
if (!immediate._destroyed) {
833-
immediateInfo[kCount]--;
834-
immediate._destroyed = true;
855+
immediateInfo[kCount]--;
856+
immediate._destroyed = true;
835857

836-
if (async_hook_fields[kDestroy] > 0) {
837-
emitDestroy(immediate[async_id_symbol]);
838-
}
858+
if (immediate[kRefed] && --immediateInfo[kRefCount] === 0)
859+
toggleImmediateRef(false);
860+
immediate[kRefed] = undefined;
861+
862+
if (async_hook_fields[kDestroy] > 0) {
863+
emitDestroy(immediate[async_id_symbol]);
839864
}
840865

841866
immediate._onImmediate = null;
Collapse file

‎src/env-inl.h‎

Copy file name to clipboardExpand all lines: src/env-inl.h
+34-6Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@ inline uint32_t Environment::ImmediateInfo::count() const {
229229
return fields_[kCount];
230230
}
231231

232+
inline uint32_t Environment::ImmediateInfo::ref_count() const {
233+
return fields_[kRefCount];
234+
}
235+
232236
inline bool Environment::ImmediateInfo::has_outstanding() const {
233237
return fields_[kHasOutstanding] == 1;
234238
}
@@ -241,6 +245,14 @@ inline void Environment::ImmediateInfo::count_dec(uint32_t decrement) {
241245
fields_[kCount] = fields_[kCount] - decrement;
242246
}
243247

248+
inline void Environment::ImmediateInfo::ref_count_inc(uint32_t increment) {
249+
fields_[kRefCount] = fields_[kRefCount] + increment;
250+
}
251+
252+
inline void Environment::ImmediateInfo::ref_count_dec(uint32_t decrement) {
253+
fields_[kRefCount] = fields_[kRefCount] - decrement;
254+
}
255+
244256
inline Environment::TickInfo::TickInfo(v8::Isolate* isolate)
245257
: fields_(isolate, kFieldsCount) {}
246258

@@ -514,20 +526,36 @@ inline void Environment::set_fs_stats_field_array(double* fields) {
514526
fs_stats_field_array_ = fields;
515527
}
516528

517-
void Environment::SetImmediate(native_immediate_callback cb,
529+
void Environment::CreateImmediate(native_immediate_callback cb,
518530
void* data,
519-
v8::Local<v8::Object> obj) {
531+
v8::Local<v8::Object> obj,
532+
bool ref) {
520533
native_immediate_callbacks_.push_back({
521534
cb,
522535
data,
523-
std::unique_ptr<v8::Persistent<v8::Object>>(
524-
obj.IsEmpty() ? nullptr : new v8::Persistent<v8::Object>(isolate_, obj))
536+
std::unique_ptr<v8::Persistent<v8::Object>>(obj.IsEmpty() ?
537+
nullptr : new v8::Persistent<v8::Object>(isolate_, obj)),
538+
ref
525539
});
526-
if (immediate_info()->count() == 0)
527-
ActivateImmediateCheck();
528540
immediate_info()->count_inc(1);
529541
}
530542

543+
void Environment::SetImmediate(native_immediate_callback cb,
544+
void* data,
545+
v8::Local<v8::Object> obj) {
546+
CreateImmediate(cb, data, obj, true);
547+
548+
if (immediate_info()->ref_count() == 0)
549+
ToggleImmediateRef(true);
550+
immediate_info()->ref_count_inc(1);
551+
}
552+
553+
void Environment::SetUnrefImmediate(native_immediate_callback cb,
554+
void* data,
555+
v8::Local<v8::Object> obj) {
556+
CreateImmediate(cb, data, obj, false);
557+
}
558+
531559
inline performance::performance_state* Environment::performance_state() {
532560
return performance_state_.get();
533561
}
Collapse file

‎src/env.cc‎

Copy file name to clipboardExpand all lines: src/env.cc
+19-17Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ void Environment::Start(int argc,
8080

8181
uv_idle_init(event_loop(), immediate_idle_handle());
8282

83+
uv_check_start(immediate_check_handle(), CheckImmediate);
84+
8385
// Inform V8's CPU profiler when we're idle. The profiler is sampling-based
8486
// but not all samples are created equal; mark the wall clock time spent in
8587
// epoll_wait() and friends so profiling tools can filter it out. The samples
@@ -272,39 +274,35 @@ void Environment::EnvPromiseHook(v8::PromiseHookType type,
272274
void Environment::RunAndClearNativeImmediates() {
273275
size_t count = native_immediate_callbacks_.size();
274276
if (count > 0) {
277+
size_t ref_count = 0;
275278
std::vector<NativeImmediateCallback> list;
276279
native_immediate_callbacks_.swap(list);
277280
for (const auto& cb : list) {
278281
cb.cb_(this, cb.data_);
279282
if (cb.keep_alive_)
280283
cb.keep_alive_->Reset();
284+
if (cb.refed_)
285+
ref_count++;
281286
}
282287

283288
#ifdef DEBUG
284289
CHECK_GE(immediate_info()->count(), count);
285290
#endif
286291
immediate_info()->count_dec(count);
292+
immediate_info()->ref_count_dec(ref_count);
287293
}
288294
}
289295

290-
static bool MaybeStopImmediate(Environment* env) {
291-
if (env->immediate_info()->count() == 0) {
292-
uv_check_stop(env->immediate_check_handle());
293-
uv_idle_stop(env->immediate_idle_handle());
294-
return true;
295-
}
296-
return false;
297-
}
298-
299296

300297
void Environment::CheckImmediate(uv_check_t* handle) {
301298
Environment* env = Environment::from_immediate_check_handle(handle);
302-
HandleScope scope(env->isolate());
303-
Context::Scope context_scope(env->context());
304299

305-
if (MaybeStopImmediate(env))
300+
if (env->immediate_info()->count() == 0)
306301
return;
307302

303+
HandleScope scope(env->isolate());
304+
Context::Scope context_scope(env->context());
305+
308306
env->RunAndClearNativeImmediates();
309307

310308
do {
@@ -316,13 +314,17 @@ void Environment::CheckImmediate(uv_check_t* handle) {
316314
{0, 0}).ToLocalChecked();
317315
} while (env->immediate_info()->has_outstanding());
318316

319-
MaybeStopImmediate(env);
317+
if (env->immediate_info()->ref_count() == 0)
318+
env->ToggleImmediateRef(false);
320319
}
321320

322-
void Environment::ActivateImmediateCheck() {
323-
uv_check_start(&immediate_check_handle_, CheckImmediate);
324-
// Idle handle is needed only to stop the event loop from blocking in poll.
325-
uv_idle_start(&immediate_idle_handle_, [](uv_idle_t*){ });
321+
void Environment::ToggleImmediateRef(bool ref) {
322+
if (ref) {
323+
// Idle handle is needed only to stop the event loop from blocking in poll.
324+
uv_idle_start(immediate_idle_handle(), [](uv_idle_t*){ });
325+
} else {
326+
uv_idle_stop(immediate_idle_handle());
327+
}
326328
}
327329

328330
void Environment::AsyncHooks::grow_async_ids_stack() {

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.