Skip to content

fix: effect teardown on failed setup, computed throw recovery - #126

Open
nickchomey wants to merge 6 commits into
stackblitz:masterfrom
nickchomey:bug-fixes
Open

nickchomey wants to merge 6 commits into
stackblitz:masterfrom
nickchomey:bug-fixes

Conversation

@nickchomey

@nickchomey nickchomey commented Sep 2, 2026 •

Copy link
Copy Markdown

This PR fixes a few related bugs. It comes in 3 pairs of red tests/green fix commits.

Note that this Supersedes/rebases #119, which fixes #118. I didnt create an issue for the two new bugs i found and fixed

Bug 1 - effect / effectScope leave live subscriptions after failed setup

Tests:

  • Stopped effect stays stopped. Reads signal, stops mid-run, reads another signal. Ends with no deps, never re-runs.
  • Failed effect setup cleaned up. Reads signal then throws. Throw propagates. Later writes do not re-run.
  • Failed scope setup cleans up children. Creates child effect then throws. Throw propagates. Later writes do not re-run child.

Fix:

  • Failed setup leaves nothing behind.
  • Inactive subscribers ignore later reads in same run.

Bug 2 - computed throwing on first evaluation never recovers

Tests:

  • Throwing computed recovers. Throws while dependency is 0, read inside catching effect. After dependency becomes 5, effect re-runs and reads 10.

Fix:

  • Failed evaluation still subscribes.
  • Dependency change triggers re-evaluation.

Bug 3 - throwing computed returns stale undefined instead of re-throwing

Tests:

  • Unrelated re-run still throws. Subscriber also reads unrelated signal. Write to unrelated signal re-runs subscriber without changing computed deps. Same error observed, getter not re-invoked.
  • Direct re-read still throws. Two consecutive reads throw with single getter call. After dep change, correct value returned.

Fix:

  • Failed evaluation is retained.
  • Unchanged dependencies re-throw, changed dependencies re-evaluate.

@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@nickchomey nickchomey changed the title Bug fixes fix: effect teardown on failed setup, computed throw recovery Sep 2, 2026

medz commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

I checked this PR at d9a310a. First-evaluation recovery and cached rethrows work. Two related cases remain:

1. Retry semantics after restoring writes

import { signal, computed } from 'alien-signals';

const source = signal('a');
let calls = 0;
const value = computed(() => {
  const current = source();
  if (++calls === 1) throw new Error('boom');
  return current;
});

try { value(); } catch {}
source('b');
source('a');
try { console.log(value()); }
catch (error) { console.log(error.message); } // boom
console.log(calls); // 1

Should a → b → a permit another attempt after a failed evaluation, or intentionally retain the cached error because the final dependency value is unchanged? Removing source('a') retries successfully.

2. Error after initial success bypasses the effect's catch and prevents recovery

import { signal, computed, effect } from 'alien-signals';

const shouldThrow = signal(false);
let getterCalls = 0;
const value = computed(() => {
  getterCalls++;
  if (shouldThrow()) throw new Error('boom');
  return 1;
});
const seen = [];
const stop = effect(() => {
  try { seen.push(value()); }
  catch { seen.push('error'); }
});

try { shouldThrow(true); }
catch (error) { console.log('setter threw:', error.message); }
shouldThrow(false);
console.log({ seen, getterCalls });
stop();

Expected: neither setter throws, seen becomes [1, 'error', 1], and getterCalls is 3.

Actual: the first setter throws boom; after the second setter, seen is still [1] and getterCalls is 2. The effect never reaches its catch or observes recovery.

The second case also reproduces on 3.2.1 and master (c00e639).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stopped effects can be re-subscribed and failed effect/effectScope setup can leak nodes

2 participants