1/**
2 * Manages many fibers together inside one scope.
3 *
4 * A `FiberSet<A, E>` tracks running fibers, removes each fiber when it
5 * completes, and interrupts all still-running fibers when the owning scope
6 * closes. This module includes scoped runtime constructors plus helpers for
7 * adding, clearing, running, counting, joining, and waiting for managed fibers.
8 *
9 * @since 2.0.0
10 */
11import * as Cause from "./Cause.ts"
12import * as Deferred from "./Deferred.ts"
13import * as Effect from "./Effect.ts"
14import * as Exit from "./Exit.ts"
15import * as Fiber from "./Fiber.ts"
16import * as Filter from "./Filter.ts"
17import { constVoid, dual } from "./Function.ts"
18import type * as Inspectable from "./Inspectable.ts"
19import { PipeInspectableProto } from "./internal/core.ts"
20import * as internalEffect from "./internal/effect.ts"
21import * as Iterable from "./Iterable.ts"
22import type { Pipeable } from "./Pipeable.ts"
23import * as Predicate from "./Predicate.ts"
24import type * as Scope from "./Scope.ts"
25
26const TypeId = "~effect/FiberSet"
27
28/**
29 * A FiberSet is a collection of fibers that can be managed together.
30 * When the associated Scope is closed, all fibers in the set will be interrupted.
31 *
32 * **Example** (Managing fibers in a set)
33 *
34 * ```ts import.meta.vitest
35 * import { Effect, FiberSet } from "effect"
36 *
37 * const program = Effect.gen(function*() {
38 * const set = yield* FiberSet.make<string, string>()
39 *
40 * // Add fibers to the set
41 * yield* FiberSet.run(set, Effect.succeed("hello"))
42 * yield* FiberSet.run(set, Effect.succeed("world"))
43 *
44 * // Wait for all fibers to complete
45 * yield* FiberSet.awaitEmpty(set)
46 * return yield* FiberSet.size(set)
47 * })
48 *
49 * const actual = await Effect.runPromise(Effect.scoped(program))
50 * actual // => 0
51 * ```
52 *
53 * @category models
54 * @since 2.0.0
55 */
56export interface FiberSet<out A = unknown, out E = unknown>
57 extends Pipeable, Inspectable.Inspectable, Iterable<Fiber.Fiber<A, E>>
58{
59 readonly [TypeId]: typeof TypeId
60 readonly deferred: Deferred.Deferred<void, unknown>
61 state: {
62 readonly _tag: "Open"
63 readonly backing: Set<Fiber.Fiber<A, E>>
64 } | {
65 readonly _tag: "Closed"
66 }
67}
68
69/**
70 * Checks whether a value is a FiberSet.
71 *
72 * **Example** (Checking if a value is a FiberSet)
73 *
74 * ```ts import.meta.vitest
75 * import { Effect, FiberSet } from "effect"
76 *
77 * const program = Effect.gen(function*() {
78 * const set = yield* FiberSet.make()
79 *
80 * return [FiberSet.isFiberSet(set), FiberSet.isFiberSet({})]
81 * })
82 *
83 * const actual = await Effect.runPromise(Effect.scoped(program))
84 * actual // => [true, false]
85 * ```
86 *
87 * @category guards
88 * @since 2.0.0
89 */
90export const isFiberSet = (u: unknown): u is FiberSet<unknown, unknown> => Predicate.hasProperty(u, TypeId)
91
92const Proto = {
93 [TypeId]: TypeId,
94 [Symbol.iterator](this: FiberSet<unknown, unknown>) {
95 if (this.state._tag === "Closed") {
96 return Iterable.empty()
97 }
98 return this.state.backing[Symbol.iterator]()
99 },
100 ...PipeInspectableProto,
101 toJSON(this: FiberSet<unknown, unknown>) {
102 return {
103 _id: "FiberSet",
104 state: this.state
105 }
106 }
107}
108
109const makeUnsafe = <A, E>(
110 backing: Set<Fiber.Fiber<A, E>>,
111 deferred: Deferred.Deferred<void, unknown>
112): FiberSet<A, E> => {
113 const self = Object.create(Proto)
114 self.state = { _tag: "Open", backing }
115 self.deferred = deferred
116 return self
117}
118
119/**
120 * Creates a scoped `FiberSet` for storing fibers.
121 *
122 * **Details**
123 *
124 * When the associated Scope is closed, all fibers in the set will be
125 * interrupted. You can add fibers to the set using `FiberSet.add` or
126 * `FiberSet.run`, and the fibers will be automatically removed from the
127 * FiberSet when they complete.
128 *
129 * **Example** (Creating a scoped FiberSet)
130 *
131 * ```ts import.meta.vitest
132 * import { Effect, FiberSet } from "effect"
133 *
134 * const program = Effect.gen(function*() {
135 * const set = yield* FiberSet.make()
136 *
137 * // run some effects and add the fibers to the set
138 * yield* FiberSet.run(set, Effect.never)
139 * yield* FiberSet.run(set, Effect.never)
140 *
141 * yield* Effect.yieldNow
142 * return yield* FiberSet.size(set)
143 * }).pipe(
144 * Effect.scoped // The fibers will be interrupted when the scope is closed
145 * )
146 *
147 * const actual = await Effect.runPromise(program)
148 * actual // => 2
149 * ```
150 *
151 * @category constructors
152 * @since 2.0.0
153 */
154export const make = <A = unknown, E = unknown>(): Effect.Effect<FiberSet<A, E>, never, Scope.Scope> =>
155 Effect.acquireRelease(
156 Effect.sync(() => makeUnsafe(new Set(), Deferred.makeUnsafe())),
157 (set) =>
158 Effect.suspend(() => {
159 const state = set.state
160 if (state._tag === "Closed") return Effect.void
161 set.state = { _tag: "Closed" }
162 const fibers = state.backing
163 return Fiber.interruptAll(fibers).pipe(
164 Deferred.into(set.deferred)
165 )
166 })
167 )
168
169/**
170 * Creates a scoped run function that forks effects into a new `FiberSet`.
171 *
172 * **Details**
173 *
174 * Each call returns the forked fiber and adds it to the set. Managed fibers are
175 * removed when they complete and are interrupted when the set's scope closes.
176 *
177 * **Example** (Creating a scoped runtime)
178 *
179 * ```ts import.meta.vitest
180 * import { Effect, Fiber, FiberSet } from "effect"
181 *
182 * const program = Effect.gen(function*() {
183 * const runFork = yield* FiberSet.makeRuntime()
184 *
185 * // Fork effects using the runtime
186 * const fiber1 = runFork(Effect.succeed("hello"))
187 * const fiber2 = runFork(Effect.succeed("world"))
188 *
189 * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)]
190 * })
191 *
192 * const actual = await Effect.runPromise(Effect.scoped(program))
193 * actual // => ["hello", "world"]
194 * ```
195 *
196 * @category constructors
197 * @since 2.0.0
198 */
199export const makeRuntime = <R = never, A = unknown, E = unknown>(): Effect.Effect<
200 (<XE extends E, XA extends A>(
201 effect: Effect.Effect<XA, XE, R>,
202 options?: (Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }) | undefined
203 ) => Fiber.Fiber<XA, XE>),
204 never,
205 Scope.Scope | R
206> =>
207 Effect.flatMap(
208 make<A, E>(),
209 (self) => runtime(self)<R>()
210 )
211
212/**
213 * Creates a scoped run function that forks effects into a new `FiberSet` and
214 * returns a `Promise` for each effect result.
215 *
216 * **When to use**
217 *
218 * Use when many scoped fibers should be tracked as a set while exposing each
219 * result through Promise-based APIs.
220 *
221 * **Details**
222 *
223 * Managed fibers are removed when they complete and are interrupted when the
224 * set's scope closes. Each Promise resolves with the effect's success value or
225 * rejects with the squashed failure cause.
226 *
227 * **Example** (Creating a promise runtime)
228 *
229 * ```ts import.meta.vitest
230 * import { Effect, FiberSet } from "effect"
231 *
232 * const program = Effect.gen(function*() {
233 * const runPromise = yield* FiberSet.makeRuntimePromise()
234 *
235 * // Run effects as promises
236 * const promise1 = runPromise(Effect.succeed("hello"))
237 * const promise2 = runPromise(Effect.succeed("world"))
238 *
239 * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)]
240 * })
241 *
242 * const actual = await Effect.runPromise(Effect.scoped(program))
243 * actual // => ["hello", "world"]
244 * ```
245 *
246 * @category constructors
247 * @since 3.13.0
248 */
249export const makeRuntimePromise = <R = never, A = unknown, E = unknown>(): Effect.Effect<
250 (<XE extends E, XA extends A>(
251 effect: Effect.Effect<XA, XE, R>,
252 options?: (Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }) | undefined
253 ) => Promise<XA>),
254 never,
255 R | Scope.Scope
256> =>
257 Effect.flatMap(
258 make<A, E>(),
259 (self) => runtimePromise(self)<R>()
260 )
261
262const internalFiberId = -1
263const isInternalInterruption = Filter.toPredicate(Filter.compose(
264 Cause.filterInterruptors,
265 Filter.has(internalFiberId)
266))
267
268/**
269 * Adds an existing fiber to the `FiberSet` using a synchronous, unsafe
270 * mutation.
271 *
272 * **When to use**
273 *
274 * Use when an already forked fiber must be registered immediately and
275 * synchronous interruption on a closed set is acceptable.
276 *
277 * **Details**
278 *
279 * When the fiber completes, it is removed from the set. If the set is already
280 * closed, the supplied fiber is interrupted immediately. Non-interruption
281 * failures are recorded for `FiberSet.join`.
282 *
283 * **Example** (Adding a fiber unsafely)
284 *
285 * ```ts import.meta.vitest
286 * import { Effect, FiberSet } from "effect"
287 *
288 * const program = Effect.gen(function*() {
289 * const set = yield* FiberSet.make()
290 * const fiber = yield* Effect.forkChild(Effect.never)
291 *
292 * // Unsafe add - doesn't return an Effect
293 * FiberSet.addUnsafe(set, fiber)
294 *
295 * // The fiber is now managed by the set
296 * return yield* FiberSet.size(set)
297 * })
298 *
299 * const actual = await Effect.runPromise(Effect.scoped(program))
300 * actual // => 1
301 * ```
302 *
303 * @category combinators
304 * @since 4.0.0
305 */
306export const addUnsafe: {
307 <A, E, XE extends E, XA extends A>(
308 fiber: Fiber.Fiber<XA, XE>,
309 options?: {
310 readonly propagateInterruption?: boolean | undefined
311 } | undefined
312 ): (self: FiberSet<A, E>) => void
313 <A, E, XE extends E, XA extends A>(
314 self: FiberSet<A, E>,
315 fiber: Fiber.Fiber<XA, XE>,
316 options?: {
317 readonly propagateInterruption?: boolean | undefined
318 } | undefined
319 ): void
320} = dual((args) => isFiberSet(args[0]), <A, E, XE extends E, XA extends A>(
321 self: FiberSet<A, E>,
322 fiber: Fiber.Fiber<XA, XE>,
323 options?: {
324 readonly propagateInterruption?: boolean | undefined
325 } | undefined
326): void => {
327 if (self.state._tag === "Closed") {
328 fiber.interruptUnsafe(internalFiberId)
329 return
330 } else if (self.state.backing.has(fiber)) {
331 return
332 }
333 self.state.backing.add(fiber)
334 fiber.addObserver((exit) => {
335 if (self.state._tag === "Closed") {
336 return
337 }
338 self.state.backing.delete(fiber)
339 if (
340 Exit.isFailure(exit) &&
341 (
342 options?.propagateInterruption === true ?
343 !isInternalInterruption(exit.cause) :
344 !Cause.hasInterruptsOnly(exit.cause)
345 )
346 ) {
347 Deferred.doneUnsafe(self.deferred, exit as any)
348 }
349 })
350})
351
352/**
353 * Adds a fiber to the FiberSet. When the fiber completes, it will be removed.
354 *
355 * **Example** (Adding a fiber)
356 *
357 * ```ts import.meta.vitest
358 * import { Effect, FiberSet } from "effect"
359 *
360 * const program = Effect.gen(function*() {
361 * const set = yield* FiberSet.make()
362 * const fiber = yield* Effect.forkChild(Effect.never)
363 *
364 * // Add the fiber to the set
365 * yield* FiberSet.add(set, fiber)
366 *
367 * // The fiber is now managed by the set
368 * return yield* FiberSet.size(set)
369 * })
370 *
371 * const actual = await Effect.runPromise(Effect.scoped(program))
372 * actual // => 1
373 * ```
374 *
375 * @category combinators
376 * @since 2.0.0
377 */
378export const add: {
379 <A, E, XE extends E, XA extends A>(
380 fiber: Fiber.Fiber<XA, XE>,
381 options?: {
382 readonly propagateInterruption?: boolean | undefined
383 } | undefined
384 ): (self: FiberSet<A, E>) => Effect.Effect<void>
385 <A, E, XE extends E, XA extends A>(
386 self: FiberSet<A, E>,
387 fiber: Fiber.Fiber<XA, XE>,
388 options?: {
389 readonly propagateInterruption?: boolean | undefined
390 } | undefined
391 ): Effect.Effect<void>
392} = dual(
393 (args) => isFiberSet(args[0]),
394 <A, E, XE extends E, XA extends A>(
395 self: FiberSet<A, E>,
396 fiber: Fiber.Fiber<XA, XE>,
397 options?: {
398 readonly propagateInterruption?: boolean | undefined
399 } | undefined
400 ): Effect.Effect<void> => Effect.sync(() => addUnsafe(self, fiber, options))
401)
402
403/**
404 * Interrupts all fibers in the `FiberSet` and clears the set.
405 *
406 * **Example** (Clearing all fibers)
407 *
408 * ```ts import.meta.vitest
409 * import { Effect, FiberSet } from "effect"
410 *
411 * const program = Effect.gen(function*() {
412 * const set = yield* FiberSet.make()
413 *
414 * // Add some fibers
415 * yield* FiberSet.run(set, Effect.never)
416 * yield* FiberSet.run(set, Effect.never)
417 *
418 * const sizeBefore = yield* FiberSet.size(set)
419 *
420 * // Clear all fibers
421 * yield* FiberSet.clear(set)
422 *
423 * return [sizeBefore, yield* FiberSet.size(set)]
424 * })
425 *
426 * const actual = await Effect.runPromise(Effect.scoped(program))
427 * actual // => [2, 0]
428 * ```
429 *
430 * @category combinators
431 * @since 2.0.0
432 */
433export const clear = <A, E>(self: FiberSet<A, E>): Effect.Effect<void> =>
434 Effect.suspend(() => {
435 if (self.state._tag === "Closed") {
436 return Effect.void
437 }
438 return Fiber.interruptAllAs(self.state.backing, internalFiberId)
439 })
440
441const constInterruptedFiber = (function() {
442 let fiber: Fiber.Fiber<never, never> | undefined = undefined
443 return () => {
444 if (fiber === undefined) {
445 fiber = Effect.runFork(Effect.interrupt)
446 }
447 return fiber
448 }
449})()
450
451/**
452 * Forks an Effect and adds the fiber to the FiberSet. The fiber is removed
453 * when it completes. Set `startImmediately: false` to defer startup. By default,
454 * the effect starts immediately.
455 *
456 * **Example** (Forking effects into a set)
457 *
458 * ```ts import.meta.vitest
459 * import { Effect, Fiber, FiberSet } from "effect"
460 *
461 * const program = Effect.gen(function*() {
462 * const set = yield* FiberSet.make()
463 *
464 * // Fork and add to set
465 * const fiber1 = yield* FiberSet.run(set, Effect.succeed("hello"))
466 * const fiber2 = yield* FiberSet.run(set, Effect.succeed("world"))
467 *
468 * // Get results
469 * return [yield* Fiber.join(fiber1), yield* Fiber.join(fiber2)]
470 * })
471 *
472 * const actual = await Effect.runPromise(Effect.scoped(program))
473 * actual // => ["hello", "world"]
474 * ```
475 *
476 * @category combinators
477 * @since 2.0.0
478 */
479export const run: {
480 <A, E>(
481 self: FiberSet<A, E>,
482 options?: {
483 readonly propagateInterruption?: boolean | undefined
484 readonly startImmediately?: boolean | undefined
485 } | undefined
486 ): <R, XE extends E, XA extends A>(
487 effect: Effect.Effect<XA, XE, R>
488 ) => Effect.Effect<Fiber.Fiber<XA, XE>, never, R>
489 <A, E, R, XE extends E, XA extends A>(
490 self: FiberSet<A, E>,
491 effect: Effect.Effect<XA, XE, R>,
492 options?: {
493 readonly propagateInterruption?: boolean | undefined
494 readonly startImmediately?: boolean | undefined
495 } | undefined
496 ): Effect.Effect<Fiber.Fiber<XA, XE>, never, R>
497} = function() {
498 const self = arguments[0] as FiberSet<any, any>
499 if (!Effect.isEffect(arguments[1])) {
500 const options = arguments[1]
501 return (effect: Effect.Effect<any, any, any>) => runImpl(self, effect, options)
502 }
503 return runImpl(self, arguments[1], arguments[2]) as any
504}
505
506const runImpl = <A, E, R, XE extends E, XA extends A>(
507 self: FiberSet<A, E>,
508 effect: Effect.Effect<XA, XE, R>,
509 options?: {
510 readonly startImmediately?: boolean | undefined
511 readonly propagateInterruption?: boolean | undefined
512 }
513): Effect.Effect<Fiber.Fiber<XA, XE>, never, R> =>
514 Effect.withFiber((parent) => {
515 if (self.state._tag === "Closed") {
516 return Effect.sync(constInterruptedFiber)
517 }
518 const fiber: Fiber.Fiber<XA, XE> = internalEffect.forkUnsafe(
519 parent,
520 effect,
521 options?.startImmediately ?? true,
522 true
523 )
524 addUnsafe(self, fiber, options)
525 return Effect.succeed(fiber)
526 })
527
528/**
529 * Captures a `Runtime` and uses it to fork effects into the `FiberSet`.
530 *
531 * **Example** (Capturing a runtime)
532 *
533 * ```ts import.meta.vitest
534 * import { Context, Effect, Fiber, FiberSet } from "effect"
535 *
536 * class Users extends Context.Service<Users, {
537 * readonly getAll: Effect.Effect<Array<unknown>>
538 * }>()("Users") {}
539 *
540 * const program = Effect.gen(function*() {
541 * const set = yield* FiberSet.make()
542 * const run = yield* FiberSet.runtime(set)<Users>()
543 *
544 * // run some effects and add the fibers to the set
545 * const fiber = run(Effect.andThen(Users, (_) => _.getAll))
546 * return (yield* Fiber.join(fiber)).length
547 * }).pipe(
548 * Effect.scoped // The fibers will be interrupted when the scope is closed
549 * )
550 *
551 * const actual = await Effect.runPromise(Effect.provideService(program, Users, {
552 * getAll: Effect.succeed([])
553 * }))
554 * actual // => 0
555 * ```
556 *
557 * @category combinators
558 * @since 2.0.0
559 */
560export const runtime: <A, E>(
561 self: FiberSet<A, E>
562) => <R = never>() => Effect.Effect<
563 <XE extends E, XA extends A>(
564 effect: Effect.Effect<XA, XE, R>,
565 options?:
566 | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }
567 | undefined
568 ) => Fiber.Fiber<XA, XE>,
569 never,
570 R
571> = <A, E>(self: FiberSet<A, E>) => <R>() =>
572 Effect.map(
573 Effect.context<R>(),
574 (services) => {
575 const runFork = Effect.runForkWith(services)
576 return <XE extends E, XA extends A>(
577 effect: Effect.Effect<XA, XE, R>,
578 options?:
579 | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }
580 | undefined
581 ) => {
582 if (self.state._tag === "Closed") {
583 return constInterruptedFiber()
584 }
585 const fiber = runFork(effect, options)
586 addUnsafe(self, fiber, options)
587 return fiber
588 }
589 }
590 )
591
592/**
593 * Captures a `Runtime` and returns a Promise-based runner that forks effects
594 * into the `FiberSet`.
595 *
596 * **When to use**
597 *
598 * Use when you need to bridge effects to `Promise` values while still tracking
599 * their fibers in a `FiberSet`.
600 *
601 * **Details**
602 *
603 * The returned run function returns a `Promise` for each effect result.
604 *
605 * **Example** (Running effects as promises)
606 *
607 * ```ts import.meta.vitest
608 * import { Effect, FiberSet } from "effect"
609 *
610 * const program = Effect.gen(function*() {
611 * const set = yield* FiberSet.make()
612 * const runPromise = yield* FiberSet.runtimePromise(set)()
613 *
614 * // Run effects as promises
615 * const promise1 = runPromise(Effect.succeed("hello"))
616 * const promise2 = runPromise(Effect.succeed("world"))
617 *
618 * return [yield* Effect.promise(() => promise1), yield* Effect.promise(() => promise2)]
619 * })
620 *
621 * const actual = await Effect.runPromise(Effect.scoped(program))
622 * actual // => ["hello", "world"]
623 * ```
624 *
625 * @see {@link runtime} for a runner that returns the forked `Fiber`
626 *
627 * @category combinators
628 * @since 3.13.0
629 */
630export const runtimePromise = <A, E>(self: FiberSet<A, E>): <R = never>() => Effect.Effect<
631 <XE extends E, XA extends A>(
632 effect: Effect.Effect<XA, XE, R>,
633 options?:
634 | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }
635 | undefined
636 ) => Promise<XA>,
637 never,
638 R
639> =>
640<R>() =>
641 Effect.map(
642 runtime(self)<R>(),
643 (runFork) =>
644 <XE extends E, XA extends A>(
645 effect: Effect.Effect<XA, XE, R>,
646 options?:
647 | Effect.RunOptions & { readonly propagateInterruption?: boolean | undefined }
648 | undefined
649 ): Promise<XA> =>
650 new Promise((resolve, reject) =>
651 runFork(effect, options).addObserver((exit) => {
652 if (Exit.isSuccess(exit)) {
653 resolve(exit.value)
654 } else {
655 reject(Cause.squash(exit.cause))
656 }
657 })
658 )
659 )
660
661/**
662 * Gets the number of fibers currently in the FiberSet.
663 *
664 * **Example** (Checking the set size)
665 *
666 * ```ts import.meta.vitest
667 * import { Effect, FiberSet } from "effect"
668 *
669 * const program = Effect.gen(function*() {
670 * const set = yield* FiberSet.make()
671 *
672 * const sizeBefore = yield* FiberSet.size(set)
673 *
674 * // Add some fibers
675 * yield* FiberSet.run(set, Effect.never)
676 * yield* FiberSet.run(set, Effect.never)
677 *
678 * return [sizeBefore, yield* FiberSet.size(set)]
679 * })
680 *
681 * const actual = await Effect.runPromise(Effect.scoped(program))
682 * actual // => [0, 2]
683 * ```
684 *
685 * @category combinators
686 * @since 2.0.0
687 */
688export const size = <A, E>(self: FiberSet<A, E>): Effect.Effect<number> =>
689 Effect.sync(() => self.state._tag === "Closed" ? 0 : self.state.backing.size)
690
691/**
692 * Joins all fibers in the FiberSet. If any fiber in the set terminates with a failure,
693 * the returned Effect will terminate with the first failure that occurred.
694 *
695 * **Example** (Joining failing fibers)
696 *
697 * ```ts import.meta.vitest
698 * import { Effect, Exit, FiberSet } from "effect"
699 *
700 * const program = Effect.gen(function*() {
701 * const set = yield* FiberSet.make()
702 * yield* FiberSet.add(set, Effect.runFork(Effect.fail("error")))
703 *
704 * // parent fiber will fail with "error"
705 * yield* FiberSet.join(set)
706 * })
707 *
708 * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program)))
709 * actual // => Exit.fail("error")
710 * ```
711 *
712 * @category combinators
713 * @since 2.0.0
714 */
715export const join = <A, E>(self: FiberSet<A, E>): Effect.Effect<void, E> =>
716 Deferred.await(self.deferred as Deferred.Deferred<void, E>)
717
718/**
719 * Waits until the fiber set is empty.
720 *
721 * **Example** (Waiting for an empty set)
722 *
723 * ```ts import.meta.vitest
724 * import { Effect, FiberSet } from "effect"
725 *
726 * const program = Effect.gen(function*() {
727 * const set = yield* FiberSet.make()
728 *
729 * yield* FiberSet.run(set, Effect.yieldNow)
730 * yield* FiberSet.run(set, Effect.yieldNow)
731 *
732 * // Wait for all fibers to complete
733 * yield* FiberSet.awaitEmpty(set)
734 *
735 * return yield* FiberSet.size(set)
736 * })
737 *
738 * const actual = await Effect.runPromise(Effect.scoped(program))
739 * actual // => 0
740 * ```
741 *
742 * @category combinators
743 * @since 3.13.0
744 */
745export const awaitEmpty = <A, E>(self: FiberSet<A, E>): Effect.Effect<void> =>
746 Effect.whileLoop({
747 while: () => self.state._tag === "Open" && self.state.backing.size > 0,
748 body: () => Fiber.await(Iterable.headUnsafe(self)),
749 step: constVoid
750 })