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

Add stack trace to code recursively scheduling timers #325

Conversation

alistairjcbrown
Copy link
Contributor

@alistairjcbrown alistairjcbrown commented May 17, 2020

Purpose (TL;DR) - mandatory

Fix issue #230 by using stack information for jobs and timers to provide more context when an infinite loop error is thrown.

Solution - optional

This solution works by adding a stack property to jobs and tasks. This data can then be used, if an infinite loop is detected, to set the stack of the thrown infiniteLoopError.

Example stack trace from test run
Error: Aborting after running 5 timers, assuming an infinite loop!
Timeout - recursiveCreateTimerTimeout
      at recursiveCreateTimer (test/fake-timers-test.js:4668)
      at recursiveCreateTimerTimeout (test/fake-timers-test.js:4669)
      at callTimer (src/fake-timers-src.js:441)
      at Object.next (src/fake-timers-src.js:1039)
      at src/fake-timers-src.js:1128

@codecov
Copy link

codecov bot commented May 17, 2020

Codecov Report

Merging #325 into master will increase coverage by 0.39%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #325      +/-   ##
==========================================
+ Coverage   93.12%   93.52%   +0.39%     
==========================================
  Files           1        1              
  Lines         553      587      +34     
==========================================
+ Hits          515      549      +34     
  Misses         38       38              
Flag Coverage Δ
#unit 93.52% <100.00%> (+0.39%) ⬆️
Impacted Files Coverage Δ
src/fake-timers-src.js 93.52% <100.00%> (+0.39%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update cb4c2f6...d95493e. Read the comment docs.

src/fake-timers-src.js Outdated Show resolved Hide resolved
@SimenB
Copy link
Member

SimenB commented May 17, 2020

Could we just keep track of the last scheduled timer instead of all scheduled timers? Not sure how much that matters

@SimenB SimenB requested review from benjamingr and fatso83 May 17, 2020 11:45
@alistairjcbrown alistairjcbrown force-pushed the feature/provide-stack-on-infinites-recursion branch from 9a92f55 to 290426e Compare May 17, 2020 13:20
@SimenB
Copy link
Member

SimenB commented May 17, 2020

Another thought, this is mostly meant to catch timers added while running timers. Could we add some flag internally that's set to true whenever we run timers, and only collect these at that time?

@alistairjcbrown alistairjcbrown force-pushed the feature/provide-stack-on-infinites-recursion branch from 290426e to 033e9fb Compare May 17, 2020 21:01
@alistairjcbrown
Copy link
Contributor Author

@SimenB Thanks for the feedback!
I've updated to set an error object on the job instead of getting the error stack. The second commit adds in a isNearInfiniteLimit flag which is used to decide whether to attach an error object to the job. Jobs from the job before we hit the limit will have an error property populated, which is then used if we need to create an InfiniteLoopError. What do you think?

@fatso83
Copy link
Contributor

fatso83 commented May 19, 2020

no time (parental leave) at the moment to look at PRs, sorry.

" timers, assuming an infinite loop!"
);

var sliceSize = job.type ? 4 : 3;
Copy link
Member

Choose a reason for hiding this comment

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

It would be ideal to calculate this by looking for ourselves in the stack trace rather than setting prefixed sizes. This can fail based on adding new jobs.


var sliceSize = job.type ? 4 : 3;

infiniteLoopError.stack =
Copy link
Member

Choose a reason for hiding this comment

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

Some environments don't have a writeable stack property - so it would be better to wrap this in a try/catch and to use defineProperty

Copy link
Member

Choose a reason for hiding this comment

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

In particular we had this issue several times in bluebird which also mutates stacks

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

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

Hey, left a few comments.

Code looks good in principle - the things I'd want to see to approve (other collaborators may approve before I'm not blocking):

  • Tests that check a larger chunk of the stack (so we're sure this doesn't break) .
  • Dealing with a non-writeable stack property.

I'll approve if those two get addressed even without calculating the slice size automatically.

@benjamingr
Copy link
Member

Also:

What do you think?

The actual implementation logic lgtm

@alistairjcbrown
Copy link
Contributor Author

@benjamingr Awesome, thanks for the feedback! I'll take a run through it tomorrow night 👍

@alistairjcbrown alistairjcbrown force-pushed the feature/provide-stack-on-infinites-recursion branch from 825a19b to 0a5b187 Compare May 20, 2020 21:32
@alistairjcbrown
Copy link
Contributor Author

@benjamingr

Dealing with a non-writeable stack property

