Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34.2k
timers: introduce setInterval async iterator#37153
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
Uh oh!
There was an error while loading. Please reload this page.
Conversation
Linkgoron commented Jan 31, 2021 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
45584a1 to 42a7f52CompareLinkgoron commented Jan 31, 2021
cc @benjamingr |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
lib/timers/promises.js Outdated
| if(abortCallback){ | ||
| abortCallback(newAbortError()); | ||
| passCallback=undefined; | ||
| abortCallback=undefined; | ||
| } |
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.
Assigning passCallback and abortCallback seems unnecessary here.
| if(abortCallback){ | |
| abortCallback(newAbortError()); | |
| passCallback=undefined; | |
| abortCallback=undefined; | |
| } | |
| abortCallback?.(newAbortError()); |
LinkgoronJan 31, 2021 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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.
the undefinds are there because otherwise we sometimes get an error during the tests because of multiple resolves (as we could resolve the promise and then someone aborts the iterator).
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.
Could you move those to after the await new Promise to avoid the repetition? No strong feeling though, if you prefer as it is now, feel free to disregard, I'd personally find it easier to follow if it were down there.
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.
Actually, that's where I originally put it. However, it's incorrect because you could still resolve and reject before the await yields, and get multiple resolves.
lib/timers/promises.js Outdated
| // eslint-disable-next-line no-undef | ||
| clearInterval(interval); |
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.
Shouldn't we import this from timers?
LinkgoronJan 31, 2021 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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 could, I just saw that clearTimeout also wasn't imported (I actually copied the exact same comment from that one)
lib/timers/promises.js Outdated
| if(passCallback){ | ||
| passCallback(); | ||
| passCallback=undefined; | ||
| abortCallback=undefined; | ||
| } |
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.
Again, assigning the variables to undefined seems unnecessary
| if(passCallback){ | |
| passCallback(); | |
| passCallback=undefined; | |
| abortCallback=undefined; | |
| } | |
| passCallback?.(); |
LinkgoronJan 31, 2021 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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.
the undefinds are there because otherwise we sometimes get an error during the tests because of multiple resolves (as we could resolve the promise and then someone aborts the iterator)
lib/timers/promises.js Outdated
| } | ||
| while(!signal?.aborted){ | ||
| if(notYielded===0){ |
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 get rid of the if statement by moving the for loop above the await new Promise?
LinkgoronJan 31, 2021 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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 that we would get an issue like the following:
suppose that the promise was fulfilled, and before we got back to it the interval was somehow aborted. we'll go back to the while condition and get out of the while loop, instead of yielding the value.
2cb80b2 to 1f8b21cCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
benjamingr left a comment
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.
Good job, left a few comments!
targos commented Jan 31, 2021 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Is it possible to |
benjamingr commented Jan 31, 2021
A test for that case is a good idea, I think indeed a |
benjamingr commented Jan 31, 2021
@Linkgoron I think what Michael meant is that you can |
lib/timers/promises.js Outdated
| awaitnewPromise((resolve,reject)=>{ | ||
| passCallback=resolve; | ||
| abortCallback=reject; | ||
| }); |
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.
| }); | |
| }); | |
| passCallback=undefined; | |
| abortCallback=undefined; |
lib/timers/promises.js Outdated
| if(abortCallback){ | ||
| abortCallback(newAbortError()); | ||
| passCallback=undefined; | ||
| abortCallback=undefined; | ||
| } |
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.
Could you move those to after the await new Promise to avoid the repetition? No strong feeling though, if you prefer as it is now, feel free to disregard, I'd personally find it easier to follow if it were down there.
Linkgoron commented Jan 31, 2021
Oh. I didn't know that! Anyway, I'll update the PR soon. |
4db2941 to bc95c21Compare
benjamingr left a comment
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.
Thanks, good job. I think this is good enough to land (the module is experimental) and iron out small nits later :]
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
targos commented Feb 1, 2021
Just a thought: could we easily make the iterated values resolve to the number of iterations? |
benjamingr commented Feb 1, 2021
@targos I would prefer that as well - but James raised the point in the original PR that |
bc95c21 to 6c7ec0eComparenodejs-github-bot commented Feb 1, 2021
3508413 to a651ce4Comparenodejs-github-bot commented Feb 2, 2021
nodejs-github-bot commented Feb 2, 2021
nodejs-github-bot commented Feb 2, 2021
nodejs-github-bot commented Feb 3, 2021
benjamingr commented Feb 3, 2021
benjamingr commented Feb 3, 2021
Landed in fefc639 🎉 |
Added setInterval async generator to timers\promises. Utilises async generators to provide an iterator compatible with `for await`. Co-Authored-By: Fabian Cook <[email protected]> fix message PR-URL: #37153 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]>
| { | ||
| // Check that if we abort when we have some callbacks left, | ||
| // we actually call them. | ||
| constcontroller=newAbortController(); | ||
| const{ signal }=controller; | ||
| constdelay=10; | ||
| lettotalIterations=0; | ||
| consttimeoutLoop=runInterval(async(iterationNumber)=>{ | ||
| if(iterationNumber===2){ | ||
| awaitsetTimeout(delay*2); | ||
| controller.abort(); | ||
| } | ||
| if(iterationNumber>totalIterations){ | ||
| totalIterations=iterationNumber; | ||
| } | ||
| },delay,signal); | ||
| timeoutLoop.catch(common.mustCall(()=>{ | ||
| assert.ok(totalIterations>=3,`iterations was ${totalIterations} < 3`); | ||
| })); | ||
| } | ||
| } |
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.
Unfortunately, this test is unreliable and is failing in CI on Raspberry Pi devices. I also can make it fail trivially on my macOS laptop by running tools/test.py -j96 --repeat=192 test/parallel/test-timers-promisified.js. I haven't looked closely (yet) but in my experience, having a magic number delay variable like this in a timers test indicates an assumption that the host isn't so slow that (say) a 10ms second timer won't fire 50ms later. This assumption is, of course, incorrect if the machine has low CPU/memory (like a Raspberry Pi device) or if there are a lot of other things competing for the machine's resources (-j96 --repeat=192).
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.
Sorry for causing an issue - I'll take a look and fix it
LinkgoronFeb 4, 2021 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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.
When implementing this, I was under the assumption that if we I have an interval and a timeout where the interval starts before a timeout and is supposed to execute before the timeout executes, the interval's callback would always execute before the timeout's callback. Either this assumption was wrong, or something in my setInterval implementation is incorrect. (i.e. I thought that the setTimeout would always run after the internal setInterval)
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.
Added setInterval async generator to timers\promises. Utilises async generators to provide an iterator compatible with `for await`. Co-Authored-By: Fabian Cook <[email protected]> fix message PR-URL: #37153 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: James M Snell <[email protected]>
Notable Changes: * crypto: * add keyObject.export() 'jwk' format option (Filip Skokan) #37081 * deps: * upgrade to libuv 1.41.0 (Colin Ihrig) #37360 * doc: * add dmabupt to collaborators (Xu Meng) #37377 * refactor fs docs structure (James M Snell) #37170 * fs: * add fsPromises.watch() (James M Snell) #37179 * use a default callback for fs.close() (James M Snell) #37174 * add AbortSignal support to watch (Benjamin Gruenbaum) #37190 * perf_hooks: * introduce createHistogram (James M Snell) #37155 * stream: * improve Readable.from error handling (Benjamin Gruenbaum) #37158 * timers: * introduce setInterval async iterator (linkgoron) #37153 * tls: * add ability to get cert/peer cert as X509Certificate object (James M Snell) #37070
Notable Changes: * crypto: * add keyObject.export() 'jwk' format option (Filip Skokan) #37081 * deps: * upgrade to libuv 1.41.0 (Colin Ihrig) #37360 * doc: * add dmabupt to collaborators (Xu Meng) #37377 * refactor fs docs structure (James M Snell) #37170 * fs: * add fsPromises.watch() (James M Snell) #37179 * use a default callback for fs.close() (James M Snell) #37174 * add AbortSignal support to watch (Benjamin Gruenbaum) #37190 * perf_hooks: * introduce createHistogram (James M Snell) #37155 * stream: * improve Readable.from error handling (Benjamin Gruenbaum) #37158 * timers: * introduce setInterval async iterator (linkgoron) #37153 * tls: * add ability to get cert/peer cert as X509Certificate object (James M Snell) #37070
Notable Changes: * crypto: * add keyObject.export() jwk format option (Filip Skokan) #37081 * deps: * upgrade to libuv 1.41.0 (Colin Ihrig) #37360 * doc: * add dmabupt to collaborators (Xu Meng) #37377 * refactor fs docs structure (James M Snell) #37170 * fs: * add fsPromises.watch() (James M Snell) #37179 * use a default callback for fs.close() (James M Snell) #37174 * add AbortSignal support to watch (Benjamin Gruenbaum) #37190 * perf_hooks: * introduce createHistogram (James M Snell) #37155 * stream: * improve Readable.from error handling (Benjamin Gruenbaum) #37158 * timers: * introduce setInterval async iterator (linkgoron) #37153 * tls: * add ability to get cert/peer cert as X509Certificate object (James M Snell) #37070
PR-URL: #37406 Notable Changes: * crypto: * add keyObject.export() jwk format option (Filip Skokan) #37081 * deps: * upgrade to libuv 1.41.0 (Colin Ihrig) #37360 * doc: * add dmabupt to collaborators (Xu Meng) #37377 * refactor fs docs structure (James M Snell) #37170 * fs: * add fsPromises.watch() (James M Snell) #37179 * use a default callback for fs.close() (James M Snell) #37174 * add AbortSignal support to watch (Benjamin Gruenbaum) #37190 * perf_hooks: * introduce createHistogram (James M Snell) #37155 * stream: * improve Readable.from error handling (Benjamin Gruenbaum) #37158 * timers: * introduce setInterval async iterator (linkgoron) #37153 * tls: * add ability to get cert/peer cert as X509Certificate object (James M Snell) #37070
PR-URL: #37406 Notable Changes: * crypto: * add keyObject.export() jwk format option (Filip Skokan) #37081 * deps: * upgrade to libuv 1.41.0 (Colin Ihrig) #37360 * doc: * add dmabupt to collaborators (Xu Meng) #37377 * refactor fs docs structure (James M Snell) #37170 * fs: * add fsPromises.watch() (James M Snell) #37179 * use a default callback for fs.close() (James M Snell) #37174 * add AbortSignal support to watch (Benjamin Gruenbaum) #37190 * perf_hooks: * introduce createHistogram (James M Snell) #37155 * stream: * improve Readable.from error handling (Benjamin Gruenbaum) #37158 * timers: * introduce setInterval async iterator (linkgoron) #37153 * tls: * add ability to get cert/peer cert as X509Certificate object (James M Snell) #37070
targos commented Jul 19, 2021
I'm marking this dont-land-on-v14.x because |
Added
setIntervalasync generator to timers\promises. Utilises async generators to provide an iterator compatible withfor await. This is a continuation of initial work by Fabian Cook #35841 (who is also marked as co-author in this PR). The async-generator throws anAbortErrorwhen aborted.Note that there are some decisions I've made regarding (so-called) backpressure. If multiple iterations were completed before a value is yielded, the next iteration won't wait
delaylong, but yield the next value in the next tick. In addition, if there was backpressure before the controller was aborted, the generator will emit all of the passed intervals, and only then finish the iteration. I'd be happy to change this behavior if this is not what's expected (i.e. change the generator to stop producing immediately).See the previous PR for more information and discussions.
Example:
make -j4 test(UNIX), orvcbuild test(Windows) passes