Skip to content

RouteProgress: timer leak on mid-navigation unmount (missing onDestroy cleanup) #384

@stickerdaniel

Description

@stickerdaniel

Problem

src/lib/components/RouteProgress.svelte schedules four cascade setTimeout / setInterval timers (startDelayTimer, trickleTimer, completeTimer, fadeTimer) inside $effect reactions to navigating, but it does not clean them up on component unmount. If the component is destroyed mid-navigation (e.g. user logs out, sub-tree re-mounts, top-level layout swap), the timers keep firing against detached state until they self-resolve.

The clearTimers() helper exists but is only called from startProgress() / completeProgress() entry points. There is no onDestroy(clearTimers).

Repro

  1. Trigger navigation that takes >100ms (so startDelayTimer fires).
  2. Before navigation completes, unmount the layout containing <RouteProgress /> (e.g. force a full reload, switch auth state).
  3. Observe in DevTools that trickleTimer continues writing to detached progress state.

Suggested fix

Two-line cleanup + a runed simplification of the three setTimeouts:

<script lang="ts">
  import { useDebounce } from 'runed';
  import { onDestroy } from 'svelte';

  // Replace startDelayTimer/completeTimer/fadeTimer with cancellable useDebounce
  // calls; keep trickleTimer as setInterval since it needs the recurring tick.
  const scheduleShow = useDebounce(showProgress, START_DELAY_MS);
  const scheduleFadeStart = useDebounce(() => {
    opacity = 0;
    scheduleFadeFinish();
  }, SPEED);
  const scheduleFadeFinish = useDebounce(() => {
    showBar = false; opacity = 1; progress = 0;
  }, SPEED);

  function clearTimers() {
    scheduleShow.cancel();
    scheduleFadeStart.cancel();
    scheduleFadeFinish.cancel();
    if (trickleTimer) { clearInterval(trickleTimer); trickleTimer = null; }
  }
  onDestroy(clearTimers);
</script>

Eliminates 3 local timer variables, gets auto-cleanup of the debounce timers on unmount, and the explicit onDestroy(clearTimers) handles the interval.

Working diff verified in a downstream fork; happy to send a PR if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions