timers: enable timers to be used as primitives#19683
timers: enable timers to be used as primitives#19683bmeck wants to merge 7 commits intonodejs:masterfrom
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.
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.
seems fine to change that.
There was a problem hiding this comment.
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. |
addaleax
left a comment
There was a problem hiding this comment.
LGTM if the benchmarks are happy
This is semver-minor, right? Should we document this?
lib/timers.js
Outdated
| 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.
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.
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.RSee 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...) |
doc/api/timers.md
Outdated
|
|
||
| ### 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.
Nit: Please wrap at 80 characters :)
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.
Would timer = KNOWN_TIMERS[timer] work? This would avoid a recursion.
doc/api/timers.md
Outdated
|
|
||
| ### 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. |
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.
Put
* Returns: {integer}right below the heading.
| const unrefedLists = Object.create(null); | ||
|
|
||
| const KNOWN_TIMERS = Object.create(null); | ||
|
|
| return true; | ||
| } | ||
|
|
||
| Timeout.prototype[Symbol.toPrimitive] = function() { |
There was a problem hiding this comment.
Why @@toPrimitive rather than valueOf?
There was a problem hiding this comment.
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? |
mscdex
left a comment
There was a problem hiding this comment.
-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.
This won't pass linting due to missing semicolon. Also, can the function be factored out?
There was a problem hiding this comment.
fixed / it cannot be factored out since it closes over $close.
There was a problem hiding this comment.
$close is effectively always equal to Timeout.prototype.close, right?
There was a problem hiding this comment.
since it is public and mutable, that is not certain.
There was a problem hiding this comment.
@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.
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
| }; | ||
|
|
||
| exports.clearInterval = function(timer) { | ||
| const clearInterval = exports.clearInterval = function(timer) { |
There was a problem hiding this comment.
Why the addition of clearInterval variable? Doesn't seem used anywhere.
There was a problem hiding this comment.
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 { |
|
@mscdex with the most change can you recheck your perf concerns? |
Fishrock123
left a comment
There was a problem hiding this comment.
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.
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_symbolto get a numeric ID to correspond with timers.Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passes