Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit 22533c0

Browse files
committed
timers: fix setInterval() assert
Test case: var t = setInterval(function() {}, 1); process.nextTick(t.unref); Output: Assertion failed: (args.Holder()->InternalFieldCount() > 0), function Unref, file ../src/handle_wrap.cc, line 78. setInterval() returns a binding layer object. Make it stop doing that, wrap the raw process.binding('timer_wrap').Timer object in a Timeout object. Fixes #4261.
1 parent 1deeab2 commit 22533c0

File tree

2 files changed

+32
-12
lines changed

2 files changed

+32
-12
lines changed

lib/timers.js

+25-12
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ exports.setTimeout = function(callback, after) {
222222
exports.clearTimeout = function(timer) {
223223
if (timer && (timer.ontimeout || timer._onTimeout)) {
224224
timer.ontimeout = timer._onTimeout = null;
225-
if (timer instanceof Timer || timer instanceof Timeout) {
225+
if (timer instanceof Timeout) {
226226
timer.close(); // for after === 0
227227
} else {
228228
exports.unenroll(timer);
@@ -232,39 +232,52 @@ exports.clearTimeout = function(timer) {
232232

233233

234234
exports.setInterval = function(callback, repeat) {
235-
var timer = new Timer();
236-
237-
if (process.domain) timer.domain = process.domain;
238-
239235
repeat *= 1; // coalesce to number or NaN
240236

241237
if (!(repeat >= 1 && repeat <= TIMEOUT_MAX)) {
242238
repeat = 1; // schedule on next tick, follows browser behaviour
243239
}
244240

241+
var timer = new Timeout(repeat);
245242
var args = Array.prototype.slice.call(arguments, 2);
246-
timer.ontimeout = function() {
247-
callback.apply(timer, args);
248-
}
243+
timer._onTimeout = wrapper;
244+
timer._repeat = true;
245+
246+
if (process.domain) timer.domain = process.domain;
247+
exports.active(timer);
249248

250-
timer.start(repeat, repeat);
251249
return timer;
250+
251+
function wrapper() {
252+
callback.apply(this, args);
253+
// If callback called clearInterval().
254+
if (timer._repeat === false) return;
255+
// If timer is unref'd (or was - it's permanently removed from the list.)
256+
if (this._handle) {
257+
this._handle.start(repeat, 0);
258+
} else {
259+
timer._idleTimeout = repeat;
260+
exports.active(timer);
261+
}
262+
}
252263
};
253264

254265

255266
exports.clearInterval = function(timer) {
256-
if (timer instanceof Timer) {
257-
timer.ontimeout = null;
258-
timer.close();
267+
if (timer && timer._repeat) {
268+
timer._repeat = false;
269+
clearTimeout(timer);
259270
}
260271
};
261272

273+
262274
var Timeout = function(after) {
263275
this._idleTimeout = after;
264276
this._idlePrev = this;
265277
this._idleNext = this;
266278
this._idleStart = null;
267279
this._onTimeout = null;
280+
this._repeat = false;
268281
};
269282

270283
Timeout.prototype.unref = function() {

test/simple/test-timers-unref.js

+7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ check_unref = setInterval(function() {
5454
checks += 1;
5555
}, 100);
5656

57+
// Should not assert on args.Holder()->InternalFieldCount() > 0. See #4261.
58+
(function() {
59+
var t = setInterval(function() {}, 1);
60+
process.nextTick(t.unref.bind({}));
61+
process.nextTick(t.unref.bind(t));
62+
})();
63+
5764
process.on('exit', function() {
5865
assert.strictEqual(interval_fired, false, 'Interval should not fire');
5966
assert.strictEqual(timeout_fired, false, 'Timeout should not fire');

0 commit comments

Comments
 (0)