Skip to content

Log effect exceptions and clear the render lock owner - #56

Merged
maartenbreddels merged 1 commit into
masterfrom
fix/log-effect-exceptions
Sep 3, 2026
Merged

Log effect exceptions and clear the render lock owner#56
maartenbreddels merged 1 commit into
masterfrom
fix/log-effect-exceptions

Conversation

@maartenbreddels

Copy link
Copy Markdown
Contributor

Why

Two things found while chasing a production crash (reacton PR #54).

  • An exception raised by an effect or an effect cleanup is collected and rendered as the error page, but only ever logged at INFO ("Exception occurred, rendering error message"). Component-body exceptions are logged with logger.exception in _render. In a server deployment with the reacton logger at WARNING, an effect crash therefore never reaches the logs or Sentry: the crash behind PR Survive a widget/component slot flip inside one render call #54 was invisible for months, users saw the traceback page and nothing else recorded it.
  • render() sets _lock_thread after acquiring thread_lock and never clears it. close() takes the lock without setting it. The recursion guard at the top of render() compares _lock_thread to the current thread while the lock is held, so a thread that merely rendered last, while close() runs on another thread, gets a false RuntimeError("Recursive render detected") instead of waiting.

What

  • logger.exception("Effect %r raised exception %r", ...) and the cleanup variant at every catch site in both renderers, no control-flow change.
  • _lock_thread = None in the finally of render(), before the lock is released.
  • Three tests: an effect exception and a cleanup exception each produce one ERROR record with exc_info; a close() on another thread makes render() wait instead of raising.

185 passed on both renderers.

🤖 Generated with Claude Code

An exception in an effect (or an effect cleanup) was only collected into
exceptions_self and then shown as an error page. The render loop logged it
at INFO, so a production incident left nothing at WARNING or above: an
effect that broke the widget tree was invisible in server logs and in
Sentry. Component-body exceptions were already logged with
logger.exception, so effects now do the same at every catch site, in both
renderers. Control flow is unchanged.

render() set _lock_thread when it took the lock but never cleared it. The
recursion guard therefore fired on a stale owner: thread A rendered last,
thread B holds the lock (close() takes it without setting _lock_thread),
and A calling render() got "Recursive render detected" instead of waiting.
Clearing _lock_thread before the lock is released makes the guard describe
the current owner only.
@maartenbreddels
maartenbreddels merged commit 40a90a8 into master Sep 3, 2026
23 of 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