Repository navigation
fix(effect): handle synchronous re-entrancy in Effect.cached* - #8977
Merged
Merged
Conversation
🦋 Changeset detectedLatest commit: 41fde49 The changes in this PR will be included in the next version bump. This PR includes changesets to release 32 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
Bundle Size AnalysisGenerated from PR build output; treat the content below as untrusted.
|
tim-smart
marked this pull request as ready for review
October 10, 2026 07:15
tim-smart
enabled auto-merge (squash)
October 10, 2026 07:15
This was referenced Oct 15, 2026
brandhaug
added a commit
to brandhaug/b2b-saas-starter
that referenced
this pull request
Oct 15, 2026
## pnpm-workspace.yaml (default) ## Dependency Updates | Package | From | To | Type | | --- | --- | --- | --- | | `@inlang/plugin-message-format` | 4.5.0 | 4.5.2 | patch | | `@tanstack/react-query` | 5.104.1 | 5.104.2 | patch | | `effect` | 4.0.2 | 4.0.3 | patch | ## Release Notes <details> <summary><b>@<!---->tanstack/react-query</b> (5.104.1 → 5.104.2)</summary> ### Patch Changes - Updated dependencies [[`03e8c39`](TanStack/query@03e8c39)]: - @<!---->tanstack/[email protected] </details> <details> <summary><b>effect</b> (4.0.2 → 4.0.3)</summary> ### Patch Changes - [#8977](Effect-TS/effect#8977) [`b4e5af8`](Effect-TS/effect@b4e5af8) Thanks @<!---->kitlangton! - Prevent crashes and premature interruption when `Effect.cached`, `Effect.cachedWithTTL`, and `Effect.cachedInvalidateWithTTL` are synchronously re-entered. - [#8982](Effect-TS/effect#8982) [`b8d6e7a`](Effect-TS/effect@b8d6e7a) Thanks @<!---->Yi-111-a! - Export `CliOutput.displayWidth` to measure terminal-cell widths using the same logic as built-in CLI help and tables. - [#8933](Effect-TS/effect#8933) [`89d5c88`](Effect-TS/effect@89d5c88) Thanks @<!---->tim-smart! - Respect `NO_COLOR` in CLI prompts and the command wizard, suppressing color and style escapes while preserving cursor and redraw controls. - [#8941](Effect-TS/effect#8941) [`66257d2`](Effect-TS/effect@66257d2) Thanks @<!---->Tanner-Scadden! - Hand cluster shards off one at a time when `Sharding` shuts down, after their entities and singletons have stopped, so live runners can take them over sooner. The handoff is best effort: its wait is bounded, and if it times out, `releaseAll` attempts to release the remaining locks. A runner that is shutting down no longer acquires new shards, and releasing a shard no longer waits forever on an entity whose id is also active on another shard. - [#8940](Effect-TS/effect#8940) [`cd2ab19`](Effect-TS/effect@cd2ab19) Thanks @<!---->Tanner-Scadden! - Fix cluster shard handoffs. Interrupts and stream acknowledgements now reach requests still draining on the previous owner, and `RpcServer` accepts acknowledgements for active streams after a client's EOF. Volatile requests sent duri …[full notes](https://github.com/Effect-TS/effect/releases/tag/effect%404.0.3) </details> --- *This PR was auto-generated by [catalog-update-action](https://github.com/brandhaug/catalog-update-action).* Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This branch had an error being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Found while upgrading a large app to Effect 4.0.1.
In
makeCachedUnsafe(shared byEffect.cached,Effect.cachedWithTTL, andEffect.cachedInvalidateWithTTL), a cache miss publishescurrent = runwithrun.fiber = undefinedand then callsrun.fiber = forkUnsafe(fiber, ..., true, true). Becauseimmediateistrue,forkUnsafeevaluates the cached effect synchronously before returning the new fiber.If that synchronous evaluation wakes another fiber that immediately evaluates the same cached effect before
forkUnsafereturns (for example, completing aDeferredwhose waiter runs a scope finalizer that calls the cached effect), the second fiber seescurrent !== undefinedand callsjoin(fiber, current)whilerun.fiberis stillundefined:fiberJoin(run.fiber!)throwsTypeError: Cannot read properties of undefined (reading '_exit').onExitUnsafehandler decrementsrun.awaitersfrom1to0(because the initiating caller only incrementsrun.awaitersafterforkUnsafereturns), clearscurrent = undefined, and callsfiberInterrupt(undefined).fiberInterruptAsisdualand checkshasProperty(args[0], FiberTypeId), so passingundefinedreturns a curried function instead of anEffect, which fails the run loop with:Minimal reproduction:
Fix
run.fiber = new FiberImpl(fiber.context)onCachedRunbefore publishingcurrent = runand callingrun.fiber.evaluate(...), sorun.fiberis always defined when a re-entrant caller joins or interrupts it.run.awaitersto1for the initiating caller and incrementcurrent.awaiterswhen subsequent callers join, so a re-entrant waiter that joins and is interrupted during synchronous startup cannot droprun.awaitersto0and abandon the run whileevaluateis still on the stack.fiber === current.fiber), returnfiberJoin(current.fiber)without incrementingcurrent.awaitersor registering an abandonment finalizer. A direct self-call suspends on self-join instead of crashing, and interrupting all external callers still dropsrun.awaitersto0and interruptsrun.fiber.Alternatives
immediate = false(forkUnsafe(fiber, ..., false, true)). This would assignrun.fiberand register the first caller before the cached body runs a single step, eliminating synchronous re-entrancy during startup. However, it gives up synchronous evaluation on cache misses:run.fiber._exitwould never be populated synchronously, every first call would suspend until the scheduler flushes, and callers relying on synchronous startup effects (such asEffect.forkChild(cached, { startImmediately: true })) would observe different ordering.CachedRunwith aDeferredorLatch(similar toScopedCache). Waiting on aDeferredavoids readingrun.fiberinjoin, but it allocates an extra coordination object per cache miss and still requiresrun.fiberto be defined if a re-entrant waiter is interrupted during synchronous startup.Cache,RcRef, andResourceMap.RcRefandResourceMapalso publish an in-flight state withfiber: undefined as anybeforeEffect.runForkWithevaluates the acquire/lookup effect synchronously, andCache.getrunsnew EntryImpl(which callsforkUnsafe(..., true, true)) before inserting the entry intoself.map. Keeping this PR scoped tomakeCachedUnsafekeeps the fix minimal, and the same pre-allocated fiber pattern can be applied to those modules if desired.Tests
Closes EFF-1910