diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/Child.svelte
new file mode 100644
index 0000000000..9b705f3064
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/Child.svelte
@@ -0,0 +1,18 @@
+
+
+
bar: {bar}
+baz: {baz}
+{#if qux}
+
+{/if}
diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/_config.js b/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/_config.js
new file mode 100644
index 0000000000..ff77b1c512
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/_config.js
@@ -0,0 +1,25 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+// Ensure async derived remains reactive with associated effect and boundary with guard (#17271)
+//
+// Accounts for both UNINITIALIZED leaking from get and the baz derived remaining NaN
+// due to having both an $effect and a boundary with an if in the same template
+export default test({
+ html: `Loading...
`,
+
+ skip_no_async: true,
+
+ async test({ assert, target }) {
+ await tick();
+
+ assert.htmlEqual(
+ target.innerHTML,
+ `
+ bar: 1
+ baz: 69
+
+ `
+ );
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/main.svelte
new file mode 100644
index 0000000000..9687e51400
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-derived-with-effect-and-boundary/main.svelte
@@ -0,0 +1,11 @@
+
+
+
+ {#snippet pending()}
+ Loading...
+ {/snippet}
+
+
+
diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts
index 23c4bb42f9..b3bf3df5fe 100644
--- a/packages/svelte/tests/signals/test.ts
+++ b/packages/svelte/tests/signals/test.ts
@@ -1,5 +1,5 @@
import { describe, assert, it } from 'vitest';
-import { flushSync } from '../../src/index-client';
+import { flushSync, fork } from '../../src/index-client';
import * as $ from '../../src/internal/client/runtime';
import { push, pop } from '../../src/internal/client/context';
import {
@@ -9,15 +9,24 @@ import {
user_effect,
user_pre_effect
} from '../../src/internal/client/reactivity/effects';
-import { state, set, update, update_pre } from '../../src/internal/client/reactivity/sources';
+import {
+ state,
+ set,
+ update,
+ update_pre,
+ internal_set,
+ source
+} from '../../src/internal/client/reactivity/sources';
import type { Derived, Effect, Source, Value } from '../../src/internal/client/types';
import { proxy } from '../../src/internal/client/proxy';
import { derived } from '../../src/internal/client/reactivity/deriveds';
import { snapshot } from '../../src/internal/shared/clone.js';
import { SvelteSet } from '../../src/reactivity/set';
-import { DESTROYED } from '../../src/internal/client/constants';
+import { DESTROYED, MAYBE_DIRTY } from '../../src/internal/client/constants';
+import { UNINITIALIZED } from '../../src/constants';
import { noop } from 'svelte/internal/client';
import { disable_async_mode_flag, enable_async_mode_flag } from '../../src/internal/flags';
+import { Batch } from '../../src/internal/client/reactivity/batch';
/**
* @param runes runes mode
@@ -1493,4 +1502,104 @@ describe('signals', () => {
assert.deepEqual(log, ['inner destroyed', 'inner destroyed']);
};
});
+
+ // prevent UNINITIALIZED from leaking in get() (#17271)
+ it('derived with deps evaluated in fork should not return UNINITIALIZED', () => {
+ enable_async_mode_flag();
+
+ try {
+ const count = state(5);
+ let d: Derived;
+
+ const f = fork(() => {
+ effect_root(() => {
+ render_effect(() => {
+ d = derived(() => $.get(count) * 2);
+ $.get(d);
+ });
+ })();
+ });
+
+ assert.equal(d!.v, /** @type {any} */ UNINITIALIZED);
+ assert.equal((d!.f & MAYBE_DIRTY) !== 0, true);
+ assert.equal($.get(d!), 10);
+
+ f.discard();
+ } finally {
+ disable_async_mode_flag();
+ }
+ });
+
+ it('should return undefined when reading UNINITIALIZED from batch.previous via apply()', () => {
+ enable_async_mode_flag();
+
+ try {
+ const s = source(/** @type {any} */ UNINITIALIZED);
+ const dummy = state(0);
+
+ const forkA = fork(() => {
+ effect_root(() => {
+ render_effect(() => {
+ internal_set(s, 'resolved');
+ });
+ })();
+ });
+
+ let readValue: any;
+
+ const forkB = fork(() => {
+ effect_root(() => {
+ render_effect(() => {
+ set(dummy, 1);
+ readValue = $.get(s);
+ });
+ })();
+ });
+
+ assert.equal(readValue, undefined);
+ assert.notEqual(readValue, UNINITIALIZED);
+
+ forkA.discard();
+ forkB.discard();
+ } finally {
+ disable_async_mode_flag();
+ }
+ });
+
+ it('should return undefined when reading UNINITIALIZED source captured in concurrent batch', () => {
+ enable_async_mode_flag();
+
+ try {
+ const s = source('initial');
+ const dummy = state(0);
+
+ // Create a pending batch
+ const f1 = fork(() => {
+ effect_root(() => {
+ render_effect(() => {
+ set(dummy, 1);
+ });
+ })();
+ });
+
+ let readValue: any;
+ const f2 = fork(() => {
+ effect_root(() => {
+ render_effect(() => {
+ // Puts UNINITIALIZED in batch_values via capture()
+ internal_set(s, /** @type {any} */ UNINITIALIZED);
+ readValue = $.get(s);
+ });
+ })();
+ });
+
+ assert.equal(readValue, undefined);
+ assert.notEqual(readValue, UNINITIALIZED);
+
+ f1.discard();
+ f2.discard();
+ } finally {
+ disable_async_mode_flag();
+ }
+ });
});