diff --git a/packages/effect/src/FiberHandle.ts b/packages/effect/src/FiberHandle.ts index 6ba1aa8d46..4534f0fefe 100644 --- a/packages/effect/src/FiberHandle.ts +++ b/packages/effect/src/FiberHandle.ts @@ -276,6 +276,16 @@ export const clear = (self: FiberHandle): Effect.Effect => }) ) +const constInterruptedFiber = (function() { + let fiber: Fiber.RuntimeFiber | undefined = undefined + return () => { + if (fiber === undefined) { + fiber = Effect.runFork(Effect.interrupt) + } + return fiber + } +})() + /** * Run an Effect and add the forked fiber to the FiberHandle. * When the fiber completes, it will be removed from the FiberHandle. @@ -308,7 +318,7 @@ export const run: { if (self.state._tag === "Closed") { return Effect.interrupt } else if (self.state.fiber !== undefined && options?.onlyIfMissing === true) { - return set(self, Effect.runFork(Effect.never), options) + return Effect.sync(constInterruptedFiber) } return Effect.uninterruptibleMask((restore) => Effect.tap( @@ -324,7 +334,7 @@ export const run: { if (self.state._tag === "Closed") { return Effect.interrupt } else if (self.state.fiber !== undefined && options?.onlyIfMissing === true) { - return set(self, Effect.runFork(Effect.never), options) + return Effect.sync(constInterruptedFiber) } return Effect.uninterruptibleMask((restore) => Effect.tap( @@ -390,6 +400,11 @@ export const runtime: ( } | undefined ) => { + if (self.state._tag === "Closed") { + return constInterruptedFiber() + } else if (self.state.fiber !== undefined && options?.onlyIfMissing === true) { + return constInterruptedFiber() + } const fiber = runFork(effect, options) unsafeSet(self, fiber, options) return fiber diff --git a/packages/effect/src/FiberMap.ts b/packages/effect/src/FiberMap.ts index 16ae906536..9ed52027d2 100644 --- a/packages/effect/src/FiberMap.ts +++ b/packages/effect/src/FiberMap.ts @@ -376,6 +376,16 @@ export const clear = (self: FiberMap): Effect.Effect => Fiber.interrupt(fiber)) }) +const constInterruptedFiber = (function() { + let fiber: Fiber.RuntimeFiber | undefined = undefined + return () => { + if (fiber === undefined) { + fiber = Effect.runFork(Effect.interrupt) + } + return fiber + } +})() + /** * Run an Effect and add the forked fiber to the FiberMap. * When the fiber completes, it will be removed from the FiberMap. @@ -410,8 +420,8 @@ export const run: { return Effect.suspend(() => { if (self.state._tag === "Closed") { return Effect.interrupt - } else if (unsafeHas(self, key) && options?.onlyIfMissing === true) { - return set(self, key, Effect.runFork(Effect.never), options) + } else if (options?.onlyIfMissing === true && unsafeHas(self, key)) { + return Effect.sync(constInterruptedFiber) } return Effect.uninterruptibleMask((restore) => Effect.tap( @@ -428,8 +438,8 @@ export const run: { Effect.suspend(() => { if (self.state._tag === "Closed") { return Effect.interrupt - } else if (unsafeHas(self, key) && options?.onlyIfMissing === true) { - return set(self, key, Effect.runFork(Effect.never), options) + } else if (options?.onlyIfMissing === true && unsafeHas(self, key)) { + return Effect.sync(constInterruptedFiber) } return Effect.uninterruptibleMask((restore) => Effect.tap( @@ -495,6 +505,11 @@ export const runtime: ( } | undefined ) => { + if (self.state._tag === "Closed") { + return constInterruptedFiber() + } else if (options?.onlyIfMissing === true && unsafeHas(self, key)) { + return constInterruptedFiber() + } const fiber = runFork(effect, options) unsafeSet(self, key, fiber, options) return fiber diff --git a/packages/effect/test/FiberHandle.test.ts b/packages/effect/test/FiberHandle.test.ts index 21b2f0c432..dee7b82472 100644 --- a/packages/effect/test/FiberHandle.test.ts +++ b/packages/effect/test/FiberHandle.test.ts @@ -1,4 +1,4 @@ -import { Effect, Ref } from "effect" +import { Effect, Exit, Ref } from "effect" import * as it from "effect-test/utils/extend" import * as FiberHandle from "effect/FiberHandle" import { assert, describe } from "vitest" @@ -34,12 +34,12 @@ describe("FiberHandle", () => { onlyIfMissing: true }) yield* _(Effect.yieldNow()) - assert.strictEqual(yield* _(Ref.get(ref)), 2) + assert.strictEqual(yield* _(Ref.get(ref)), 1) }), Effect.scoped ) - assert.strictEqual(yield* _(Ref.get(ref)), 3) + assert.strictEqual(yield* _(Ref.get(ref)), 2) })) it.scoped("join", () => @@ -50,4 +50,28 @@ describe("FiberHandle", () => { const result = yield* _(FiberHandle.join(handle), Effect.flip) assert.strictEqual(result, "fail") })) + + it.scoped("onlyIfMissing", () => + Effect.gen(function*(_) { + const handle = yield* _(FiberHandle.make()) + const fiberA = yield* _(FiberHandle.run(handle, Effect.never)) + const fiberB = yield* _(FiberHandle.run(handle, Effect.never, { onlyIfMissing: true })) + const fiberC = yield* _(FiberHandle.run(handle, Effect.never, { onlyIfMissing: true })) + yield* _(Effect.yieldNow()) + assert.isTrue(Exit.isInterrupted(yield* _(fiberB.await))) + assert.isTrue(Exit.isInterrupted(yield* _(fiberC.await))) + assert.strictEqual(fiberA.unsafePoll(), null) + })) + + it.scoped("runtime onlyIfMissing", () => + Effect.gen(function*(_) { + const run = yield* _(FiberHandle.makeRuntime()) + const fiberA = run(Effect.never) + const fiberB = run(Effect.never, { onlyIfMissing: true }) + const fiberC = run(Effect.never, { onlyIfMissing: true }) + yield* _(Effect.yieldNow()) + assert.isTrue(Exit.isInterrupted(yield* _(fiberB.await))) + assert.isTrue(Exit.isInterrupted(yield* _(fiberC.await))) + assert.strictEqual(fiberA.unsafePoll(), null) + })) }) diff --git a/packages/effect/test/FiberMap.test.ts b/packages/effect/test/FiberMap.test.ts index f89263a47f..246f63b451 100644 --- a/packages/effect/test/FiberMap.test.ts +++ b/packages/effect/test/FiberMap.test.ts @@ -72,4 +72,28 @@ describe("FiberMap", () => { yield* _(Scope.close(scope, Exit.void)) assert.strictEqual(yield* _(FiberMap.size(set)), 0) })) + + it.scoped("onlyIfMissing", () => + Effect.gen(function*(_) { + const handle = yield* _(FiberMap.make()) + const fiberA = yield* _(FiberMap.run(handle, "a", Effect.never)) + const fiberB = yield* _(FiberMap.run(handle, "a", Effect.never, { onlyIfMissing: true })) + const fiberC = yield* _(FiberMap.run(handle, "a", Effect.never, { onlyIfMissing: true })) + yield* _(Effect.yieldNow()) + assert.isTrue(Exit.isInterrupted(yield* _(fiberB.await))) + assert.isTrue(Exit.isInterrupted(yield* _(fiberC.await))) + assert.strictEqual(fiberA.unsafePoll(), null) + })) + + it.scoped("runtime onlyIfMissing", () => + Effect.gen(function*(_) { + const run = yield* _(FiberMap.makeRuntime()) + const fiberA = run("a", Effect.never) + const fiberB = run("a", Effect.never, { onlyIfMissing: true }) + const fiberC = run("a", Effect.never, { onlyIfMissing: true }) + yield* _(Effect.yieldNow()) + assert.isTrue(Exit.isInterrupted(yield* _(fiberB.await))) + assert.isTrue(Exit.isInterrupted(yield* _(fiberC.await))) + assert.strictEqual(fiberA.unsafePoll(), null) + })) })