Using defineProperty wrapped in try/catch as suggested - TIL about non-writeable stacks 👍

calculating the slice size automatically

Doing this by running through the stack line by line, checking the line matches at target.X (where X may be (anonymous function) or <computed>) or at Object.Y (where Y is a clock method).

Tests that check a larger chunk of the stack (so we're sure this doesn't break)

Apologies, I wasn't 100% sure on what this was asking, so I've added more tests to cover requestAnimationFrame, setImmediate, setInterval, requestIdleCallback, nextTick.

If this was referring to checking more of the stack trace itself, the tests are checking that we're getting the expected method at the top of the stack - but we can't check the filename as it'll be __puppeteer_evaluation_script__ on headless. Do you want more of the stack trace checked?

Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

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

Latest version LGTM. Thanks for this - this makes debugging easier :)

Let's give other members a few days to respond before merging.

@benjamingr
Copy link
Member

Hey, I wanted to merge and it looks like there is a merge conflict - can you please take a look?

Use stack information for jobs and timers to provide more context when
an infinite loop error is thrown.

Fixes sinonjs#230
Use a flag when approaching infinite loop detection which will enable
an error object to be attached to the job, which can then be used for
generating the stack output.
- Safely update stack property using `defineProperty`, wrapped in try/catch
- Tests for more clock functions to confirm stack trace provided
- Stack trace slice automatically calculated based on clock function position
- Use global setInterval and setImmediate to match setTimeout use
- Remove unnecessary duration parameter from setImmediate and setImmediate
@alistairjcbrown alistairjcbrown force-pushed the feature/provide-stack-on-infinites-recursion branch from 955361e to d95493e Compare June 8, 2020 20:24
@alistairjcbrown
Copy link
Contributor Author

@benjamingr Rebased off master and conflict resolved 👍

@benjamingr
Copy link
Member

Thanks!

@benjamingr benjamingr merged commit 384f696 into sinonjs:master Jun 8, 2020
@alistairjcbrown alistairjcbrown deleted the feature/provide-stack-on-infinites-recursion branch June 8, 2020 20:33
@fatso83
Copy link
Contributor

fatso83 commented Sep 14, 2020

This broke the builds on Safari, IE and Firefox. It needs some patching before we can release it as part of a new release.
See https://app.circleci.com/pipelines/github/sinonjs/fake-timers/117/workflows/625d68b6-ed2c-4d04-a450-c22a240599dd/jobs/670

The "cloud" tests are only run on master, as PR runs can expose the Sauce Labs credentials.

@fatso83
Copy link
Contributor

fatso83 commented Jan 15, 2021

@benjamingr Do you think you can get this in again, after fixing the issues that broke the build? Was some good work done here by @alistairjcbrown. Shame to let it go to waste.

@benjamingr
Copy link
Member

Yeah I agree - I just didn't have a ton of free time recently :/

Maybe @itayperry wants to pick up readding this? (I can guide, I just have little free time to dedicate to this myself)

@itayperry
Copy link
Contributor

itayperry commented Jan 22, 2021

I would be happy to take part in this @benjamingr 🎉
🪴 I could, of course, use some guidance and tips. I hope I'll be able to help 💭

fatso83 pushed a commit that referenced this pull request Jun 8, 2021
)

* Add stack trace to code recursively scheduling timers

Use stack information for jobs and timers to provide more context when
an infinite loop error is thrown.

Fixes #230

Add flag for approaching infinite loop

Use a flag when approaching infinite loop detection which will enable
an error object to be attached to the job, which can then be used for
generating the stack output.

Update for PR feedback

- Safely update stack property using `defineProperty`, wrapped in try/catch
- Tests for more clock functions to confirm stack trace provided
- Stack trace slice automatically calculated based on clock function position

Remove unnecessary durations and use globals

- Use global setInterval and setImmediate to match setTimeout use
- Remove unnecessary duration parameter from setImmediate and setImmediate

add use of prettier

Firefox and Chrome tests pass after RegExp fix

Chrome shows stack messages differently, and uses the words: 'at' and 'Object', Firefox does not - that has created two problems: first, the stack trace messages that are shown to the user are a bit different (Firefox always has at least one more line at the start of the stack list - usually internal functions that are not supposed to appear). Second, the RegExp tried to match parts of the strings that only exist in Chrome, failing the Firefox tests)

prettier used

* small RegExp fix for node env + linting

* improving test-coverage statistics

* at attempt to ignore certain condition in nyc coverage

Co-authored-by: Alistair Brown <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants