Skip to content

Fast renderer: clean up removed components before new effects run - #68

Merged
maartenbreddels merged 2 commits into
masterfrom
fix/fast-cleanup-before-new-effects
Sep 29, 2026
Merged

maartenbreddels merged 2 commits into
masterfrom
fix/fast-cleanup-before-new-effects

Conversation

@maartenbreddels

Copy link
Copy Markdown
Contributor

Part of the audit of REACTON_FAST=1 against the default renderer. This is one small PR per finding; this one is D5.

Problem

When a component replaces another one in a container (a conditional child, or a changed key), the default renderer runs the effect cleanups of the removed component before the effects of the new one. The fast renderer removed stale elements only after it had reconciled the whole context, so the new effect ran first. Two things went wrong:

  • An effect that sets a global value and resets it in its cleanup ended with the reset value (None) in fast mode, but with the new value in default mode.
  • When two cleanups raised, a different exception reached use_exception.

Fix

  • The fast renderer now runs the effect cleanups of stale components at the same point in the walk as the default renderer, in sorted key order, including nested components.
  • Their widgets are still closed later, after the parent got its new children. This is the fast renderer's existing, deliberate difference (D5c in benchmarks/README.md).
  • The default renderer is not changed. It never reads the two new ComponentContext fields.

Tests

  • Two new tests in core_test.py. Both fail on master with REACTON_FAST=1 and pass in both modes.
  • fuzz_test.py now also logs effect cleanups. Before, it compared effect runs only, so it could not see this class of bug. With cleanups logged, 7 of its 12 cases fail on master; with this PR all 12 pass. A reviewer's extended fuzz run found 0 of 400 seeds that differ, against 217 on master.
  • Full suite: 211 passed (default), 212 passed (fast).

Review

Crossreview with Astra (GPT-6), Opus and GLM, in 2 rounds. All three approve the final version.

  • Round 1 found no ordering bug. It found:
    • pop(0) made the cleanup quadratic;
    • the new fields were not freed on close;
    • self.context was not restored;
    • the fuzz test could not see cleanups.
  • All of these are fixed in the second commit, and round 2 confirmed the fixes.
  • Open and not blocking:
    • If reconciliation aborts with an internal error between the early cleanup and the removal, a component can stay mounted with its effect cleaned up. A reviewer could only trigger this by injecting an internal error.
    • .shared() elements are still broken in both renderers, as they are on master. That needs its own issue.

The fix was written by a codex worker and reviewed and adjusted by me.

🤖 Generated with Claude Code

maartenbreddels and others added 2 commits September 29, 2026 11:32
When a component replaces another one in a container, the default
renderer runs the effect cleanups of the removed component before the
effects of the new one. The fast renderer removed stale elements only
after the whole context was reconciled, so the new effect ran first.
An effect that sets a global value and resets it in its cleanup then
ended with the reset value, and with two failing cleanups a different
exception reached use_exception.

The fast renderer now runs the effect cleanups of stale components at
the same point as the default renderer. Their widgets are still closed
later, after the parent got its new children, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review found that taking stale keys from the front of a list is
quadratic in the number of stale keys, that a closed context kept the
removed keys alive through its new bookkeeping fields, and that the
cleanup loop left self.context changed. The fuzz test only logged effect
runs, so it could not see a difference in cleanup order: with cleanups
logged, 7 of its 12 cases fail on master and all pass with this change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@maartenbreddels
maartenbreddels force-pushed the fix/fast-cleanup-before-new-effects branch from e4b9314 to f474df4 Compare September 29, 2026 09:33
@maartenbreddels
maartenbreddels merged commit 9e88876 into master Sep 29, 2026
24 checks passed
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.

1 participant