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. PR-URL: https://github.com/nodejs/node/pull/18139 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
7809f386b0
commit
c1234673bb
@ -18,6 +18,38 @@ This object is created internally and is returned from [`setImmediate()`][]. It
|
|||||||
can be passed to [`clearImmediate()`][] in order to cancel the scheduled
|
can be passed to [`clearImmediate()`][] in order to cancel the scheduled
|
||||||
actions.
|
actions.
|
||||||
|
|
||||||
|
By default, when an immediate is scheduled, the Node.js event loop will continue
|
||||||
|
running as long as the immediate is active. The `Immediate` object returned by
|
||||||
|
[`setImmediate()`][] exports both `immediate.ref()` and `immediate.unref()`
|
||||||
|
functions that can be used to control this default behavior.
|
||||||
|
|
||||||
|
### immediate.ref()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
When called, requests that the Node.js event loop *not* exit so long as the
|
||||||
|
`Immediate` is active. Calling `immediate.ref()` multiple times will have no
|
||||||
|
effect.
|
||||||
|
|
||||||
|
*Note*: By default, all `Immediate` objects are "ref'd", making it normally
|
||||||
|
unnecessary to call `immediate.ref()` unless `immediate.unref()` had been called
|
||||||
|
previously.
|
||||||
|
|
||||||
|
Returns a reference to the `Immediate`.
|
||||||
|
|
||||||
|
### immediate.unref()
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
When called, the active `Immediate` object will not require the Node.js event
|
||||||
|
loop to remain active. If there is no other activity keeping the event loop
|
||||||
|
running, the process may exit before the `Immediate` object's callback is
|
||||||
|
invoked. Calling `immediate.unref()` multiple times will have no effect.
|
||||||
|
|
||||||
|
Returns a reference to the `Immediate`.
|
||||||
|
|
||||||
## Class: Timeout
|
## Class: Timeout
|
||||||
|
|
||||||
This object is created internally and is returned from [`setTimeout()`][] and
|
This object is created internally and is returned from [`setTimeout()`][] and
|
||||||
|
@ -53,11 +53,14 @@ const trigger_async_id_symbol = timerInternals.trigger_async_id_symbol;
|
|||||||
|
|
||||||
// *Must* match Environment::ImmediateInfo::Fields in src/env.h.
|
// *Must* match Environment::ImmediateInfo::Fields in src/env.h.
|
||||||
const kCount = 0;
|
const kCount = 0;
|
||||||
const kHasOutstanding = 1;
|
const kRefCount = 1;
|
||||||
|
const kHasOutstanding = 2;
|
||||||
|
|
||||||
const [activateImmediateCheck, immediateInfo] =
|
const [immediateInfo, toggleImmediateRef] =
|
||||||
setImmediateCallback(processImmediate);
|
setImmediateCallback(processImmediate);
|
||||||
|
|
||||||
|
const kRefed = Symbol('refed');
|
||||||
|
|
||||||
// The Timeout class
|
// The Timeout class
|
||||||
const Timeout = timerInternals.Timeout;
|
const Timeout = timerInternals.Timeout;
|
||||||
|
|
||||||
@ -656,42 +659,41 @@ function processImmediate() {
|
|||||||
const queue = outstandingQueue.head !== null ?
|
const queue = outstandingQueue.head !== null ?
|
||||||
outstandingQueue : immediateQueue;
|
outstandingQueue : immediateQueue;
|
||||||
var immediate = queue.head;
|
var immediate = queue.head;
|
||||||
var tail = queue.tail;
|
const tail = queue.tail;
|
||||||
|
|
||||||
// Clear the linked list early in case new `setImmediate()` calls occur while
|
// Clear the linked list early in case new `setImmediate()` calls occur while
|
||||||
// immediate callbacks are executed
|
// immediate callbacks are executed
|
||||||
queue.head = queue.tail = null;
|
queue.head = queue.tail = null;
|
||||||
|
|
||||||
while (immediate !== null) {
|
let count = 0;
|
||||||
if (!immediate._onImmediate) {
|
let refCount = 0;
|
||||||
immediate = immediate._idleNext;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save next in case `clearImmediate(immediate)` is called from callback
|
while (immediate !== null) {
|
||||||
const next = immediate._idleNext;
|
immediate._destroyed = true;
|
||||||
|
|
||||||
const asyncId = immediate[async_id_symbol];
|
const asyncId = immediate[async_id_symbol];
|
||||||
emitBefore(asyncId, immediate[trigger_async_id_symbol]);
|
emitBefore(asyncId, immediate[trigger_async_id_symbol]);
|
||||||
|
|
||||||
tryOnImmediate(immediate, next, tail);
|
count++;
|
||||||
|
if (immediate[kRefed])
|
||||||
|
refCount++;
|
||||||
|
immediate[kRefed] = undefined;
|
||||||
|
|
||||||
|
tryOnImmediate(immediate, tail, count, refCount);
|
||||||
|
|
||||||
emitAfter(asyncId);
|
emitAfter(asyncId);
|
||||||
|
|
||||||
// If `clearImmediate(immediate)` wasn't called from the callback, use the
|
|
||||||
// `immediate`'s next item
|
|
||||||
if (immediate._idleNext !== null)
|
|
||||||
immediate = immediate._idleNext;
|
immediate = immediate._idleNext;
|
||||||
else
|
|
||||||
immediate = next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
immediateInfo[kCount] -= count;
|
||||||
|
immediateInfo[kRefCount] -= refCount;
|
||||||
immediateInfo[kHasOutstanding] = 0;
|
immediateInfo[kHasOutstanding] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// An optimization so that the try/finally only de-optimizes (since at least v8
|
// An optimization so that the try/finally only de-optimizes (since at least v8
|
||||||
// 4.7) what is in this smaller function.
|
// 4.7) what is in this smaller function.
|
||||||
function tryOnImmediate(immediate, next, oldTail) {
|
function tryOnImmediate(immediate, oldTail, count, refCount) {
|
||||||
var threw = true;
|
var threw = true;
|
||||||
try {
|
try {
|
||||||
// make the actual call outside the try/finally to allow it to be optimized
|
// make the actual call outside the try/finally to allow it to be optimized
|
||||||
@ -700,24 +702,24 @@ function tryOnImmediate(immediate, next, oldTail) {
|
|||||||
} finally {
|
} finally {
|
||||||
immediate._onImmediate = null;
|
immediate._onImmediate = null;
|
||||||
|
|
||||||
if (!immediate._destroyed) {
|
|
||||||
immediate._destroyed = true;
|
|
||||||
immediateInfo[kCount]--;
|
|
||||||
|
|
||||||
if (async_hook_fields[kDestroy] > 0) {
|
if (async_hook_fields[kDestroy] > 0) {
|
||||||
emitDestroy(immediate[async_id_symbol]);
|
emitDestroy(immediate[async_id_symbol]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (threw && (immediate._idleNext !== null || next !== null)) {
|
if (threw) {
|
||||||
|
immediateInfo[kCount] -= count;
|
||||||
|
immediateInfo[kRefCount] -= refCount;
|
||||||
|
|
||||||
|
if (immediate._idleNext !== null) {
|
||||||
// Handle any remaining Immediates after error handling has resolved,
|
// Handle any remaining Immediates after error handling has resolved,
|
||||||
// assuming we're still alive to do so.
|
// assuming we're still alive to do so.
|
||||||
outstandingQueue.head = immediate._idleNext || next;
|
outstandingQueue.head = immediate._idleNext;
|
||||||
outstandingQueue.tail = oldTail;
|
outstandingQueue.tail = oldTail;
|
||||||
immediateInfo[kHasOutstanding] = 1;
|
immediateInfo[kHasOutstanding] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function runCallback(timer) {
|
function runCallback(timer) {
|
||||||
const argv = timer._argv;
|
const argv = timer._argv;
|
||||||
@ -729,7 +731,8 @@ function runCallback(timer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Immediate(callback, args) {
|
const Immediate = class Immediate {
|
||||||
|
constructor(callback, args) {
|
||||||
this._idleNext = null;
|
this._idleNext = null;
|
||||||
this._idlePrev = null;
|
this._idlePrev = null;
|
||||||
// this must be set to null first to avoid function tracking
|
// this must be set to null first to avoid function tracking
|
||||||
@ -738,6 +741,7 @@ function Immediate(callback, args) {
|
|||||||
this._onImmediate = callback;
|
this._onImmediate = callback;
|
||||||
this._argv = args;
|
this._argv = args;
|
||||||
this._destroyed = false;
|
this._destroyed = false;
|
||||||
|
this[kRefed] = false;
|
||||||
|
|
||||||
this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
|
this[async_id_symbol] = ++async_id_fields[kAsyncIdCounter];
|
||||||
this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
|
this[trigger_async_id_symbol] = getDefaultTriggerAsyncId();
|
||||||
@ -748,13 +752,31 @@ function Immediate(callback, args) {
|
|||||||
this);
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (immediateInfo[kCount] === 0)
|
this.ref();
|
||||||
activateImmediateCheck();
|
|
||||||
immediateInfo[kCount]++;
|
immediateInfo[kCount]++;
|
||||||
|
|
||||||
immediateQueue.append(this);
|
immediateQueue.append(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref() {
|
||||||
|
if (this[kRefed] === false) {
|
||||||
|
this[kRefed] = true;
|
||||||
|
if (immediateInfo[kRefCount]++ === 0)
|
||||||
|
toggleImmediateRef(true);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
unref() {
|
||||||
|
if (this[kRefed] === true) {
|
||||||
|
this[kRefed] = false;
|
||||||
|
if (--immediateInfo[kRefCount] === 0)
|
||||||
|
toggleImmediateRef(false);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function setImmediate(callback, arg1, arg2, arg3) {
|
function setImmediate(callback, arg1, arg2, arg3) {
|
||||||
if (typeof callback !== 'function') {
|
if (typeof callback !== 'function') {
|
||||||
throw new errors.TypeError('ERR_INVALID_CALLBACK');
|
throw new errors.TypeError('ERR_INVALID_CALLBACK');
|
||||||
@ -793,16 +815,19 @@ exports.setImmediate = setImmediate;
|
|||||||
|
|
||||||
|
|
||||||
exports.clearImmediate = function(immediate) {
|
exports.clearImmediate = function(immediate) {
|
||||||
if (!immediate) return;
|
if (!immediate || immediate._destroyed)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!immediate._destroyed) {
|
|
||||||
immediateInfo[kCount]--;
|
immediateInfo[kCount]--;
|
||||||
immediate._destroyed = true;
|
immediate._destroyed = true;
|
||||||
|
|
||||||
|
if (immediate[kRefed] && --immediateInfo[kRefCount] === 0)
|
||||||
|
toggleImmediateRef(false);
|
||||||
|
immediate[kRefed] = undefined;
|
||||||
|
|
||||||
if (async_hook_fields[kDestroy] > 0) {
|
if (async_hook_fields[kDestroy] > 0) {
|
||||||
emitDestroy(immediate[async_id_symbol]);
|
emitDestroy(immediate[async_id_symbol]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
immediate._onImmediate = null;
|
immediate._onImmediate = null;
|
||||||
|
|
||||||
|
@ -229,6 +229,10 @@ inline uint32_t Environment::ImmediateInfo::count() const {
|
|||||||
return fields_[kCount];
|
return fields_[kCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline uint32_t Environment::ImmediateInfo::ref_count() const {
|
||||||
|
return fields_[kRefCount];
|
||||||
|
}
|
||||||
|
|
||||||
inline bool Environment::ImmediateInfo::has_outstanding() const {
|
inline bool Environment::ImmediateInfo::has_outstanding() const {
|
||||||
return fields_[kHasOutstanding] == 1;
|
return fields_[kHasOutstanding] == 1;
|
||||||
}
|
}
|
||||||
@ -241,6 +245,14 @@ inline void Environment::ImmediateInfo::count_dec(uint32_t decrement) {
|
|||||||
fields_[kCount] = fields_[kCount] - decrement;
|
fields_[kCount] = fields_[kCount] - decrement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Environment::ImmediateInfo::ref_count_inc(uint32_t increment) {
|
||||||
|
fields_[kRefCount] = fields_[kRefCount] + increment;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Environment::ImmediateInfo::ref_count_dec(uint32_t decrement) {
|
||||||
|
fields_[kRefCount] = fields_[kRefCount] - decrement;
|
||||||
|
}
|
||||||
|
|
||||||
inline Environment::TickInfo::TickInfo(v8::Isolate* isolate)
|
inline Environment::TickInfo::TickInfo(v8::Isolate* isolate)
|
||||||
: fields_(isolate, kFieldsCount) {}
|
: fields_(isolate, kFieldsCount) {}
|
||||||
|
|
||||||
@ -536,20 +548,36 @@ inline void Environment::set_fs_stats_field_array(double* fields) {
|
|||||||
fs_stats_field_array_ = fields;
|
fs_stats_field_array_ = fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Environment::SetImmediate(native_immediate_callback cb,
|
void Environment::CreateImmediate(native_immediate_callback cb,
|
||||||
void* data,
|
void* data,
|
||||||
v8::Local<v8::Object> obj) {
|
v8::Local<v8::Object> obj,
|
||||||
|
bool ref) {
|
||||||
native_immediate_callbacks_.push_back({
|
native_immediate_callbacks_.push_back({
|
||||||
cb,
|
cb,
|
||||||
data,
|
data,
|
||||||
std::unique_ptr<v8::Persistent<v8::Object>>(
|
std::unique_ptr<v8::Persistent<v8::Object>>(obj.IsEmpty() ?
|
||||||
obj.IsEmpty() ? nullptr : new v8::Persistent<v8::Object>(isolate_, obj))
|
nullptr : new v8::Persistent<v8::Object>(isolate_, obj)),
|
||||||
|
ref
|
||||||
});
|
});
|
||||||
if (immediate_info()->count() == 0)
|
|
||||||
ActivateImmediateCheck();
|
|
||||||
immediate_info()->count_inc(1);
|
immediate_info()->count_inc(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Environment::SetImmediate(native_immediate_callback cb,
|
||||||
|
void* data,
|
||||||
|
v8::Local<v8::Object> obj) {
|
||||||
|
CreateImmediate(cb, data, obj, true);
|
||||||
|
|
||||||
|
if (immediate_info()->ref_count() == 0)
|
||||||
|
ToggleImmediateRef(true);
|
||||||
|
immediate_info()->ref_count_inc(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Environment::SetUnrefImmediate(native_immediate_callback cb,
|
||||||
|
void* data,
|
||||||
|
v8::Local<v8::Object> obj) {
|
||||||
|
CreateImmediate(cb, data, obj, false);
|
||||||
|
}
|
||||||
|
|
||||||
inline performance::performance_state* Environment::performance_state() {
|
inline performance::performance_state* Environment::performance_state() {
|
||||||
return performance_state_;
|
return performance_state_;
|
||||||
}
|
}
|
||||||
|
34
src/env.cc
34
src/env.cc
@ -82,6 +82,8 @@ void Environment::Start(int argc,
|
|||||||
|
|
||||||
uv_idle_init(event_loop(), immediate_idle_handle());
|
uv_idle_init(event_loop(), immediate_idle_handle());
|
||||||
|
|
||||||
|
uv_check_start(immediate_check_handle(), CheckImmediate);
|
||||||
|
|
||||||
// Inform V8's CPU profiler when we're idle. The profiler is sampling-based
|
// Inform V8's CPU profiler when we're idle. The profiler is sampling-based
|
||||||
// but not all samples are created equal; mark the wall clock time spent in
|
// but not all samples are created equal; mark the wall clock time spent in
|
||||||
// epoll_wait() and friends so profiling tools can filter it out. The samples
|
// epoll_wait() and friends so profiling tools can filter it out. The samples
|
||||||
@ -274,39 +276,35 @@ void Environment::EnvPromiseHook(v8::PromiseHookType type,
|
|||||||
void Environment::RunAndClearNativeImmediates() {
|
void Environment::RunAndClearNativeImmediates() {
|
||||||
size_t count = native_immediate_callbacks_.size();
|
size_t count = native_immediate_callbacks_.size();
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
|
size_t ref_count = 0;
|
||||||
std::vector<NativeImmediateCallback> list;
|
std::vector<NativeImmediateCallback> list;
|
||||||
native_immediate_callbacks_.swap(list);
|
native_immediate_callbacks_.swap(list);
|
||||||
for (const auto& cb : list) {
|
for (const auto& cb : list) {
|
||||||
cb.cb_(this, cb.data_);
|
cb.cb_(this, cb.data_);
|
||||||
if (cb.keep_alive_)
|
if (cb.keep_alive_)
|
||||||
cb.keep_alive_->Reset();
|
cb.keep_alive_->Reset();
|
||||||
|
if (cb.refed_)
|
||||||
|
ref_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
CHECK_GE(immediate_info()->count(), count);
|
CHECK_GE(immediate_info()->count(), count);
|
||||||
#endif
|
#endif
|
||||||
immediate_info()->count_dec(count);
|
immediate_info()->count_dec(count);
|
||||||
|
immediate_info()->ref_count_dec(ref_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool MaybeStopImmediate(Environment* env) {
|
|
||||||
if (env->immediate_info()->count() == 0) {
|
|
||||||
uv_check_stop(env->immediate_check_handle());
|
|
||||||
uv_idle_stop(env->immediate_idle_handle());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Environment::CheckImmediate(uv_check_t* handle) {
|
void Environment::CheckImmediate(uv_check_t* handle) {
|
||||||
Environment* env = Environment::from_immediate_check_handle(handle);
|
Environment* env = Environment::from_immediate_check_handle(handle);
|
||||||
|
|
||||||
|
if (env->immediate_info()->count() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
HandleScope scope(env->isolate());
|
HandleScope scope(env->isolate());
|
||||||
Context::Scope context_scope(env->context());
|
Context::Scope context_scope(env->context());
|
||||||
|
|
||||||
if (MaybeStopImmediate(env))
|
|
||||||
return;
|
|
||||||
|
|
||||||
env->RunAndClearNativeImmediates();
|
env->RunAndClearNativeImmediates();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -318,13 +316,17 @@ void Environment::CheckImmediate(uv_check_t* handle) {
|
|||||||
{0, 0}).ToLocalChecked();
|
{0, 0}).ToLocalChecked();
|
||||||
} while (env->immediate_info()->has_outstanding());
|
} while (env->immediate_info()->has_outstanding());
|
||||||
|
|
||||||
MaybeStopImmediate(env);
|
if (env->immediate_info()->ref_count() == 0)
|
||||||
|
env->ToggleImmediateRef(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Environment::ActivateImmediateCheck() {
|
void Environment::ToggleImmediateRef(bool ref) {
|
||||||
uv_check_start(&immediate_check_handle_, CheckImmediate);
|
if (ref) {
|
||||||
// Idle handle is needed only to stop the event loop from blocking in poll.
|
// Idle handle is needed only to stop the event loop from blocking in poll.
|
||||||
uv_idle_start(&immediate_idle_handle_, [](uv_idle_t*){ });
|
uv_idle_start(immediate_idle_handle(), [](uv_idle_t*){ });
|
||||||
|
} else {
|
||||||
|
uv_idle_stop(immediate_idle_handle());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
17
src/env.h
17
src/env.h
@ -453,17 +453,22 @@ class Environment {
|
|||||||
public:
|
public:
|
||||||
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
|
inline AliasedBuffer<uint32_t, v8::Uint32Array>& fields();
|
||||||
inline uint32_t count() const;
|
inline uint32_t count() const;
|
||||||
|
inline uint32_t ref_count() const;
|
||||||
inline bool has_outstanding() const;
|
inline bool has_outstanding() const;
|
||||||
|
|
||||||
inline void count_inc(uint32_t increment);
|
inline void count_inc(uint32_t increment);
|
||||||
inline void count_dec(uint32_t decrement);
|
inline void count_dec(uint32_t decrement);
|
||||||
|
|
||||||
|
inline void ref_count_inc(uint32_t increment);
|
||||||
|
inline void ref_count_dec(uint32_t decrement);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Environment; // So we can call the constructor.
|
friend class Environment; // So we can call the constructor.
|
||||||
inline explicit ImmediateInfo(v8::Isolate* isolate);
|
inline explicit ImmediateInfo(v8::Isolate* isolate);
|
||||||
|
|
||||||
enum Fields {
|
enum Fields {
|
||||||
kCount,
|
kCount,
|
||||||
|
kRefCount,
|
||||||
kHasOutstanding,
|
kHasOutstanding,
|
||||||
kFieldsCount
|
kFieldsCount
|
||||||
};
|
};
|
||||||
@ -694,8 +699,12 @@ class Environment {
|
|||||||
inline void SetImmediate(native_immediate_callback cb,
|
inline void SetImmediate(native_immediate_callback cb,
|
||||||
void* data,
|
void* data,
|
||||||
v8::Local<v8::Object> obj = v8::Local<v8::Object>());
|
v8::Local<v8::Object> obj = v8::Local<v8::Object>());
|
||||||
|
inline void SetUnrefImmediate(native_immediate_callback cb,
|
||||||
|
void* data,
|
||||||
|
v8::Local<v8::Object> obj =
|
||||||
|
v8::Local<v8::Object>());
|
||||||
// This needs to be available for the JS-land setImmediate().
|
// This needs to be available for the JS-land setImmediate().
|
||||||
void ActivateImmediateCheck();
|
void ToggleImmediateRef(bool ref);
|
||||||
|
|
||||||
class ShouldNotAbortOnUncaughtScope {
|
class ShouldNotAbortOnUncaughtScope {
|
||||||
public:
|
public:
|
||||||
@ -712,6 +721,11 @@ class Environment {
|
|||||||
static inline Environment* ForAsyncHooks(AsyncHooks* hooks);
|
static inline Environment* ForAsyncHooks(AsyncHooks* hooks);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
inline void CreateImmediate(native_immediate_callback cb,
|
||||||
|
void* data,
|
||||||
|
v8::Local<v8::Object> obj,
|
||||||
|
bool ref);
|
||||||
|
|
||||||
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
|
inline void ThrowError(v8::Local<v8::Value> (*fun)(v8::Local<v8::String>),
|
||||||
const char* errmsg);
|
const char* errmsg);
|
||||||
|
|
||||||
@ -776,6 +790,7 @@ class Environment {
|
|||||||
native_immediate_callback cb_;
|
native_immediate_callback cb_;
|
||||||
void* data_;
|
void* data_;
|
||||||
std::unique_ptr<v8::Persistent<v8::Object>> keep_alive_;
|
std::unique_ptr<v8::Persistent<v8::Object>> keep_alive_;
|
||||||
|
bool refed_;
|
||||||
};
|
};
|
||||||
std::vector<NativeImmediateCallback> native_immediate_callbacks_;
|
std::vector<NativeImmediateCallback> native_immediate_callbacks_;
|
||||||
void RunAndClearNativeImmediates();
|
void RunAndClearNativeImmediates();
|
||||||
|
@ -182,9 +182,8 @@ void SetupPerformanceObservers(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Creates a GC Performance Entry and passes it to observers
|
// Creates a GC Performance Entry and passes it to observers
|
||||||
void PerformanceGCCallback(uv_async_t* handle) {
|
void PerformanceGCCallback(Environment* env, void* ptr) {
|
||||||
GCPerformanceEntry* entry = static_cast<GCPerformanceEntry*>(handle->data);
|
GCPerformanceEntry* entry = static_cast<GCPerformanceEntry*>(ptr);
|
||||||
Environment* env = entry->env();
|
|
||||||
HandleScope scope(env->isolate());
|
HandleScope scope(env->isolate());
|
||||||
Local<Context> context = env->context();
|
Local<Context> context = env->context();
|
||||||
|
|
||||||
@ -201,10 +200,6 @@ void PerformanceGCCallback(uv_async_t* handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete entry;
|
delete entry;
|
||||||
auto closeCB = [](uv_handle_t* handle) {
|
|
||||||
delete reinterpret_cast<uv_async_t*>(handle);
|
|
||||||
};
|
|
||||||
uv_close(reinterpret_cast<uv_handle_t*>(handle), closeCB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marks the start of a GC cycle
|
// Marks the start of a GC cycle
|
||||||
@ -221,16 +216,13 @@ void MarkGarbageCollectionEnd(Isolate* isolate,
|
|||||||
v8::GCCallbackFlags flags,
|
v8::GCCallbackFlags flags,
|
||||||
void* data) {
|
void* data) {
|
||||||
Environment* env = static_cast<Environment*>(data);
|
Environment* env = static_cast<Environment*>(data);
|
||||||
uv_async_t* async = new uv_async_t();
|
GCPerformanceEntry* entry =
|
||||||
if (uv_async_init(env->event_loop(), async, PerformanceGCCallback))
|
|
||||||
return delete async;
|
|
||||||
uv_unref(reinterpret_cast<uv_handle_t*>(async));
|
|
||||||
async->data =
|
|
||||||
new GCPerformanceEntry(env,
|
new GCPerformanceEntry(env,
|
||||||
static_cast<PerformanceGCKind>(type),
|
static_cast<PerformanceGCKind>(type),
|
||||||
performance_last_gc_start_mark_,
|
performance_last_gc_start_mark_,
|
||||||
PERFORMANCE_NOW());
|
PERFORMANCE_NOW());
|
||||||
CHECK_EQ(0, uv_async_send(async));
|
env->SetUnrefImmediate(PerformanceGCCallback,
|
||||||
|
entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,16 +83,16 @@ class TimerWrap : public HandleWrap {
|
|||||||
CHECK(args[0]->IsFunction());
|
CHECK(args[0]->IsFunction());
|
||||||
auto env = Environment::GetCurrent(args);
|
auto env = Environment::GetCurrent(args);
|
||||||
env->set_immediate_callback_function(args[0].As<Function>());
|
env->set_immediate_callback_function(args[0].As<Function>());
|
||||||
auto activate_cb = [] (const FunctionCallbackInfo<Value>& args) {
|
auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
|
||||||
Environment::GetCurrent(args)->ActivateImmediateCheck();
|
Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
|
||||||
};
|
};
|
||||||
auto activate_function =
|
auto toggle_ref_function =
|
||||||
env->NewFunctionTemplate(activate_cb)->GetFunction(env->context())
|
env->NewFunctionTemplate(toggle_ref_cb)->GetFunction(env->context())
|
||||||
.ToLocalChecked();
|
.ToLocalChecked();
|
||||||
auto result = Array::New(env->isolate(), 2);
|
auto result = Array::New(env->isolate(), 2);
|
||||||
result->Set(env->context(), 0, activate_function).FromJust();
|
result->Set(env->context(), 0,
|
||||||
result->Set(env->context(), 1,
|
|
||||||
env->immediate_info()->fields().GetJSArray()).FromJust();
|
env->immediate_info()->fields().GetJSArray()).FromJust();
|
||||||
|
result->Set(env->context(), 1, toggle_ref_function).FromJust();
|
||||||
args.GetReturnValue().Set(result);
|
args.GetReturnValue().Set(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,15 @@ void* SetImmediate(napi_env env, T&& cb) {
|
|||||||
|
|
||||||
assert(cb() != nullptr);
|
assert(cb() != nullptr);
|
||||||
});
|
});
|
||||||
|
// Idle handle is needed only to stop the event loop from blocking in poll.
|
||||||
|
uv_idle_t* idle = new uv_idle_t;
|
||||||
|
uv_idle_init(loop, idle);
|
||||||
|
uv_idle_start(idle, [](uv_idle_t* idle) {
|
||||||
|
uv_close(reinterpret_cast<uv_handle_t*>(idle), [](uv_handle_t* handle) {
|
||||||
|
delete reinterpret_cast<uv_check_t*>(handle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
test/parallel/test-timers-immediate-unref-simple.js
Normal file
7
test/parallel/test-timers-immediate-unref-simple.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
// This immediate should not execute as it was unrefed
|
||||||
|
// and nothing else is keeping the event loop alive
|
||||||
|
setImmediate(common.mustNotCall()).unref();
|
37
test/parallel/test-timers-immediate-unref.js
Normal file
37
test/parallel/test-timers-immediate-unref.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const Countdown = require('../common/countdown');
|
||||||
|
|
||||||
|
// This immediate should execute as it was unrefed and refed again.
|
||||||
|
// It also confirms that unref/ref are chainable.
|
||||||
|
setImmediate(common.mustCall(firstStep)).ref().unref().unref().ref();
|
||||||
|
|
||||||
|
function firstStep() {
|
||||||
|
const countdown =
|
||||||
|
new Countdown(2, common.mustCall(() => setImmediate(secondStep)));
|
||||||
|
// Unrefed setImmediate executes if it was unrefed but something else keeps
|
||||||
|
// the loop open
|
||||||
|
setImmediate(() => countdown.dec()).unref();
|
||||||
|
setTimeout(() => countdown.dec(), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
function secondStep() {
|
||||||
|
// clearImmediate works just fine with unref'd immediates
|
||||||
|
const immA = setImmediate(() => {
|
||||||
|
clearImmediate(immA);
|
||||||
|
clearImmediate(immB);
|
||||||
|
// this should not keep the event loop open indefinitely
|
||||||
|
// or do anything else weird
|
||||||
|
immA.ref();
|
||||||
|
immB.ref();
|
||||||
|
}).unref();
|
||||||
|
const immB = setImmediate(common.mustNotCall()).unref();
|
||||||
|
setImmediate(common.mustCall(finalStep));
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalStep() {
|
||||||
|
// This immediate should not execute as it was unrefed
|
||||||
|
// and nothing else is keeping the event loop alive
|
||||||
|
setImmediate(common.mustNotCall()).unref();
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user