timers: reuse timer in setTimeout().unref()

Instead of creating new timer - reuse the timer from the freelist. This
won't make the freelist timer active for the duration of `uv_close()`,
and will let the event-loop exit properly.

Fix: #1264
PR-URL: https://github.com/nodejs/node/pull/3407
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
This commit is contained in:
Fedor Indutny 2015-10-16 15:34:15 -04:00
parent 57f99a9479
commit 3eecdf9f14
2 changed files with 42 additions and 7 deletions

View File

@ -119,16 +119,27 @@ function listOnTimeoutNT(list) {
}
const unenroll = exports.unenroll = function(item) {
function reuse(item) {
L.remove(item);
var list = lists[item._idleTimeout];
// if empty then stop the watcher
debug('unenroll');
// if empty - reuse the watcher
if (list && L.isEmpty(list)) {
debug('reuse hit');
list.stop();
delete lists[item._idleTimeout];
return list;
}
return null;
}
const unenroll = exports.unenroll = function(item) {
var list = reuse(item);
if (list) {
debug('unenroll: list empty');
list.close();
delete lists[item._idleTimeout];
}
// if active is called later, then we want to make sure not to insert again
item._idleTimeout = -1;
@ -312,12 +323,16 @@ Timeout.prototype.unref = function() {
if (!this._idleStart) this._idleStart = now;
var delay = this._idleStart + this._idleTimeout - now;
if (delay < 0) delay = 0;
exports.unenroll(this);
// Prevent running cb again when unref() is called during the same cb
if (this._called && !this._repeat) return;
if (this._called && !this._repeat) {
exports.unenroll(this);
return;
}
this._handle = new Timer();
var handle = reuse(this);
this._handle = handle || new Timer();
this._handle.owner = this;
this._handle[kOnTimeout] = unrefdHandle;
this._handle.start(delay, 0);

View File

@ -0,0 +1,20 @@
'use strict';
require('../common');
const assert = require('assert');
var once = 0;
process.on('beforeExit', () => {
if (once > 1)
throw new RangeError('beforeExit should only have been called once!');
setTimeout(() => {}, 1).unref();
once++;
});
process.on('exit', (code) => {
if (code !== 0) return;
assert.strictEqual(once, 1);
});