sveltejs / svelte

web development for the rest of us
https://svelte.dev
MIT License
79.83k stars 4.24k forks source link

Svelte 5: How to debug effect_update_depth_exceeded in a Svelte 4 library? #13192

Closed cshaa closed 1 month ago

cshaa commented 2 months ago

Describe the bug

My team developed an extensive graphing library in Svelte 4. However, when trying to use the library in a Svelte 5 app, half of the features don't work and the error effect_update_depth_exceeded appears in the console. However, the stack trace is only four functions, and all of them are Svelte internals:

effect_update_depth_exceeded   errors.js:187
    infinite_loop_guard        runtime.js:461
    flush_queued_root_effects  runtime.js:475
    process_deferred           runtime.js:539

Therefore, it seems almost impossible to debug. This issue means that our library is impossible to use in any Svelte 5 application.

Reproduction

To inspect the project directly:

git clone git@github.com:chartium/libchartium.git
cd libchartium
git checkout svelte5
pnpm install
pnpm dev

Alternatively, for a simpler setup, check out my demo project, which uses the current version of libchartium available from NPM.

System Info

```shell System: OS: Linux 6.8 elementary OS 7.1 Horus 7.1 Horus CPU: (16) x64 AMD Ryzen 7 5700U with Radeon Graphics Memory: 6.00 GB / 14.97 GB Container: Yes Shell: 3.7.1 - /usr/bin/fish Binaries: Node: 22.8.0 - ~/.nvm/versions/node/v22.8.0/bin/node npm: 10.8.2 - ~/.nvm/versions/node/v22.8.0/bin/npm pnpm: 9.1.1 - ~/.nvm/versions/node/v22.8.0/bin/pnpm bun: 1.1.27 - ~/.bun/bin/bun Browsers: Brave Browser: 128.1.69.162 npmPackages: svelte: 5.0.0-next.244 => 5.0.0-next.244 ```

Severity

blocking an upgrade

paoloricciuti commented 2 months ago

Generally speaking this happens when you read and write to some state in an effect. Since your app is still in svelte 4 you might want to look for reactive blocks where you might modify state.

I know it's kinda hard to debug with this stacktrace but i don't know if we can do better (i'll try to think of some solution) because it's literally an infinite loop we are guarding against so the stack trace is very very big.

kyoshino commented 2 months ago

I also have had some difficult time solving infinite loops that weren’t exist in Svelte 4. For example, the timerId below was previously untracked:

$: {
  globalThis.clearTimeout(timerId);
  timerId = globalThis.setTimeout(() => {
    show = false;
  }, 5000);
}

but Svelte 5 tracks it, causing effect_update_depth_exceeded with no clue. I needed to use untrack to solve the error:

$effect(() => {
  untrack(() => {
    globalThis.clearTimeout(timerId);
  });
  timerId = globalThis.setTimeout(() => {
    show = false;
  }, 5000);
});

It would be really nice to have a clear stack trace so that we can quickly identify and fix such pitfalls.

paoloricciuti commented 2 months ago

I also have had some difficult time solving infinite loops that weren’t exist in Svelte 4. For example, the timerId below was previously untracked:

$: {
  globalThis.clearTimeout(timerId);
  timerId = globalThis.setTimeout(() => {
    show = false;
  }, 5000);
}

but Svelte 5 tracks it, causing effect_update_depth_exceeded with no clue. I needed to use untrack to solve the error:

$effect(() => {
  untrack(() => {
    globalThis.clearTimeout(timerId);
  });
  timerId = globalThis.setTimeout(() => {
    show = false;
  }, 5000);
});

It would be really nice to have a clear stack trace so that we can quickly identify and fix such pitfalls.

Unless you need to react to timerId changing (showing it in the ui or rerun an effect when it changes) you can avoid using state with timerId

wackbyte commented 2 months ago

I think you should be doing this so that the timer is cleared on unmount:

$effect(() => {
  timerId = globalThis.setTimeout(() => {
    show = false;
  }, 5000);
  return () => {
    globalThis.clearTimeout(timerId);
  };
});
kyoshino commented 2 months ago

I think you should be doing this so that the timer is cleared on unmount:

I understand what you mean, but it’s a different meaning from my purpose of clearTimeout 🙂 The original code is here.