| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 7a8c3e0 commit 403d7ee
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -477,6 +477,39 @@ exports.clearImmediate = function(immediate) { | |||
| 477 | 477 | ||
| 478 | 478 | var unrefList, unrefTimer; | |
| 479 | 479 | ||
| 480 | + function _makeTimerTimeout(timer) { | ||
| 481 | + var domain = timer.domain; | ||
| 482 | + var msecs = timer._idleTimeout; | ||
| 483 | + | ||
| 484 | + // Timer has been unenrolled by another timer that fired at the same time, | ||
| 485 | + // so don't make it timeout. | ||
| 486 | + if (!msecs || msecs < 0) | ||
| 487 | + return; | ||
| 488 | + | ||
| 489 | + if (!timer._onTimeout) | ||
| 490 | + return; | ||
| 491 | + | ||
| 492 | + if (domain && domain._disposed) | ||
| 493 | + return; | ||
| 494 | + | ||
| 495 | + try { | ||
| 496 | + var threw = true; | ||
| 497 | + | ||
| 498 | + if (domain) domain.enter(); | ||
| 499 | + | ||
| 500 | + debug('unreftimer firing timeout'); | ||
| 501 | + L.remove(timer); | ||
| 502 | + timer._called = true; | ||
| 503 | + timer._onTimeout(); | ||
| 504 | + | ||
| 505 | + threw = false; | ||
| 506 | + | ||
| 507 | + if (domain) | ||
| 508 | + domain.exit(); | ||
| 509 | + } finally { | ||
| 510 | + if (threw) process.nextTick(unrefTimeout); | ||
| 511 | + } | ||
| 512 | + } | ||
| 480 | 513 | ||
| 481 | 514 | function unrefTimeout() { | |
| 482 | 515 | var now = Timer.now(); | |
@@ -487,7 +520,7 @@ function unrefTimeout() { | |||
| 487 | 520 | var nextTimeoutTime; | |
| 488 | 521 | var nextTimeoutDuration; | |
| 489 | 522 | var minNextTimeoutTime; | |
| 490 | - var itemToDelete; | ||
| 523 | + var timersToTimeout = []; | ||
| 491 | 524 | ||
| 492 | 525 | // The actual timer fired and has not yet been rearmed, | |
| 493 | 526 | // let's consider its next firing time is invalid for now. | |
@@ -518,45 +551,21 @@ function unrefTimeout() { | |||
| 518 | 551 | // we scanned through the whole list. | |
| 519 | 552 | minNextTimeoutTime = nextTimeoutTime; | |
| 520 | 553 | } | |
| 521 | - | ||
| 522 | - // This timer hasn't expired yet, skipping | ||
| 523 | - cur = cur._idlePrev; | ||
| 524 | - continue; | ||
| 554 | + } else { | ||
| 555 | + // We found a timer that expired. Do not call its _onTimeout callback | ||
| 556 | + // right now, as it could mutate any item of the list (including itself). | ||
| 557 | + // Instead, add it to another list that will be processed once the list | ||
| 558 | + // of current timers has been fully traversed. | ||
| 559 | + timersToTimeout.push(cur); | ||
| 525 | 560 | } | |
| 526 | 561 | ||
| 527 | - // We found a timer that expired | ||
| 528 | - var domain = cur.domain; | ||
| 529 | - | ||
| 530 | - if (!cur._onTimeout) continue; | ||
| 531 | - | ||
| 532 | - if (domain && domain._disposed) | ||
| 533 | - continue; | ||
| 534 | - | ||
| 535 | - try { | ||
| 536 | - var threw = true; | ||
| 537 | - | ||
| 538 | - if (domain) domain.enter(); | ||
| 539 | - | ||
| 540 | - itemToDelete = cur; | ||
| 541 | - // Move to the previous item before calling the _onTimeout callback, | ||
| 542 | - // as it can mutate the list. | ||
| 543 | - cur = cur._idlePrev; | ||
| 544 | - | ||
| 545 | - // Remove the timeout from the list because it expired. | ||
| 546 | - L.remove(itemToDelete); | ||
| 547 | - | ||
| 548 | - debug('unreftimer firing timeout'); | ||
| 549 | - itemToDelete._called = true; | ||
| 550 | - itemToDelete._onTimeout(); | ||
| 562 | + cur = cur._idlePrev; | ||
| 563 | + } | ||
| 551 | 564 | ||
| 552 | - threw = false; | ||
| 565 | + var nbTimersToTimeout = timersToTimeout.length; | ||
| 566 | + for (var timerIdx = 0; timerIdx < nbTimersToTimeout; ++timerIdx) | ||
| 567 | + _makeTimerTimeout(timersToTimeout[timerIdx]); | ||
| 553 | 568 | ||
| 554 | - if (domain) | ||
| 555 | - domain.exit(); | ||
| 556 | - } finally { | ||
| 557 | - if (threw) process.nextTick(unrefTimeout); | ||
| 558 | - } | ||
| 559 | - } | ||
| 560 | 569 | ||
| 561 | 570 | // Rearm the actual timer with the timeout delay | |
| 562 | 571 | // of the earliest timeout found. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,50 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + /* | ||
| 4 | + * This test is a regression test for joyent/node#8897. | ||
| 5 | + */ | ||
| 6 | + | ||
| 7 | + const common = require('../common'); | ||
| 8 | + const assert = require('assert'); | ||
| 9 | + const net = require('net'); | ||
| 10 | + | ||
| 11 | + const clients = []; | ||
| 12 | + | ||
| 13 | + const server = net.createServer(function onClient(client) { | ||
| 14 | + clients.push(client); | ||
| 15 | + | ||
| 16 | + if (clients.length === 2) { | ||
| 17 | + /* | ||
| 18 | + * Enroll two timers, and make the one supposed to fire first | ||
| 19 | + * unenroll the other one supposed to fire later. This mutates | ||
| 20 | + * the list of unref timers when traversing it, and exposes the | ||
| 21 | + * original issue in joyent/node#8897. | ||
| 22 | + */ | ||
| 23 | + clients[0].setTimeout(1, function onTimeout() { | ||
| 24 | + clients[1].setTimeout(0); | ||
| 25 | + clients[0].end(); | ||
| 26 | + clients[1].end(); | ||
| 27 | + }); | ||
| 28 | + | ||
| 29 | + // Use a delay that is higher than the lowest timer resolution accross all | ||
| 30 | + // supported platforms, so that the two timers don't fire at the same time. | ||
| 31 | + clients[1].setTimeout(50); | ||
| 32 | + } | ||
| 33 | + }); | ||
| 34 | + | ||
| 35 | + server.listen(common.PORT, common.localhostIPv4, function() { | ||
| 36 | + var nbClientsEnded = 0; | ||
| 37 | + | ||
| 38 | + function addEndedClient(client) { | ||
| 39 | + ++nbClientsEnded; | ||
| 40 | + if (nbClientsEnded === 2) { | ||
| 41 | + server.close(); | ||
| 42 | + } | ||
| 43 | + }; | ||
| 44 | + | ||
| 45 | + const client1 = net.connect({ port: common.PORT }); | ||
| 46 | + client1.on('end', addEndedClient); | ||
| 47 | + | ||
| 48 | + const client2 = net.connect({ port: common.PORT }); | ||
| 49 | + client2.on('end', addEndedClient); | ||
| 50 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,41 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + /* | ||
| 4 | + * The goal of this test is to make sure that, after the regression introduced | ||
| 5 | + * by 934bfe23a16556d05bfb1844ef4d53e8c9887c3d, the fix preserves the following | ||
| 6 | + * behavior of unref timers: if two timers are scheduled to fire at the same | ||
| 7 | + * time, if one unenrolls the other one in its _onTimeout callback, the other | ||
| 8 | + * one will *not* fire. | ||
| 9 | + * | ||
| 10 | + * This behavior is a private implementation detail and should not be | ||
| 11 | + * considered public interface. | ||
| 12 | + */ | ||
| 13 | + const common = require('../common'); | ||
| 14 | + const timers = require('timers'); | ||
| 15 | + const assert = require('assert'); | ||
| 16 | + | ||
| 17 | + var nbTimersFired = 0; | ||
| 18 | + | ||
| 19 | + const foo = { | ||
| 20 | + _onTimeout: function() { | ||
| 21 | + ++nbTimersFired; | ||
| 22 | + timers.unenroll(bar); | ||
| 23 | + } | ||
| 24 | + }; | ||
| 25 | + | ||
| 26 | + const bar = { | ||
| 27 | + _onTimeout: function() { | ||
| 28 | + ++nbTimersFired; | ||
| 29 | + timers.unenroll(foo); | ||
| 30 | + } | ||
| 31 | + }; | ||
| 32 | + | ||
| 33 | + timers.enroll(bar, 1); | ||
| 34 | + timers._unrefActive(bar); | ||
| 35 | + | ||
| 36 | + timers.enroll(foo, 1); | ||
| 37 | + timers._unrefActive(foo); | ||
| 38 | + | ||
| 39 | + setTimeout(function() { | ||
| 40 | + assert.notEqual(nbTimersFired, 2); | ||
| 41 | + }, 20); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,33 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + /* | ||
| 4 | + * This test is a regression test for joyent/node#8897. | ||
| 5 | + * | ||
| 6 | + * It tests some private implementation details that should not be | ||
| 7 | + * considered public interface. | ||
| 8 | + */ | ||
| 9 | + const common = require('../common'); | ||
| 10 | + const assert = require('assert'); | ||
| 11 | + const timers = require('timers'); | ||
| 12 | + | ||
| 13 | + const foo = { | ||
| 14 | + _onTimeout: assert.fail | ||
| 15 | + }; | ||
| 16 | + | ||
| 17 | + const bar = { | ||
| 18 | + _onTimeout: common.mustCall(function() { | ||
| 19 | + timers.unenroll(foo); | ||
| 20 | + }) | ||
| 21 | + }; | ||
| 22 | + | ||
| 23 | + // We use timers with expiration times that are sufficiently apart to make | ||
| 24 | + // sure that they're not fired at the same time on platforms where the timer | ||
| 25 | + // resolution is a bit coarse (e.g Windows with a default resolution of ~15ms). | ||
| 26 | + timers.enroll(bar, 1); | ||
| 27 | + timers._unrefActive(bar); | ||
| 28 | + | ||
| 29 | + timers.enroll(foo, 50); | ||
| 30 | + timers._unrefActive(foo); | ||
| 31 | + | ||
| 32 | + // Keep the process open. | ||
| 33 | + setTimeout(function() {}, 100); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments