fix: use right batch/branch on first run#18162
Conversation
Fixes the regression that was uncovered by a SvelteKit test. It consistens of two parts: 1. Restoring the latest, not the initial batch in `flatten`: At the beginning `flatten` stores the current batch, and once everything async is finished it restores it. That falls down if one of the async deriveds reruns during that time. Now the batch associated with that rerun should be used because its `current` map contains a later value. We gotta use that. The hacky fix for now is to set the latest batch onto the source that the async derived has. That could mess with perf since the source object shape is not the same all the time anymore. There's probably a better way to write this but it's getting late here. This fix solves the regression part where it shows the wrong string for the url pathname. 2. branches deletes older batches and then bails when it comes across itself. The logic assumes that batches are in the map in ascending order but that's not always true. Making the logic robust to that fixes the part where it keeps showing the "should never see this" string from the obsolete `+page.svelte`. I wasn't able to make a test for this yet.
🦋 Changeset detectedLatest commit: 184b86f The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
While looking into #18162 I found an adjacent bug. Currently, if an async derived resolves in batch 2 before it resolves in batch 1, we reject the promise belonging to batch 1 and by extension the batch itself. This means that any other changes in batch 1 are silently discarded, incorrectly. The fix is almost comically simple: rather than rejecting the earlier promise, we just resolve it with the latest value. I have a hunch that this might also enable us to simplify the rebase logic, though I haven't investigated that in this PR. ### Before submitting the PR, please make sure you do the following - [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`. - [x] This message body should clearly illustrate what problems it solves. - [x] Ideally, include a test that fails without this PR but passes with it. - [x] If this PR changes code within `packages/svelte/src`, add a changeset (`npx changeset`). ### Tests and linting - [x] Run the tests with `pnpm test` and lint the project with `pnpm lint`
|
Still trying to wrap my head around the right way to think about this problem. But the more I think about it the more I think When we use the most recent batch for the template effect, instead of the batch in which it was created, we see tearing (the first occurrence of So this approach gets rid of one form of tearing (between Thinking aloud time: This is a lightly-tweaked version of the test in this PR, minus the boundary (which I don't think is relevant to the problem we're trying to solve). If we press 'increment' twice then 'pop', the sequence of events is this:
In this case it would be sufficient to fix 9 such that the async effect doesn't re-run. But I suspect that would still result in buggy behaviour in other cases. Ultimately I think we need to fix 7. This PR does so by creating the template effect in the context of batch 3, but as explained above I don't think that's quite right. Maybe this is a case where batch 2 needs to wait until batch 3 is ready (similar to #17872). Or maybe we need to rethink blocking: instead of only resolving batch 3 once its blockers have completed, we apply batch 3's values onto its blockers and then discard it? The journey continues. |
|
Yes blocking needs more thought. I'm not sure if it's a series of tweaks or if we need to rethink how blocking works in general. E.g. another thing I found: I'm also not sure if the current blocking mechanism finds all types of correct blockers, or if some could be false positives. e.g. what if batch B will destroy the sources of batch A that are different to it (and therefore B thinks it needs to wait for A but it doesn't need to). ... which all makes me wonder if finding blockers through the reactivity graph (similar to how I try to entangle in #17872) could be another avenue. |
|
Note to self: an example of the situation above could be
|
|
(Actually is that right? |
|
yes I noticed that, too, and now I'm not sure why it would happen. But it definitely helps fix the bug described in sveltejs/kit#15431. I'll have to investigate more. |
|
With the changes in main, in your "lightly-tweaked version"-playground, the batches now correctly resolve in order. The UI shows |
This is another attempt to address some of the tricky edge cases that
arise when async batches resolve out of order. The idea is this:
Essentially, when a batch resolves:
1. we find the latest batch it shares changes with
2. if none exists (either because this is the earliest batch, or because
it is independent of any earlier batches):
- we commit it: effects are flushed, `oncommit` callbacks are run
- we restart any async work in later batches that depends on both the
committed values and the later batch's changes
3. otherwise, we don't commit the batch. instead:
- we merge the changes from the later batch onto the earlier batch. in
some cases this may mean restarting async work on the earlier batch, but
we avoid doing so unnecessarily.
- we then `#process()` the earlier batch. if it resolves, goto 1
This feels like it ought to work. There are still two failing tests,
which I'm currently looking into.
Notable changes:
- Instead of having a `batches` set, we have a linked list. When a batch
resolves, this makes it easy to find the batch that it should be merged
into
- The `#is_deferred` logic now takes account of skipped effects —
there's no need to wait for a promise inside a falsy `if` block (this
accounts for the change in the `async-inner-after-outer` test)
- We no longer care about `#blockers`
I would like to believe that this approach will allow us to simplify and
delete some code, for example the `rebase` logic, though that remains to
be seen. It also feels like some version of #18035 would be helpful.
- closes #18189
- closes #18162
- fixes sveltejs/kit#15431
### Before submitting the PR, please make sure you do the following
- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] Prefix your PR title with `feat:`, `fix:`, `chore:`, or `docs:`.
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.
- [x] If this PR changes code within `packages/svelte/src`, add a
changeset (`npx changeset`).
### Tests and linting
- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint`
---------
Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com>
Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
Fixes the regression that was uncovered by a SvelteKit test. It consistens of two parts:
flatten: At the beginningflattenstores the current batch, and once everything async is finished it restores it. That falls down if one of the async deriveds reruns during that time. Now the batch associated with that rerun should be used because itscurrentmap contains a later value. We gotta use that. The hacky fix for now is to set the latest batch onto the source that the async derived has. That could mess with perf since the source object shape is not the same all the time anymore. There's probably a better way to write this but it's getting late here. This fix solves the regression part where it shows the wrong string for the url pathname.+page.svelte. I wasn't able to make a test for this yet.