Skip to content

SSR: onDestroy callbacks are skipped when the render throws #18584

Description

@Nic-Polumeyv

Describe the bug

Looking into lifecycle internals for #18583 led me here. If a server render throws partway through, onDestroy callbacks registered by components that already ran are never called: #close_render is the only caller of #collect_on_destroy and is unreachable on the error path. The abort-signal cleanup in the same finally blocks does run (renderer.js:643, renderer.js:669), so getAbortSignal().addEventListener('abort', ...) fires on failed renders while onDestroy doesn't.

#10296 added server onDestroy so per-request resources get released, and a failed render is where skipping that hurts most, since errors tend to repeat across requests. If success-only cleanup is intended, I couldn't find it documented.

Affects both the sync and async (top-level await) render paths.

Reproduction

<!-- Parent.svelte -->
<script>
	import { onDestroy, getAbortSignal } from 'svelte';
	import Child from './Child.svelte';
	let { fail } = $props();
	onDestroy(() => console.log('onDestroy fired'));
	getAbortSignal().addEventListener('abort', () => console.log('abort fired'));
</script>
{#if fail}<Child />{/if}

<!-- Child.svelte -->
<script>
	throw new Error('boom');
</script>

render(Parent, { props: { fail: false } }) logs both callbacks. With fail: true it logs only abort fired.

Logs

--- successful render ---
onDestroy fired
abort fired
--- failing render ---
abort fired
threw: boom

System Info

svelte 5.56.7, node 22.23.1

Severity

annoyance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions