Skip to content

fix: clear previous_task reference after abort in Tween - #18541

Merged
dummdidumm merged 2 commits into
sveltejs:mainfrom
prashantg-pixi:fix/tween-previous-task-leak
Jul 14, 2026
Merged

dummdidumm merged 2 commits into
sveltejs:mainfrom
prashantg-pixi:fix/tween-previous-task-leak

Conversation

@prashantg-pixi

Copy link
Copy Markdown
Contributor

Before submitting the PR, please make sure you do the following

  • 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
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

Problem

Tween.set() (in packages/svelte/src/motion/tweened.js) aborts the previous task when interrupted by a new set() call, but never clears the previous_task closure variable afterward. Each new set() call's rAF loop closure retains a reference to the previous (already-aborted) task — and transitively, the entire chain of prior tasks — so on any long-lived or frequently-interrupted Tween, memory grows unbounded across interruptions and is never released.

No linked issue — new issue creation is currently restricted on this repo for my account, so filing the details directly here instead.

Repro

import { Tween } from 'svelte/motion';
const t = new Tween(new Array(10_000).fill(0));
setInterval(() => t.set(new Array(10_000).fill(Math.random())), 20);

Chrome DevTools heap snapshots taken across repeated intervals show a steadily growing retainer chain rooted at {promise, abort} → previous_task, never released between GC passes.

Fix

One-line fix: null the previous_task reference immediately after aborting it, inside the !started branch of the rAF loop:

 previous_task?.abort();
+previous_task = null;

This mirrors the identical pattern already present in the deprecated tweened() store further up in the same file:

if (previous_task) {
  previous_task.abort();
  previous_task = null;
}

It appears this line was dropped when the newer Tween class was written. previous_task is not read anywhere else after this point in the closure, so this change has no effect on control flow — it only releases a reference that was never used again.

Verification

  • Confirmed via Chrome DevTools heap snapshot diffing across many rounds of interrupted tweens in a real-world app: the previous_task retainer chain is fully eliminated post-fix, with allocation/collection back in balance.
  • No change to existing abort()/resolve/reject semantics — the fix only clears a variable, it doesn't alter when or how tasks are aborted.
  • pnpm test: 7558 passed, 154 skipped. One unrelated failure in runtime-browser/test.ts due to a missing local Playwright browser binary (environment-only, not present in CI).
  • pnpm lint: clean.

Happy to add a regression test if maintainers can point me to the right pattern under packages/svelte/tests/motion — didn't want to guess at the existing test conventions for this file.

@pkg-svelte-dev

pkg-svelte-dev Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Install the latest version of svelte from f22eac4:

pnpm add https://pkg.svelte.dev/svelte/c/f22eac4dba89cfe83c6e4a584ec054dfd3ddc292

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/svelte/pr/18541

Note

This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed.

@changeset-bot

changeset-bot Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f22eac4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

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

@dummdidumm
dummdidumm merged commit 199ffeb into sveltejs:main Jul 14, 2026
15 of 16 checks passed
@github-actions github-actions Bot mentioned this pull request Jul 14, 2026
dummdidumm pushed a commit that referenced this pull request Jul 14, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## svelte@5.56.5

### Patch Changes

- chore: drop dead code that make TSGO fail
([#18496](#18496))

- fix: don't (re)connect deriveds when read inside branch/root effects
([#18527](#18527))

- fix: skip unnecessary derived effect in earlier batch
([#18525](#18525))

- fix: avoid declaration tag warning in event handlers
([#18500](#18500))

- fix: abort deriveds own AbortSignal when it disconnects
([#18400](#18400))

- fix: ensure `$state.eager()` is correctly transormed for SSR output
([#18530](#18530))

- fix: correctly transform declaration tags during SSR
([#18492](#18492))

- fix: transform computed keys in keyed `{#each}` destructuring patterns
([#18521](#18521))

- fix: chain preprocessor sourcemaps with an empty `sources[0]` instead
of dropping them
([#18518](#18518))

- fix: clear previous_task reference after abort in Tween to prevent
memory leak on interrupted tweens
([#18541](#18541))

- fix: don't treat declaration tags as parts inside each blocks
([#18507](#18507))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
renovate Bot added a commit to cigaleapp/cigale that referenced this pull request Jul 27, 2026
##### [v5.56.6](https://github.com/sveltejs/svelte/releases/tag/svelte%405.56.6)

##### Patch Changes

- perf: skip unnecessary blocker analysis when compiling components without top-level await ([#18548](sveltejs/svelte#18548))

- fix: rerun derived that had an abort controller on reconnection ([#18551](sveltejs/svelte#18551))
##### [v5.56.5](https://github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#5565)

##### Patch Changes

- chore: drop dead code that make TSGO fail ([#18496](sveltejs/svelte#18496))

- fix: don't (re)connect deriveds when read inside branch/root effects ([#18527](sveltejs/svelte#18527))

- fix: skip unnecessary derived effect in earlier batch ([#18525](sveltejs/svelte#18525))

- fix: avoid declaration tag warning in event handlers ([#18500](sveltejs/svelte#18500))

- fix: abort deriveds own AbortSignal when it disconnects ([#18400](sveltejs/svelte#18400))

- fix: ensure `$state.eager()` is correctly transormed for SSR output ([#18530](sveltejs/svelte#18530))

- fix: correctly transform declaration tags during SSR ([#18492](sveltejs/svelte#18492))

- fix: transform computed keys in keyed `{#each}` destructuring patterns ([#18521](sveltejs/svelte#18521))

- fix: chain preprocessor sourcemaps with an empty `sources[0]` instead of dropping them ([#18518](sveltejs/svelte#18518))

- fix: clear previous\_task reference after abort in Tween to prevent memory leak on interrupted tweens ([#18541](sveltejs/svelte#18541))

- fix: don't treat declaration tags as parts inside each blocks ([#18507](sveltejs/svelte#18507))
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.

2 participants