From 8bbeed9b8d9409ff3e819fad332a14893534e108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Mon, 20 Jul 2026 02:15:39 +0200 Subject: [PATCH] Fix scope registry race condition --- packages/effect-fc-next/src/Component.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/effect-fc-next/src/Component.ts b/packages/effect-fc-next/src/Component.ts index d457468..8aaa622 100644 --- a/packages/effect-fc-next/src/Component.ts +++ b/packages/effect-fc-next/src/Component.ts @@ -1,6 +1,6 @@ /** biome-ignore-all lint/complexity/noBannedTypes: {} is the default type for React props */ /** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */ -import { Context, type Duration, Effect, Equivalence, Exit, Fiber, Function, HashMap, identity, Layer, Option, Pipeable, Predicate, Ref, Scope, Tracer } from "effect" +import { Cause, Context, type Duration, Effect, Equal, Equivalence, Exit, Fiber, Function, HashMap, identity, Layer, Option, Pipeable, Predicate, Ref, Scope, Tracer } from "effect" import * as React from "react" @@ -698,13 +698,13 @@ export const useScope = Effect.fnUntraced(function*( ScopeMap as unknown as Effect.Effect, scopeMap => scopeMap.ref, )), - Effect.let("key", () => ({})), + Effect.let("key", () => Equal.byReference({})), Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy)), Effect.tap(({ scopeMapRef, key, scope }) => - Ref.update(scopeMapRef, HashMap.set(key, { + Ref.update(scopeMapRef, HashMap.set(key, Equal.byReference({ scope, closeFiber: Option.none(), - })) + }))) ), // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList )), deps) @@ -724,13 +724,18 @@ export const useScope = Effect.fnUntraced(function*( () => Effect.runSyncWith(contextRef.current)(Effect.flatMap( Effect.sleep(options?.finalizerExecutionDebounce ?? defaultOptions.finalizerExecutionDebounce).pipe( Effect.andThen(Scope.close(scope, Exit.void)), - Effect.onExit(() => Ref.update(ref, HashMap.remove(key))), + Effect.onExit(exit => Exit.match(exit, { + onSuccess: () => Ref.update(ref, HashMap.remove(key)), + onFailure: cause => Cause.hasInterruptsOnly(cause) + ? Effect.void + : Ref.update(ref, HashMap.remove(key)), + })), Effect.forkDetach, ), - fiber => Ref.update(ref, HashMap.set(key, { + fiber => Ref.update(ref, HashMap.set(key, Equal.byReference({ scope, closeFiber: Option.some(fiber), - })), + }))), )) ), )