Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timer: make sure timer is cleared #4303

Closed
wants to merge 1 commit into from
Closed

Conversation

zhangzifa
Copy link
Contributor

When close is called in timer(set by setInterval) callback, active and
listOnTimeout will be called again which should not be called anymore.

When unenroll is called in timer(set by setInterval) callback, the timer
still works.

With this fix, the timer behaves the same as clearTimeout/clearInterval
is called in callback.
  • The timer (set by setInterval) should be disarmed when close/unenroll is called in callback.
  • The current implementation:

-- when close called in callback, an extra active is called.

> var i = setInterval(function(){console.log('1111'); i.close();}, 1111);
undefined
> TIMER 7917: timeout callback 15
TIMER 7917: now: 12423
TIMER 7917: 15 list empty
TIMER 7917: timeout callback 1111
TIMER 7917: now: 13514
1111
TIMER 7917: unenroll
TIMER 7917: unenroll: list empty
TIMER 7917: 1111 list empty
TIMER 7917: timeout callback 1111     //this callback execution is unnecessary.
TIMER 7917: now: 14627
TIMER 7917: 1111 list empty

-- when unenroll called, the timer is still working

> var T = require('timers');
...
> var i = setInterval(function(){console.log('1111'); T.unenroll(i);}, 1111);
> TIMER 7868: timeout callback 15
TIMER 7868: now: 30631
TIMER 7868: 15 list empty
TIMER 7868: timeout callback 1111
TIMER 7868: now: 31728
1111
TIMER 7868: unenroll
TIMER 7868: unenroll: list empty
TIMER 7868: 1111 list empty
TIMER 7868: timeout callback 1111
TIMER 7868: now: 32840
1111
TIMER 7868: unenroll
TIMER 7868: 1111 list empty
TIMER 7868: timeout callback 1111
TIMER 7868: now: 33953
1111
TIMER 7868: unenroll
TIMER 7868: 1111 list empty

(^C again to quit)
> TIMER 7868: timeout callback 1111
TIMER 7868: now: 35065
1111
TIMER 7868: unenroll
TIMER 7868: 1111 list empty

@mscdex mscdex added the timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. label Dec 16, 2015
@mscdex
Copy link
Contributor

mscdex commented Dec 16, 2015

Can you add tests?

@zhangzifa
Copy link
Contributor Author

Ok, I will add test. And before that, i should fix another test. :-).
I did not notice the change between LTS and master for unref.

@zhangzifa
Copy link
Contributor Author

tests added, @mscdex

THANKS

@zhangzifa zhangzifa changed the title disarm timer when close/unenroll called within callback disarmed timer by close/unenroll in callback should not fire anymore Dec 16, 2015
@mscdex
Copy link
Contributor

mscdex commented Dec 16, 2015

I think it'd be better to forgo the use of child processes and just execute the timer tests directly.

Also, instead of using counters, you might use common.mustCall(fn, numOfExpectedCalls) to achieve the same effect with less code (e.g. setInterval(common.mustCall(function() { .... }), 11) to expect 1 call). Then inside your setTimeout() callback where you were checking the counters, just put a process.exit(0);.

@zhangzifa
Copy link
Contributor Author

But mustCall can't check how many times an internal function executed.

This fix is to make sure listOnTimeout(the same as exports.active) should not be triggered again after close/unenroll in setInterval callback.

