diff --git a/.changeset/early-crabs-rest.md b/.changeset/early-crabs-rest.md
new file mode 100644
index 0000000000..d3f290a5c2
--- /dev/null
+++ b/.changeset/early-crabs-rest.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: abort deriveds own AbortSignal when it disconnects
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index fd64f3b45d..0410280100 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -28,6 +28,7 @@ import {
skipped_deps,
new_deps
} from '../runtime.js';
+import { without_reactive_context } from '../dom/elements/bindings/shared.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
import * as w from '../warnings.js';
@@ -444,6 +445,13 @@ export function update_derived(derived) {
* @param {Derived} derived
*/
export function freeze_derived_effects(derived) {
+ if (derived.ac !== null) {
+ without_reactive_context(() => {
+ derived.ac?.abort(STALE_REACTION);
+ derived.ac = null;
+ });
+ }
+
if (derived.effects === null) return;
for (const e of derived.effects) {
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte
new file mode 100644
index 0000000000..dca1206949
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/Child.svelte
@@ -0,0 +1,20 @@
+
+
+{der}
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js
new file mode 100644
index 0000000000..d52fb442d0
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/_config.js
@@ -0,0 +1,14 @@
+import { ok, test } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+ async test({ assert, target, errors }) {
+ const btn = target.querySelector('button');
+
+ flushSync(() => {
+ btn?.click();
+ });
+ assert.htmlEqual(target.innerHTML, '1 ');
+ assert.deepEqual(errors, []);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte
new file mode 100644
index 0000000000..3997fcc35d
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-destroy/main.svelte
@@ -0,0 +1,14 @@
+
+
+{aborted}
+
+
+
+{#if count % 2 === 0}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js
index 2dacf188d7..aa38dd8280 100644
--- a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/_config.js
@@ -4,9 +4,11 @@ import { flushSync } from 'svelte';
export default test({
async test({ assert, target, errors }) {
const btn = target.querySelector('button');
+
flushSync(() => {
btn?.click();
});
+ assert.htmlEqual(target.innerHTML, '1:1 ');
assert.deepEqual(errors, []);
}
});
diff --git a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte
index ebefe38fb2..b1076926e3 100644
--- a/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/abort-signal-derived-set-state/main.svelte
@@ -19,6 +19,6 @@
})
-{der}
+{der}:{aborted}
-
\ No newline at end of file
+