fix: capture infinite_loop_guard in error boundary (#14534)

* fix: capture infinite_loop_guard in error boundary

* fix
pull/14537/head
Dominic Gannaway 3 weeks ago committed by GitHub
parent d5a28a01a1
commit b5588523fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: capture infinite_loop_guard in error boundary

@ -48,6 +48,9 @@ let scheduler_mode = FLUSH_MICROTASK;
// Used for handling scheduling
let is_micro_task_queued = false;
/** @type {Effect | null} */
let last_scheduled_effect = null;
export let is_flushing_effect = false;
export let is_destroying_effect = false;
@ -532,27 +535,47 @@ export function update_effect(effect) {
}
}
function log_effect_stack() {
// eslint-disable-next-line no-console
console.error(
'Last ten effects were: ',
dev_effect_stack.slice(-10).map((d) => d.fn)
);
dev_effect_stack = [];
}
function infinite_loop_guard() {
if (flush_count > 1000) {
flush_count = 0;
if (DEV) {
try {
e.effect_update_depth_exceeded();
} catch (error) {
try {
e.effect_update_depth_exceeded();
} catch (error) {
if (DEV) {
// stack is garbage, ignore. Instead add a console.error message.
define_property(error, 'stack', {
value: ''
});
// eslint-disable-next-line no-console
console.error(
'Last ten effects were: ',
dev_effect_stack.slice(-10).map((d) => d.fn)
);
dev_effect_stack = [];
}
// Try and handle the error so it can be caught at a boundary, that's
// if there's an effect available from when it was last scheduled
if (last_scheduled_effect !== null) {
if (DEV) {
try {
handle_error(error, last_scheduled_effect, null, null);
} catch (e) {
// Only log the effect stack if the error is re-thrown
log_effect_stack();
throw e;
}
} else {
handle_error(error, last_scheduled_effect, null, null);
}
} else {
if (DEV) {
log_effect_stack();
}
throw error;
}
} else {
e.effect_update_depth_exceeded();
}
}
flush_count++;
@ -637,8 +660,10 @@ function process_deferred() {
const previous_queued_root_effects = queued_root_effects;
queued_root_effects = [];
flush_queued_root_effects(previous_queued_root_effects);
if (!is_micro_task_queued) {
flush_count = 0;
last_scheduled_effect = null;
if (DEV) {
dev_effect_stack = [];
}
@ -657,6 +682,8 @@ export function schedule_effect(signal) {
}
}
last_scheduled_effect = signal;
var effect = signal;
while (effect.parent !== null) {
@ -776,6 +803,7 @@ export function flush_sync(fn) {
}
flush_count = 0;
last_scheduled_effect = null;
if (DEV) {
dev_effect_stack = [];
}

@ -0,0 +1,17 @@
<script>
let count = $state(0);
let clicked = $state(false);
function increment() {
clicked = true;
count++;
}
$effect(() => {
if (clicked) {
count++;
}
});
</script>
<button onclick={increment}>{count}</button>

@ -0,0 +1,13 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target }) {
let btn = target.querySelector('button');
btn?.click();
flushSync();
assert.htmlEqual(target.innerHTML, `<div class="error">An error occurred!</div>`);
}
});

@ -0,0 +1,10 @@
<script>
import Child from "./Child.svelte"
</script>
<svelte:boundary>
<Child />
{#snippet failed()}
<div class="error">An error occurred!</div>
{/snippet}
</svelte:boundary>
Loading…
Cancel
Save