lib/timers.js Outdated
@@ -264,7 +264,7 @@ exports.setInterval = function(callback, repeat) {
timer._repeat.call(this);

// Timer might be closed - no point in restarting it
if (!timer._repeat)
if ((!timer._repeat) || ((timer._idleTimeout === -1) && (!this._handle)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: there's an extra set or parenthesis here.

Also I think the check should prioritize timer._idleTimeout === -1, and only check ._repeat for legacy purposes.

I'm not entirely sure checking the handle is necessary. (Aside: Timer#unref() should probably check ._idleTimeout === -1, I think.)

@zhangzifa
Copy link
Contributor Author

Maybe the legacy logic to prioritize to check timer._repeat is result of calling clearTimeout/clearInterval more often than close/unenroll.

Check handle is necessary for unref() timer. (test case: test/parallel/test-timers-unrefd-interval-still-fires.js)

@Fishrock123
Copy link
Member

Maybe the legacy logic to prioritize to check timer._repeat is result of calling clearTimeout/clearInterval more often than close/unenroll.

@zhangzifa Right, neither close() no unenroll() are documented, and as such it is not suggested to use them.

Checking timer._idleTimeout === -1 however, is more in line with how the rest of the code works.

@zhangzifa
Copy link
Contributor Author

@Fishrock123
active(), close() and unenroll() should be removed, in my opinion. However, many modules call them.
https://github.com/search?utf8=%E2%9C%93&q=timers.unenroll&type=Code&ref=searchresults

I agree with you that check timer._idleTimeout === -1 is enough.

Someone optimized master branch and removed reuse(), it seems that this optimization should be removed and restore 'reuse()'.

@Fishrock123
Copy link
Member

@zhangzifa That search has a lot of false positives, GitHub search is not very good for that.

Someone optimized master branch and removed reuse(), it seems that this optimization should be removed and restore 'reuse()'.

Pardon? https://github.com/nodejs/node/blob/master/lib/timers.js#L122-L139

@zhangzifa
Copy link
Contributor Author

@Fishrock123 Sorry, my local copy is out of date.

My copy is before this PR.#3407

lib/timers.js Outdated
@@ -276,7 +276,7 @@ exports.setInterval = function(callback, repeat) {
timer._repeat();

// Timer might be closed - no point in restarting it
if (!timer._repeat)
if ((!timer._repeat) || ((timer._idleTimeout === -1) && (!this._handle)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's two extra sets or parenthesis here.

Also, could you make the change to prioritize timer._idleTimeout === -1? Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking timer._idleTimeout === -1 is enough, if _idleTimeout should be set to -1 in Timeout.prototype.close when this._handle is true.

@zhangzifa zhangzifa changed the title disarmed timer by close/unenroll in callback should not fire anymore timer: disarmed timer in callback should not fire anymore Dec 22, 2015
@zhangzifa zhangzifa changed the title timer: disarmed timer in callback should not fire anymore timer: make sure timer is cleared Dec 22, 2015
@Fishrock123
Copy link
Member

timer._idleTimeout === -1 should always be true when a timer is closed/canceled/unenrolled/whatever to maintain consistency.

@zhangzifa
Copy link
Contributor Author

Yes, the old implementation missed to assign timer._idleTimeout as -1 when close an unrefd timer.

@Trott
Copy link
Member

Trott commented Dec 23, 2015

One of the filenames has a typo in it: callbak -> callback

nbIntervalFired3++;
T.unenroll(timer3);
}, 13);
setTimeout(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we can't just do this?:

  • Move this file to /test/parallel
  • Add const common = require('../common'); to the top.
  • Surround the arrow function here with common.mustCall()
  • Get rid of test-timers-disarmed-in-callbak-not-fire-anymore.js

Never mind!

listOnTimeout will be called again which should not be called anymore.

When unenroll is called in timer(set by setInterval) callback, the timer
still works.

With this fix, the timer behaves the same as clearTimeout/clearInterval
is called in callback.
@rvagg
Copy link
Member

rvagg commented Jan 4, 2016

Hey @zhangzifa, I believe this would be your first commit to core if it gets in, welcome to the project! As I think you're finding out here, messing around with timers code can be tricky and involve a lot of edge-cases that are not necessarily covered by tests. Expect this PR to continue to be picked over by other collaborators before anything's ready to be merged. It's also possible that this might go another way, such as just deprecating close() and unenroll() (I think I'd prefer this option). Please persist with us while we try and get this one right!

@zhangzifa
Copy link
Contributor Author

@rvagg Happy new year! Yes, it's my first PR to this project. I'm puzzled why close() and unenroll() were exported, which i don't think necessary. Anyhow, before close() and unenroll() are removed, I think it's better to fix this fault as it is a logic fault. @Fishrock123 is refactoring timer (#4007), while this fault is still there. I am happy if another PR can fix this with a better implementation. Or @rvagg could you help involve someone who knows timer module well to review this?

@Fishrock123
Copy link
Member

  1. Timeout#close() was add here: cd6122e (presumably reviewed by Bert)
  2. timers.unenroll() predates probably 90+% of the current codebase. It was added before the browser-like *Timeout APIs. Here is where it ("timers"!) was first added to net.js ~6 years ago: ca862d7#diff-e6ef024c3775d787c38487a6309e491dR37

I wonder somewhat how useful the {un}enroll() / active() API possibly is. Is anyone actually using it for the means of efficiency like we do in core?

I figure we should probably patch this anyways, deprecating the latter, at least, would be quite an extended process I think.

@Fishrock123
Copy link
Member

Note that, before ~March 2015, we did not have /internal/ modules.

@zhangzifa
Copy link
Contributor Author

Hi @Fishrock123. Incredibly someone is using {un}enroll() / active() API.
This is the code from which I noticed the existence of them: https://github.com/felixge/node-mysql/blob/master/lib/protocol/Protocol.js . I agree with you how to handle this.

@Fishrock123
Copy link
Member

Interesting. Perhaps we should still look at Timeout#close()'s existence.

@zhangzifa
Copy link
Contributor Author

It seems not easy to find in which module Timeout#close() is used. I tried but failed.

@jasnell
Copy link
Member

jasnell commented Mar 23, 2016

What's the status on this one?

@imyller
Copy link
Member

imyller commented Sep 24, 2016

CI against current master: https://ci.nodejs.org/job/node-test-pull-request/4260/

@jasnell
Copy link
Member

jasnell commented Feb 28, 2017

@Fishrock123 ... any thoughts on this one?

@jasnell jasnell added the stalled Issues and PRs that are stalled. label Mar 1, 2017
@Fishrock123
Copy link
Member

The tests appear to pass on master. I am quite sure I fixed this in 3f1e38c.

@Fishrock123
Copy link
Member

The tests here should also not be necessary, I think.

@Fishrock123
Copy link
Member

@zhangzifa Thanks anyways for reporting this! Sorry it took so long. 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stalled Issues and PRs that are stalled. timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants