FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

timers: enable timers to be used as primitives by bmeck · Pull Request #19683 · nodejs/node · GitHub

/ node Public

timers: enable timers to be used as primitives - #19683

Closed
bmeck wants to merge 7 commits into
nodejs:masterfrom
bmeck:timeout-toPrimitive
Closed

timers: enable timers to be used as primitives#19683
bmeck wants to merge 7 commits into
nodejs:masterfrom
bmeck:timeout-toPrimitive

Conversation

bmeck commented Mar 29, 2018
edited
Loading

Copy link
Copy Markdown
Member

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), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

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.
nodejs-github-bot added the timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. label Mar 29, 2018
bmeck requested review from addaleax and trevnorris March 29, 2018 20:25
Comment thread 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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

seems fine to change that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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.

mscdex commented Mar 29, 2018

Copy link
Copy Markdown
Contributor

Did you check benchmarks?

bmeck commented Mar 29, 2018

Copy link
Copy Markdown
Member Author

@mscdex I did not. I would be very surprised if it has any significant impact.

addaleax left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

LGTM if the benchmarks are happy

This is semver-minor, right? Should we document this?

Comment thread lib/timers.js Outdated
exports.clearInterval = function(timer) {
if (typeof timer === 'number' || typeof timer === 'string') {
if (timer in KNOWN_TIMERS) {
clearInterval(KNOWN_TIMERS[timer]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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...

addaleax added the semver-minor PRs that contain new features and should be released in the next minor version. label Mar 29, 2018

bmeck commented Mar 29, 2018

Copy link
Copy Markdown
Member Author

I ran ./node benchmark/run.js timers but am not seeing anything changing within margin of error.

Trott commented Mar 29, 2018
edited
Loading

Copy link
Copy Markdown
Member

@bmeck You may already know this, but in case not: If you have a binary compiled from master called node-old and a binary compiled with your changes called node-new, you can have it do the statistical computation for you with something like:

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.

Trott commented Mar 29, 2018

Copy link
Copy Markdown
Member

(Edited above to include correct command...)

addaleax left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Comment thread 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Nit: Please wrap at 80 characters :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

fixed

Comment thread 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]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Would timer = KNOWN_TIMERS[timer] work? This would avoid a recursion.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

fixed

Comment thread 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Wrap at 80 cols.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

fixed

Comment thread 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`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Put

* Returns: {integer}

right below the heading.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

fixed

Comment thread lib/timers.js
const unrefedLists = Object.create(null);

const KNOWN_TIMERS = Object.create(null);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Extraneous line.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

fixed

Comment thread lib/timers.js
return true;
}

Timeout.prototype[Symbol.toPrimitive] = function() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Why @@toPrimitive rather than valueOf?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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.

Copy link
Copy Markdown
Member

There's one significant drop showing in the benchmark CI:

                                          confidence improvement accuracy (*)    (**)   (***)
[...]
timers/timers-cancel-pooled.js millions=5        ***    -48.32 %      ±11.22% ±15.03% ±19.77%

I guess we have to make a call deciding whether we're okay with that?

mscdex left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

-1 if we're going to see a performance regression like that

bmeck commented Mar 30, 2018
edited
Loading

Copy link
Copy Markdown
Member Author

It is probably from the uncondition delete in the cancel. We could replace timer.close when toPrimitive is called and make that more conditional at the cost of some odd things possibly showing up if people replace timer.close with a custom function and don't expect us to wrap it. I'll munge with this a bit and see if we can fix up that cliff. Not sure why my test wasn't showing it.

Comment thread lib/timers.js Outdated
delete KNOWN_TIMERS[this[async_id_symbol]];
this.close = $close;
this.close();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This won't pass linting due to missing semicolon. Also, can the function be factored out?

bmeck Mar 30, 2018
edited
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

fixed / it cannot be factored out since it closes over $close.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

$close is effectively always equal to Timeout.prototype.close, right?

bmeck Mar 30, 2018
edited
Loading

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

since it is public and mutable, that is not certain.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

@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…

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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.

Comment thread lib/timers.js Outdated
};

exports.clearInterval = function(timer) {
const clearInterval = exports.clearInterval = function(timer) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Why the addition of clearInterval variable? Doesn't seem used anywhere.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

was added in e32b969 as a bug fix to previous iteration

Comment thread lib/timers.js Outdated
if (timer in KNOWN_TIMERS) {
timer = KNOWN_TIMERS[timer];
}
else {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This fails linting.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

fixed

bmeck commented Apr 2, 2018

Copy link
Copy Markdown
Member Author

@mscdex with the most change can you recheck your perf concerns?

mscdex commented Apr 3, 2018
edited
Loading

Copy link
Copy Markdown
Contributor

Fishrock123 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Can you check that +timeout1 === timeout1[Symbol.toPrimitive]()?

Copy link
Copy Markdown
Contributor

@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:

If you're still interested, I would gladly review that kind of update. If not, mind if I take the existing commits and adjust?

bmeck commented Apr 25, 2018 via email

Copy link
Copy Markdown
Member Author

Copy link
Copy Markdown
Contributor

@bmeck if you still want to pursue this, instead of doing the more expensive in check — you could just set a flag when a timer is converted to a number. In addition, the check within clearTimeout could be reversed. First check whether timer is true & has _onTimeout, then do the typeof check (get the timer, etc.) and call clearTimeout again with the result.

Copy link
Copy Markdown
Contributor

I'd love to see how @apapirovski's suggestions work out.

Copy link
Copy Markdown
Contributor

@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.

bmeck commented Jun 1, 2018

Copy link
Copy Markdown
Member Author

@apapirovski please take it over, your work is quite exciting :)

Copy link
Copy Markdown
Contributor

@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.

Copy link
Copy Markdown
Contributor

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.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-minor PRs that contain new features and should be released in the next minor version. 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.

8 participants


Back | FazBrowse Home | New Git URL