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

Unblock Preact tests that are executed against fake clock #30186

Merged
merged 3 commits into from Sep 10, 2020

Conversation

dvoytenko
Copy link
Contributor

@dvoytenko dvoytenko commented Sep 10, 2020

This pull request reverts #30181.

The description of the problem 1:

Preact schedules handler only once for a queue, when queue.length === 1. We had a situation where the fake test clock was used to schedule the update for queue.length === 1, but then it was discarded. All followup queue pushes were not scheduled because queue.length > 1. But the fake timer was never executed, so the queue was stuck - more items were added to it, but handler was never called. I was actually surprised by this a bit - I though clock.uninstall would run all pending timers, but apparently not.

Not yet clear how to defend from this in depth. Debugging this problem is extremely difficult - there's no way to determine a "hanged" queue on Preact's side. One thing I tried is setting Preact.options.requestAnimationFrame, which is similar to wrapping in act(). However, even with this option, Preact still relies on setTimeout.

The description of the problem 2:

Preact's test-utils used to break the queue when the render function fails. This has been fixed in preactjs/preact@4efcd75. However, we're currently blocked on our Preact upgrade due to unrelated Closure bug. See #30043.

In general, a failing render function is not a good idea and that should be fixed separately. However, noting this here since this is also very hard to debug.

Copy link
Contributor

@kristoferbaxter kristoferbaxter left a comment

Choose a reason for hiding this comment

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

LGTM, future would be smart to add some checks to prohibit this from happening in other ported components.

@@ -83,6 +83,7 @@ describes.realWin(
});

afterEach(() => {
clock.runAll();
Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense, perhaps we should add some additional enforcement to ensure this happens for all tests with a preact implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. I'm looking for defense in depth right now. But no good ideas yet.

@samouri
Copy link
Member

samouri commented Sep 10, 2020

I actually ran into a very similar problem when making a rAF polyfill in worker-dom.

Are we reusing the same queue / outdated fake timer in between the test files, or was it the same instance of Preact that had grabbed/held-on to the rAF? A defense could be a new instance of Preact and of fake-timers for each test file

@dvoytenko
Copy link
Contributor Author

@samouri The specific problem is that the Preact's global queue is shared between all tests. When the first element is added to the queue (and only when the first element added) the queue is scheduled with the setTimeout currently available in globals. If that setTimeout was a fake one - that's when the problem happens: it might never complete and Preact will not try to schedule anymore using the "good" setTimeout because queue is not empty. Preact actually protects itself from a similar problem with requestAnimationFrame. You can see it here. It checks whether the instance of rAF has changed since the last scheduling. Unfortunately the same is not done for setTimeout.

@marvinhagemeister
Copy link

@dvoytenko I think the options hook you were looking for is options.debounceRendering. By setting that you can override our internal scheduling function which is by default a microtask. options.requestAnimationFrame is only used in preact/hooks for effect scheduling. The full list of available option hooks is listed in the official Preact documentation.

Note: that we don't recommend changing scheduling functions. It can lead to unexpected consequences when libraries rely on specific scheduling semantics. Your choice to go the fake timer route is much more foolproof and reliable in the long run. It's indeed a gotcha with fake timers, that one has to run those to completion manually. Not sure how we can hint at this pitfall from Preact's side. We certainly can add a section for that in our docs.

@dvoytenko
Copy link
Contributor Author

@marvinhagemeister, thanks a lot for the advice. We are actually running into an issue with hooks specifically. So I think per your note, we should try to work this around via options.requestAnimationFrame. I'll try it.

@dvoytenko
Copy link
Contributor Author

Also discovered (and skipped) the following problem (updated in the description):

The description of the problem 2:
Preact's test-utils used to break the queue when the render function fails. This has been fixed in preactjs/preact@4efcd75. However, we're currently blocked on our Preact upgrade due to unrelated Closure bug. See #30043.

In general, a failing render function is not a good idea and that should be fixed separately. However, noting this here since this is also very hard to debug.

@dvoytenko dvoytenko merged commit c36edcc into ampproject:master Sep 10, 2020
@dvoytenko dvoytenko deleted the bento/fix-test-useeffect branch September 10, 2020 22:27
ed-bird pushed a commit to ed-bird/amphtml that referenced this pull request Dec 10, 2020
…#30186)

* Unblock Preact tests that are executed against fake clock

* another test fixed

* skip render-failing tests due to Preact issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants