-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
timers: enable timers to be used as primitives #19683
Conversation
For web compatibility this allows timers to be stored as the key of an Object property and be passed back to corresponding method to clear the timer.
lib/timers.js
Outdated
|
||
// Schedule or re-schedule a timer. | ||
// The item must have been enroll()'d first. | ||
const active = exports.active = function(item) { | ||
KNOWN_TIMERS[item[async_id_symbol]] = item; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do the caching in [@@toPrimitive]()
? I doubt this is a very frequently used feature, so it might be good if we could avoid the storage overhead for the common case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems fine to change that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure how we could easily avoid the delete
though when clearing without doing some funny stuff. the cache should swap into dictionary mode really fast though.
Did you check benchmarks? |
@mscdex I did not. I would be very surprised if it has any significant impact. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM if the benchmarks are happy
This is semver-minor, right? Should we document this?
lib/timers.js
Outdated
@@ -497,6 +505,12 @@ exports.setInterval = function(callback, repeat, arg1, arg2, arg3) { | |||
}; | |||
|
|||
exports.clearInterval = function(timer) { | |||
if (typeof timer === 'number' || typeof timer === 'string') { | |||
if (timer in KNOWN_TIMERS) { | |||
clearInterval(KNOWN_TIMERS[timer]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we may want to avoid accessing global.clearInterval
here … maybe doing something like we do for clearTimeout
, i.e. const clearInterval = exports.clearInterval = …
is a good idea here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yes, that would be a bug if that happened. I'm not 100% sure on why these functions are not named looking at them...
I ran |
@bmeck You may already know this, but in case not: If you have a binary compiled from master called node benchmark/compare.js --old node-old --new node-new timers > my-benchmark.csv ...followed by... cat my-benchmark.csv | Rscript compare.R See https://github.com/nodejs/node/blob/master/doc/guides/writing-and-running-benchmarks.md#comparing-nodejs-versions for more information. |
(Edited above to include correct command...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doc/api/timers.md
Outdated
@@ -90,6 +90,12 @@ of the Node.js application. | |||
|
|||
Returns a reference to the `Timeout`. | |||
|
|||
### timeout[Symbol.toPrimitive]() | |||
|
|||
When coercing a `Timeout` to a primitive, a primitive will be generated that can be used with the appropriate function that can be used to clear the `Timeout`. This allows enhanced compatibility with browser `setTimeout`, and `setInterval` implementations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please wrap at 80 characters :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
lib/timers.js
Outdated
const clearInterval = exports.clearInterval = function(timer) { | ||
if (typeof timer === 'number' || typeof timer === 'string') { | ||
if (timer in KNOWN_TIMERS) { | ||
clearInterval(KNOWN_TIMERS[timer]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would timer = KNOWN_TIMERS[timer]
work? This would avoid a recursion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
doc/api/timers.md
Outdated
@@ -90,6 +90,12 @@ of the Node.js application. | |||
|
|||
Returns a reference to the `Timeout`. | |||
|
|||
### timeout[Symbol.toPrimitive]() | |||
|
|||
When coercing a `Timeout` to a primitive, a primitive will be generated that can be used with the appropriate function that can be used to clear the `Timeout`. This allows enhanced compatibility with browser `setTimeout`, and `setInterval` implementations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap at 80 cols.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
doc/api/timers.md
Outdated
|
||
When coercing a `Timeout` to a primitive, a primitive will be generated that can be used with the appropriate function that can be used to clear the `Timeout`. This allows enhanced compatibility with browser `setTimeout`, and `setInterval` implementations. | ||
|
||
Returns a `number`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put
* Returns: {integer}
right below the heading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
lib/timers.js
Outdated
@@ -145,6 +145,8 @@ const kRefed = Symbol('refed'); | |||
const refedLists = Object.create(null); | |||
const unrefedLists = Object.create(null); | |||
|
|||
const KNOWN_TIMERS = Object.create(null); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extraneous line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@@ -521,6 +535,11 @@ function unrefdHandle(timer, now) { | |||
return true; | |||
} | |||
|
|||
Timeout.prototype[Symbol.toPrimitive] = function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why @@toPrimitive
rather than valueOf
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see https://codepen.io/bradleymeck/pen/eMMpdR?editors=0012 , .valueOf is not safe for using as a key. toPrimitive handles both string and number coercion.
There's one significant drop showing in the benchmark CI:
I guess we have to make a call deciding whether we're okay with that? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 if we're going to see a performance regression like that
It is probably from the uncondition |
lib/timers.js
Outdated
delete KNOWN_TIMERS[this[async_id_symbol]]; | ||
this.close = $close; | ||
this.close(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't pass linting due to missing semicolon. Also, can the function be factored out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed / it cannot be factored out since it closes over $close
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$close
is effectively always equal to Timeout.prototype.close
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since it is public and mutable, that is not certain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bmeck I think it’s fine to just ignore that. You could still run into it anyway, when .close
is changed after the [@@toPrimitive]()
call…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if they replace it they are probably wrapping it, I'm already ignoring all the ways to avoid getter/setters but don't feel comfortable with completely removing .close
this seems like one of the metrics people could be profiling handle lifetimes.
lib/timers.js
Outdated
@@ -496,7 +504,15 @@ exports.setInterval = function(callback, repeat, arg1, arg2, arg3) { | |||
return timeout; | |||
}; | |||
|
|||
exports.clearInterval = function(timer) { | |||
const clearInterval = exports.clearInterval = function(timer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the addition of clearInterval
variable? Doesn't seem used anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was added in e32b969 as a bug fix to previous iteration
lib/timers.js
Outdated
if (timer in KNOWN_TIMERS) { | ||
timer = KNOWN_TIMERS[timer]; | ||
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails linting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@mscdex with the most change can you recheck your perf concerns? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So - this does seem more useful than I thought. (With a request, like a session, you might need to store it in e.g. Redis.)
I'm going to guess the perf drop is due to some polymorphism? Maybe we should run turbolizer against it. I'm not yet going to unblock this yet but I wonder if the perf hit is really that much overall.
We should stress HTTP and see if cancel ticks go up significantly, I suppose.
|
||
assert.strictEqual(Number.isNaN(+timeout1), false); | ||
assert.strictEqual(Number.isNaN(+timeout2), false); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you check that +timeout1 === timeout1[Symbol.toPrimitive]()
?
@bmeck I have a suggestion to make, if I may: We can still get the functionality here while delaying perf concerns in the following way:
This has the following advantages:
If you're still interested, I would gladly review that kind of update. If not, mind if I take the existing commits and adjust? |
Im on vacation but would object to those suggested differences.
…On Thu, Apr 26, 2018, 12:43 AM Jeremiah Senkpiel ***@***.***> wrote:
@bmeck <https://github.com/bmeck> I have a suggestion to make, if I may:
We can still get the functionality here while delaying perf concerns in
the following way:
- Keep the toPrimitive
- Remove the id detection / polymorphism from existing timers methods
- Add a getTimerById (or similar) to get a timer object by the
primitive id
This has the following advantages:
- Allows the funcionality
- Avoids polymorphism & existing perf concerns
- Allows timers to be refreshed from id (see #20261 (comment)
<#20261 (comment)>)
If you're still interested, I would gladly review that kind of update. If
not, mind if I take the existing commits and adjust?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#19683 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAOUo9g2Dh_7r20OAuAmEcJDcQNZO8aUks5tsJmtgaJpZM4TA7q->
.
|
@bmeck if you still want to pursue this, instead of doing the more expensive |
I'd love to see how @apapirovski's suggestions work out. |
@bmeck Do you want to continue on this or can I take over basing on top of your commit (with credit preserved)? I like the idea but I think I've got some solid ideas on how to make it faster, as expressed above. |
@apapirovski please take it over, your work is quite exciting :) |
@bmeck thanks! I'll pull in your commit and build on top of it. 👍 I'll preserve the credit when/if I open a PR, depending on how the perf works out. |
I'll close this out since there's a more up-to-date / rebased version. Hopefully we can sort out the remaining issues to land it. |
For web compatibility this allows timers to be stored as the key
of an Object property and be passed back to corresponding method to
clear the timer.
Note that this is using the
async_id_symbol
to get a numeric ID to correspond with timers.Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes