1import { assert, describe, it } from "@effect/vitest"
2import { assertFalse, assertTrue, strictEqual } from "@effect/vitest/utils"
3import { Array, Deferred, Effect, Exit, Fiber, FiberSet, pipe, Ref, Scope } from "effect"
4import { TestClock } from "effect/testing"
5
6describe("FiberSet", () => {
7 it.effect("run defers startup", () =>
8 Effect.gen(function*() {
9 const container = yield* FiberSet.make()
10 let started = false
11 const fiber = yield* FiberSet.run(
12 container,
13 Effect.sync(() => {
14 started = true
15 }),
16 { startImmediately: false }
17 )
18 assert.isFalse(started)
19 yield* Fiber.join(fiber)
20 assert.isTrue(started)
21 }))
22
23 it.effect("identifies FiberSet in JSON", () =>
24 Effect.gen(function*() {
25 const set = yield* FiberSet.make()
26 strictEqual((set.toJSON() as { readonly _id: string })._id, "FiberSet")
27 }))
28
29 it.effect("interrupts running fibers when the scope closes", () =>
30 Effect.gen(function*() {
31 const ref = yield* Ref.make(0)
32 yield* Effect.scoped(
33 Effect.gen(function*() {
34 const set = yield* FiberSet.make()
35 yield* Effect.onInterrupt(
36 Effect.never,
37 () => Ref.update(ref, (n) => n + 1)
38 ).pipe(
39 FiberSet.run(set),
40 Effect.repeat({ times: 9 })
41 )
42
43 yield* Effect.yieldNow
44 })
45 )
46
47 strictEqual(yield* Ref.get(ref), 10)
48 }))
49
50 it.effect("runtime", () =>
51 Effect.gen(function*() {
52 const ref = yield* Ref.make(0)
53 yield* pipe(
54 Effect.gen(function*() {
55 const set = yield* FiberSet.make()
56 const run = yield* FiberSet.runtime(set)<never>()
57 Array.range(1, 10).forEach(() =>
58 run(
59 Effect.onInterrupt(
60 Effect.never,
61 () => Ref.update(ref, (n) => n + 1)
62 )
63 )
64 )
65 yield* Effect.yieldNow
66 }),
67 Effect.scoped
68 )
69
70 strictEqual(yield* Ref.get(ref), 10)
71 }))
72
73 it.effect("runs fibers concurrently and awaitEmpty waits for completion", () =>
74 Effect.gen(function*() {
75 const set = yield* FiberSet.make()
76 FiberSet.addUnsafe(set, Effect.runFork(Effect.void))
77 FiberSet.addUnsafe(set, Effect.runFork(Effect.void))
78 FiberSet.addUnsafe(set, Effect.runFork(Effect.fail("fail")))
79 const result = yield* pipe(FiberSet.join(set), Effect.flip)
80 strictEqual(result, "fail")
81 }))
82
83 it.effect("size", () =>
84 Effect.gen(function*() {
85 const scope = yield* Scope.make()
86 const set = yield* pipe(FiberSet.make(), Scope.provide(scope))
87 FiberSet.addUnsafe(set, Effect.runFork(Effect.never))
88 FiberSet.addUnsafe(set, Effect.runFork(Effect.never))
89 strictEqual(yield* FiberSet.size(set), 2)
90 yield* Scope.close(scope, Exit.void)
91 strictEqual(yield* FiberSet.size(set), 0)
92 }))
93
94 it.effect("propagateInterruption false ignores external interruption", () =>
95 Effect.gen(function*() {
96 const set = yield* FiberSet.make()
97 const fiber = yield* FiberSet.run(set, Effect.never, {
98 propagateInterruption: false
99 })
100 yield* Effect.yieldNow
101 yield* Fiber.interrupt(fiber)
102 assertFalse(yield* Deferred.isDone(set.deferred))
103 }))
104
105 it.effect("propagateInterruption true fails join on external interruption", () =>
106 Effect.gen(function*() {
107 const set = yield* FiberSet.make()
108 const fiber = yield* FiberSet.run(set, Effect.never, {
109 propagateInterruption: true
110 })
111 yield* Effect.yieldNow
112 yield* Fiber.interrupt(fiber)
113 assertTrue(Exit.hasInterrupts(
114 yield* FiberSet.join(set).pipe(
115 Effect.exit
116 )
117 ))
118 }))
119
120 it.effect("awaitEmpty", () =>
121 Effect.gen(function*() {
122 const set = yield* FiberSet.make()
123 yield* FiberSet.run(set, Effect.sleep(1000))
124 yield* FiberSet.run(set, Effect.sleep(1000))
125 yield* FiberSet.run(set, Effect.sleep(1000))
126 yield* FiberSet.run(set, Effect.sleep(1000))
127
128 const fiber = yield* Effect.forkChild(FiberSet.awaitEmpty(set))
129 yield* TestClock.adjust(500)
130 assert.isUndefined(fiber.pollUnsafe())
131 yield* TestClock.adjust(500)
132 assert.isDefined(fiber.pollUnsafe())
133 }))
134
135 it.effect("makeRuntimePromise", () =>
136 Effect.gen(function*() {
137 const run = yield* FiberSet.makeRuntimePromise()
138 const result = yield* Effect.promise(() => run(Effect.succeed("done")))
139 strictEqual(result, "done")
140 }))
141})