fix: more robust re-subscribe detection for `fromStore` (#13995)

Only count down after timeout, else we would reach 0 before our own render effect reruns, but reach 1 again when the tick callback of the prior teardown runs. That would mean we re-subcribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
pull/14032/head
Simon H 6 days ago committed by GitHub
parent cbc2ca36ce
commit 2e9dab6a21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: more robust re-subscribe detection for `fromStore`

@ -130,9 +130,12 @@ export function fromStore(store) {
subscribers += 1;
return () => {
subscribers -= 1;
tick().then(() => {
// Only count down after timeout, else we would reach 0 before our own render effect reruns,
// but reach 1 again when the tick callback of the prior teardown runs. That would mean we
// re-subcribe unnecessarily and create a memory leak because the old subscription is never cleaned up.
subscribers -= 1;
if (subscribers === 0) {
unsubscribe();
}

@ -666,6 +666,33 @@ describe('fromStore', () => {
teardown();
});
it('creates state from a writable store that updates after timeout', async () => {
const wait = () => new Promise((resolve) => setTimeout(resolve, 100));
const store = {
subscribe: (cb: any) => {
// new object each time to force updates of underlying signals,
// to test the whole thing doesn't rerun more often than it should
setTimeout(() => cb({}), 0);
return () => {};
}
};
const count = fromStore(store);
const log: any[] = [];
const teardown = effect_root(() => {
render_effect(() => {
log.push(count.current);
});
});
await wait();
assert.deepEqual(log, [undefined, {}]);
teardown();
});
it('creates state from a readable store', () => {
const store = readable(0);

Loading…
Cancel
Save