Skip to content

Commit e3ff789

Browse files
authoredMar 4, 2024··
add FiberMap/FiberSet.join api (#2244)
1 parent 5f7925b commit e3ff789

File tree

5 files changed

+124
-10
lines changed

5 files changed

+124
-10
lines changed
 

‎.changeset/healthy-turkeys-serve.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
add FiberMap/FiberSet.join api
6+
7+
This api can be used to propogate failures back to a parent fiber, in case any of the fibers added to the FiberMap/FiberSet fail with an error.
8+
9+
Example:
10+
11+
```ts
12+
import { Effect, FiberSet } from "effect";
13+
14+
Effect.gen(function* (_) {
15+
const set = yield* _(FiberSet.make());
16+
yield* _(FiberSet.add(set, Effect.runFork(Effect.fail("error"))));
17+
18+
// parent fiber will fail with "error"
19+
yield* _(FiberSet.join(set));
20+
});
21+
```

‎packages/effect/src/FiberMap.ts

+44-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import * as Effect from "effect/Effect"
55
import type * as Scope from "effect/Scope"
66
import type { NoSuchElementException } from "./Cause.js"
7+
import * as Cause from "./Cause.js"
8+
import * as Deferred from "./Deferred.js"
9+
import * as Exit from "./Exit.js"
710
import * as Fiber from "./Fiber.js"
811
import * as FiberId from "./FiberId.js"
912
import { dual } from "./Function.js"
@@ -30,11 +33,12 @@ export type TypeId = typeof TypeId
3033
* @since 2.0.0
3134
* @categories models
3235
*/
33-
export interface FiberMap<K, A = unknown, E = unknown>
36+
export interface FiberMap<in out K, out A = unknown, out E = unknown>
3437
extends Pipeable, Inspectable.Inspectable, Iterable<[K, Fiber.RuntimeFiber<A, E>]>
3538
{
3639
readonly [TypeId]: TypeId
3740
readonly backing: MutableHashMap.MutableHashMap<K, Fiber.RuntimeFiber<A, E>>
41+
readonly deferred: Deferred.Deferred<never, unknown>
3842
}
3943

4044
/**
@@ -65,9 +69,13 @@ const Proto = {
6569
}
6670
}
6771

68-
const unsafeMake = <K, A = unknown, E = unknown>(): FiberMap<K, A, E> => {
72+
const unsafeMake = <K, A = unknown, E = unknown>(
73+
backing: MutableHashMap.MutableHashMap<K, Fiber.RuntimeFiber<A, E>>,
74+
deferred: Deferred.Deferred<never, E>
75+
): FiberMap<K, A, E> => {
6976
const self = Object.create(Proto)
70-
self.backing = MutableHashMap.empty()
77+
self.backing = backing
78+
self.deferred = deferred
7179
return self
7280
}
7381

@@ -97,7 +105,14 @@ const unsafeMake = <K, A = unknown, E = unknown>(): FiberMap<K, A, E> => {
97105
* @categories constructors
98106
*/
99107
export const make = <K, A = unknown, E = unknown>(): Effect.Effect<FiberMap<K, A, E>, never, Scope.Scope> =>
100-
Effect.acquireRelease(Effect.sync(() => unsafeMake<K, A, E>()), clear)
108+
Effect.acquireRelease(
109+
Effect.map(Deferred.make<never, E>(), (deferred) =>
110+
unsafeMake<K, A, E>(
111+
MutableHashMap.empty(),
112+
deferred
113+
)),
114+
clear
115+
)
101116

102117
/**
103118
* Create an Effect run function that is backed by a FiberMap.
@@ -159,11 +174,14 @@ export const unsafeSet: {
159174
previous.value.unsafeInterruptAsFork(interruptAs ?? FiberId.none)
160175
}
161176
MutableHashMap.set(self.backing, key, fiber)
162-
fiber.addObserver((_) => {
177+
fiber.addObserver((exit) => {
163178
const current = MutableHashMap.get(self.backing, key)
164179
if (Option.isSome(current) && fiber === current.value) {
165180
MutableHashMap.remove(self.backing, key)
166181
}
182+
if (Exit.isFailure(exit) && !Cause.isInterruptedOnly(exit.cause)) {
183+
Deferred.unsafeDone(self.deferred, exit as any)
184+
}
167185
})
168186
})
169187

@@ -372,5 +390,25 @@ export const runtime: <K, A, E>(
372390
* @since 2.0.0
373391
* @categories combinators
374392
*/
375-
export const size = <K, E, A>(self: FiberMap<K, E, A>): Effect.Effect<number> =>
393+
export const size = <K, A, E>(self: FiberMap<K, A, E>): Effect.Effect<number> =>
376394
Effect.sync(() => MutableHashMap.size(self.backing))
395+
396+
/**
397+
* Join all fibers in the FiberMap. If any of the Fiber's in the map terminate with a failure,
398+
* the returned Effect will terminate with the first failure that occurred.
399+
*
400+
* @since 2.0.0
401+
* @categories combinators
402+
* @example
403+
* import { Effect, FiberMap } from "effect";
404+
*
405+
* Effect.gen(function* (_) {
406+
* const map = yield* _(FiberMap.make());
407+
* yield* _(FiberMap.set(map, "a", Effect.runFork(Effect.fail("error"))));
408+
*
409+
* // parent fiber will fail with "error"
410+
* yield* _(FiberMap.join(map));
411+
* });
412+
*/
413+
export const join = <K, A, E>(self: FiberMap<K, A, E>): Effect.Effect<never, E> =>
414+
Deferred.await(self.deferred as Deferred.Deferred<never, E>)

‎packages/effect/src/FiberSet.ts

+38-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
*/
44
import * as Effect from "effect/Effect"
55
import type * as Scope from "effect/Scope"
6+
import * as Cause from "./Cause.js"
7+
import * as Deferred from "./Deferred.js"
8+
import * as Exit from "./Exit.js"
69
import * as Fiber from "./Fiber.js"
710
import { dual } from "./Function.js"
811
import * as Inspectable from "./Inspectable.js"
@@ -31,6 +34,7 @@ export interface FiberSet<out A = unknown, out E = unknown>
3134
{
3235
readonly [TypeId]: TypeId
3336
readonly backing: Set<Fiber.RuntimeFiber<A, E>>
37+
readonly deferred: Deferred.Deferred<never, unknown>
3438
}
3539

3640
/**
@@ -61,9 +65,13 @@ const Proto = {
6165
}
6266
}
6367

64-
const unsafeMake = <A, E>(): FiberSet<A, E> => {
68+
const unsafeMake = <A, E>(
69+
backing: Set<Fiber.RuntimeFiber<A, E>>,
70+
deferred: Deferred.Deferred<never, unknown>
71+
): FiberSet<A, E> => {
6572
const self = Object.create(Proto)
66-
self.backing = new Set()
73+
self.backing = backing
74+
self.deferred = deferred
6775
return self
6876
}
6977

@@ -93,7 +101,10 @@ const unsafeMake = <A, E>(): FiberSet<A, E> => {
93101
* @categories constructors
94102
*/
95103
export const make = <A = unknown, E = unknown>(): Effect.Effect<FiberSet<A, E>, never, Scope.Scope> =>
96-
Effect.acquireRelease(Effect.sync(() => unsafeMake<A, E>()), clear)
104+
Effect.acquireRelease(
105+
Effect.map(Deferred.make<never, unknown>(), (deferred) => unsafeMake(new Set(), deferred)),
106+
clear
107+
)
97108

98109
/**
99110
* Create an Effect run function that is backed by a FiberSet.
@@ -136,8 +147,11 @@ export const unsafeAdd: {
136147
return
137148
}
138149
self.backing.add(fiber)
139-
fiber.addObserver((_) => {
150+
fiber.addObserver((exit) => {
140151
self.backing.delete(fiber)
152+
if (Exit.isFailure(exit) && !Cause.isInterruptedOnly(exit.cause)) {
153+
Deferred.unsafeDone(self.deferred, exit as any)
154+
}
141155
})
142156
})
143157

@@ -264,3 +278,23 @@ export const runtime: <A, E>(
264278
* @categories combinators
265279
*/
266280
export const size = <A, E>(self: FiberSet<A, E>): Effect.Effect<number> => Effect.sync(() => self.backing.size)
281+
282+
/**
283+
* Join all fibers in the FiberSet. If any of the Fiber's in the set terminate with a failure,
284+
* the returned Effect will terminate with the first failure that occurred.
285+
*
286+
* @since 2.0.0
287+
* @categories combinators
288+
* @example
289+
* import { Effect, FiberSet } from "effect";
290+
*
291+
* Effect.gen(function* (_) {
292+
* const set = yield* _(FiberSet.make());
293+
* yield* _(FiberSet.add(set, Effect.runFork(Effect.fail("error"))));
294+
*
295+
* // parent fiber will fail with "error"
296+
* yield* _(FiberSet.join(set));
297+
* });
298+
*/
299+
export const join = <A, E>(self: FiberSet<A, E>): Effect.Effect<never, E> =>
300+
Deferred.await(self.deferred as Deferred.Deferred<never, E>)

‎packages/effect/test/FiberMap.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,15 @@ describe("FiberMap", () => {
5050

5151
assert.strictEqual(yield* _(Ref.get(ref)), 10)
5252
}))
53+
54+
it.scoped("join", () =>
55+
Effect.gen(function*(_) {
56+
const map = yield* _(FiberMap.make<string>())
57+
FiberMap.unsafeSet(map, "a", Effect.runFork(Effect.unit))
58+
FiberMap.unsafeSet(map, "b", Effect.runFork(Effect.unit))
59+
FiberMap.unsafeSet(map, "c", Effect.runFork(Effect.fail("fail")))
60+
FiberMap.unsafeSet(map, "d", Effect.runFork(Effect.fail("ignored")))
61+
const result = yield* _(FiberMap.join(map), Effect.flip)
62+
assert.strictEqual(result, "fail")
63+
}))
5364
})

‎packages/effect/test/FiberSet.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,14 @@ describe("FiberSet", () => {
4949

5050
assert.strictEqual(yield* _(Ref.get(ref)), 10)
5151
}))
52+
53+
it.scoped("join", () =>
54+
Effect.gen(function*(_) {
55+
const set = yield* _(FiberSet.make())
56+
FiberSet.unsafeAdd(set, Effect.runFork(Effect.unit))
57+
FiberSet.unsafeAdd(set, Effect.runFork(Effect.unit))
58+
FiberSet.unsafeAdd(set, Effect.runFork(Effect.fail("fail")))
59+
const result = yield* _(FiberSet.join(set), Effect.flip)
60+
assert.strictEqual(result, "fail")
61+
}))
5262
})

0 commit comments

Comments
 (0)
Please sign in to comment.