Skip to content

Commit a6df4eb

Browse files
authored
feat: add infinite loop effect callstack (#13231)
This adds a new dev-time only `dev_effect_stack` variable, which executed effects are pushed to and eventually cleared out after everything's settled. If it doesn't settle however, i.e. you run into an infinite loop, the last ten effects are printed out so you get an idea where the error is coming from. For proper source mapping I also had add location info to the generated effects. Closes #13192
1 parent e5c840c commit a6df4eb

File tree

5 files changed

+58
-3
lines changed

5 files changed

+58
-3
lines changed

.changeset/violet-bats-brake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: add infinite loop effect callstack

packages/svelte/src/compiler/phases/3-transform/client/visitors/ExpressionStatement.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export function ExpressionStatement(node, context) {
1515
const callee = rune === '$effect' ? '$.user_effect' : '$.user_pre_effect';
1616
const func = /** @type {Expression} */ (context.visit(node.expression.arguments[0]));
1717

18-
return b.stmt(b.call(callee, /** @type {Expression} */ (func)));
18+
const expr = b.call(callee, /** @type {Expression} */ (func));
19+
expr.callee.loc = node.expression.callee.loc; // ensure correct mapping
20+
21+
return b.stmt(expr);
1922
}
2023
}
2124

packages/svelte/src/internal/client/runtime.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export function set_is_destroying_effect(value) {
6262
let current_queued_root_effects = [];
6363

6464
let flush_count = 0;
65+
/** @type {Effect[]} Stack of effects, dev only */
66+
let dev_effect_stack = [];
6567
// Handle signal reactivity tree dependencies and reactions
6668

6769
/** @type {null | Reaction} */
@@ -443,8 +445,11 @@ export function update_effect(effect) {
443445
execute_effect_teardown(effect);
444446
var teardown = update_reaction(effect);
445447
effect.teardown = typeof teardown === 'function' ? teardown : null;
446-
447448
effect.version = current_version;
449+
450+
if (DEV) {
451+
dev_effect_stack.push(effect);
452+
}
448453
} catch (error) {
449454
handle_error(/** @type {Error} */ (error), effect, current_component_context);
450455
} finally {
@@ -460,7 +465,25 @@ export function update_effect(effect) {
460465
function infinite_loop_guard() {
461466
if (flush_count > 1000) {
462467
flush_count = 0;
463-
e.effect_update_depth_exceeded();
468+
if (DEV) {
469+
try {
470+
e.effect_update_depth_exceeded();
471+
} catch (error) {
472+
// stack is garbage, ignore. Instead add a console.error message.
473+
define_property(error, 'stack', {
474+
value: ''
475+
});
476+
// eslint-disable-next-line no-console
477+
console.error(
478+
'Last ten effects were: ',
479+
dev_effect_stack.slice(-10).map((d) => d.fn)
480+
);
481+
dev_effect_stack = [];
482+
throw error;
483+
}
484+
} else {
485+
e.effect_update_depth_exceeded();
486+
}
464487
}
465488
flush_count++;
466489
}
@@ -541,6 +564,9 @@ function process_deferred() {
541564
flush_queued_root_effects(previous_queued_root_effects);
542565
if (!is_micro_task_queued) {
543566
flush_count = 0;
567+
if (DEV) {
568+
dev_effect_stack = [];
569+
}
544570
}
545571
}
546572

@@ -682,6 +708,9 @@ export function flush_sync(fn) {
682708
}
683709

684710
flush_count = 0;
711+
if (DEV) {
712+
dev_effect_stack = [];
713+
}
685714

686715
return result;
687716
} finally {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
client: [
5+
{ str: '$effect.pre', strGenerated: '$.user_pre_effect' },
6+
{ str: '$effect', strGenerated: '$.user_effect' }
7+
],
8+
server: []
9+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script lang="ts">
2+
$effect(() => {
3+
foo;
4+
});
5+
6+
$effect.pre(() => {
7+
bar;
8+
});
9+
</script>

0 commit comments

Comments
 (0)