1/**
2 * Manages at most one fiber inside a scope.
3 *
4 * A `FiberHandle<A, E>` can hold one `Fiber<A, E>`. Installing a new fiber
5 * interrupts the previous one unless the operation is configured with
6 * `onlyIfMissing`, and closing the owning scope interrupts the current fiber.
7 * This module includes constructors for handles and scoped runtimes, helpers
8 * for setting, reading, clearing, and running fibers, and operations for joining
9 * the current fiber or waiting until the handle is empty.
10 *
11 * @since 2.0.0
12 */
13import * as Cause from "./Cause.ts"
14import * as Deferred from "./Deferred.ts"
15import * as Effect from "./Effect.ts"
16import * as Exit from "./Exit.ts"
17import * as Fiber from "./Fiber.ts"
18import * as Filter from "./Filter.ts"
19import { dual } from "./Function.ts"
20import type * as Inspectable from "./Inspectable.ts"
21import { PipeInspectableProto } from "./internal/core.ts"
22import * as internalEffect from "./internal/effect.ts"
23import * as Option from "./Option.ts"
24import type { Pipeable } from "./Pipeable.ts"
25import * as Predicate from "./Predicate.ts"
26import type { Scheduler } from "./Scheduler.ts"
27import type * as Scope from "./Scope.ts"
28
29const TypeId = "~effect/FiberHandle"
30
31/**
32 * Scoped handle that manages at most one fiber, interrupts the current fiber
33 * when the handle's scope closes, and removes managed fibers from the handle
34 * when they complete.
35 *
36 * **Example** (Managing a single fiber)
37 *
38 * ```ts import.meta.vitest
39 * import { Effect, Fiber, FiberHandle } from "effect"
40 *
41 * const program = Effect.gen(function*() {
42 * // Create a FiberHandle that can hold fibers producing strings
43 * const handle = yield* FiberHandle.make<string, never>()
44 *
45 * // The handle can store and manage a single fiber
46 * const fiber = yield* FiberHandle.run(handle, Effect.succeed("hello"))
47 * return yield* Fiber.join(fiber)
48 * })
49 *
50 * const actual = await Effect.runPromise(Effect.scoped(program))
51 * actual // => "hello"
52 * ```
53 *
54 * @category models
55 * @since 2.0.0
56 */
57export interface FiberHandle<out A = unknown, out E = unknown> extends Pipeable, Inspectable.Inspectable {
58 readonly [TypeId]: typeof TypeId
59 readonly deferred: Deferred.Deferred<void, unknown>
60 state: {
61 readonly _tag: "Open"
62 fiber: Fiber.Fiber<A, E> | undefined
63 } | {
64 readonly _tag: "Closed"
65 }
66}
67
68/**
69 * Returns `true` if a value is a `FiberHandle` by checking for the
70 * `FiberHandle` runtime marker.
71 *
72 * **Example** (Checking fiber handles)
73 *
74 * ```ts import.meta.vitest
75 * import { Effect, FiberHandle } from "effect"
76 *
77 * const program = Effect.gen(function*() {
78 * const handle = yield* FiberHandle.make()
79 *
80 * return [FiberHandle.isFiberHandle(handle), FiberHandle.isFiberHandle("not a handle")]
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 isFiberHandle = (u: unknown): u is FiberHandle => Predicate.hasProperty(u, TypeId)
91
92const Proto = {
93 [TypeId]: TypeId,
94 ...PipeInspectableProto,
95 toJSON(this: FiberHandle) {
96 return {
97 _id: "FiberHandle",
98 state: this.state
99 }
100 }
101}
102
103const makeUnsafe = <A = unknown, E = unknown>(): FiberHandle<A, E> => {
104 const self = Object.create(Proto)
105 self.state = { _tag: "Open", fiber: undefined }
106 self.deferred = Deferred.makeUnsafe()
107 return self
108}
109
110/**
111 * Creates a scoped `FiberHandle` that can store a single fiber.
112 *
113 * **Details**
114 *
115 * When the associated `Scope` is closed, the contained fiber will be
116 * interrupted. You can add a fiber to the handle using `FiberHandle.run`, and
117 * the fiber will be automatically removed from the `FiberHandle` when it
118 * completes.
119 *
120 * **Example** (Creating a scoped fiber handle)
121 *
122 * ```ts import.meta.vitest
123 * import { Effect, FiberHandle } from "effect"
124 *
125 * const program = Effect.gen(function*() {
126 * const handle = yield* FiberHandle.make()
127 *
128 * // run some effects
129 * yield* FiberHandle.run(handle, Effect.never)
130 * // this will interrupt the previous fiber
131 * yield* FiberHandle.run(handle, Effect.never)
132 *
133 * yield* Effect.yieldNow
134 * return handle.state._tag === "Open" && handle.state.fiber !== undefined
135 * }).pipe(
136 * Effect.scoped // The fiber will be interrupted when the scope is closed
137 * )
138 *
139 * const actual = await Effect.runPromise(program)
140 * actual // => true
141 * ```
142 *
143 * @category constructors
144 * @since 2.0.0
145 */
146export const make = <A = unknown, E = unknown>(): Effect.Effect<FiberHandle<A, E>, never, Scope.Scope> =>
147 Effect.acquireRelease(
148 Effect.sync(() => makeUnsafe<A, E>()),
149 (handle) => {
150 const state = handle.state
151 if (state._tag === "Closed") return Effect.void
152 handle.state = { _tag: "Closed" }
153 return state.fiber ?
154 Deferred.into(
155 Effect.asVoid(Fiber.interruptAs(state.fiber, internalFiberId)),
156 handle.deferred
157 ) :
158 Deferred.done(handle.deferred, Exit.void)
159 }
160 )
161
162/**
163 * Creates a scoped run function that forks effects into a new `FiberHandle`.
164 *
165 * **Details**
166 *
167 * Each call returns the forked fiber, stores it in the handle, and interrupts
168 * the previous fiber unless `onlyIfMissing` is set. The managed fiber is
169 * interrupted when the handle's scope closes.
170 *
171 * **Example** (Running effects with a fiber handle)
172 *
173 * ```ts import.meta.vitest
174 * import { Cause, Effect, Exit, Fiber, FiberHandle } from "effect"
175 *
176 * const program = Effect.gen(function*() {
177 * const run = yield* FiberHandle.makeRuntime<never>()
178 *
179 * // Run effects and get fibers back
180 * const fiberA = run(Effect.never)
181 * const fiberB = run(Effect.succeed("second"))
182 *
183 * // The second fiber will interrupt the first
184 * const resultA = yield* Fiber.await(fiberA)
185 * const resultB = yield* Fiber.await(fiberB)
186 * return [resultA, resultB]
187 * }).pipe(Effect.scoped)
188 *
189 * const actual = await Effect.runPromise(program)
190 * actual // => [Exit.failCause(Cause.interrupt(-1)), Exit.succeed("second")]
191 * ```
192 *
193 * @category constructors
194 * @since 2.0.0
195 */
196export const makeRuntime = <R, E = unknown, A = unknown>(): Effect.Effect<
197 <XE extends E, XA extends A>(
198 effect: Effect.Effect<XA, XE, R>,
199 options?:
200 | {
201 readonly signal?: AbortSignal | undefined
202 readonly scheduler?: Scheduler | undefined
203 readonly onlyIfMissing?: boolean | undefined
204 readonly propagateInterruption?: boolean | undefined
205 }
206 | undefined
207 ) => Fiber.Fiber<XA, XE>,
208 never,
209 Scope.Scope | R
210> =>
211 Effect.flatMap(
212 make<A, E>(),
213 (self) => runtime(self)<R>()
214 )
215
216/**
217 * Creates a scoped run function that forks effects into a new `FiberHandle`
218 * and returns a `Promise` for each effect result.
219 *
220 * **When to use**
221 *
222 * Use when integrating a scoped `FiberHandle` runner with Promise-based APIs
223 * and Promise rejection from squashed failures is the desired boundary.
224 *
225 * **Details**
226 *
227 * Each call stores the fiber in the handle and interrupts the previous fiber
228 * unless `onlyIfMissing` is set. The returned Promise resolves with the
229 * effect's success value or rejects with the squashed failure cause.
230 *
231 * **Example** (Running effects as promises)
232 *
233 * ```ts import.meta.vitest
234 * import { Effect, FiberHandle } from "effect"
235 *
236 * const program = Effect.gen(function*() {
237 * const run = yield* FiberHandle.makeRuntimePromise()
238 *
239 * // Run effects and get promises back
240 * const promise = run(Effect.succeed("hello"))
241 * return yield* Effect.promise(() => promise)
242 * }).pipe(Effect.scoped)
243 *
244 * const actual = await Effect.runPromise(program)
245 * actual // => "hello"
246 * ```
247 *
248 * @category constructors
249 * @since 3.13.0
250 */
251export const makeRuntimePromise = <R = never, A = unknown, E = unknown>(): Effect.Effect<
252 <XE extends E, XA extends A>(
253 effect: Effect.Effect<XA, XE, R>,
254 options?: {
255 readonly signal?: AbortSignal | undefined
256 readonly scheduler?: Scheduler | undefined
257 readonly onlyIfMissing?: boolean | undefined
258 readonly propagateInterruption?: boolean | undefined
259 } | undefined
260 ) => Promise<XA>,
261 never,
262 Scope.Scope | R
263> =>
264 Effect.flatMap(
265 make<A, E>(),
266 (self) => runtimePromise(self)<R>()
267 )
268
269const internalFiberId = -1
270const isInternalInterruption = Filter.toPredicate(Filter.compose(
271 Cause.filterInterruptors,
272 Filter.has(internalFiberId)
273))
274
275/**
276 * Sets the fiber in a FiberHandle. When the fiber completes, it will be removed from the FiberHandle.
277 * If a fiber is already running, it will be interrupted unless `options.onlyIfMissing` is set.
278 *
279 * **When to use**
280 *
281 * Use when an existing forked fiber must be installed synchronously into a
282 * handle and immediate interruption of replaced or closed fibers is acceptable.
283 *
284 * **Example** (Setting a fiber unsafely)
285 *
286 * ```ts import.meta.vitest
287 * import { Effect, Fiber, FiberHandle } from "effect"
288 *
289 * const program = Effect.gen(function*() {
290 * const handle = yield* FiberHandle.make()
291 * const fiber = Effect.runFork(Effect.succeed("hello"))
292 *
293 * // Set the fiber directly (unsafe)
294 * FiberHandle.setUnsafe(handle, fiber)
295 *
296 * // The fiber is now managed by the handle
297 * return yield* Fiber.join(fiber)
298 * })
299 *
300 * const actual = await Effect.runPromise(Effect.scoped(program))
301 * actual // => "hello"
302 * ```
303 *
304 * @category combinators
305 * @since 4.0.0
306 */
307export const setUnsafe: {
308 <A, E, XE extends E, XA extends A>(
309 fiber: Fiber.Fiber<XA, XE>,
310 options?: {
311 readonly onlyIfMissing?: boolean | undefined
312 readonly propagateInterruption?: boolean | undefined
313 }
314 ): (self: FiberHandle<A, E>) => void
315 <A, E, XE extends E, XA extends A>(
316 self: FiberHandle<A, E>,
317 fiber: Fiber.Fiber<XA, XE>,
318 options?: {
319 readonly onlyIfMissing?: boolean | undefined
320 readonly propagateInterruption?: boolean | undefined
321 }
322 ): void
323} = dual((args) => isFiberHandle(args[0]), <A, E, XE extends E, XA extends A>(
324 self: FiberHandle<A, E>,
325 fiber: Fiber.Fiber<XA, XE>,
326 options?: {
327 readonly onlyIfMissing?: boolean | undefined
328 readonly propagateInterruption?: boolean | undefined
329 }
330): void => {
331 if (self.state._tag === "Closed") {
332 fiber.interruptUnsafe(internalFiberId)
333 return
334 } else if (self.state.fiber !== undefined) {
335 if (self.state.fiber === fiber) {
336 return
337 } else if (options?.onlyIfMissing === true) {
338 fiber.interruptUnsafe(internalFiberId)
339 return
340 }
341 self.state.fiber.interruptUnsafe(internalFiberId)
342 self.state.fiber = undefined
343 }
344
345 self.state.fiber = fiber
346 fiber.addObserver((exit) => {
347 if (self.state._tag === "Open" && fiber === self.state.fiber) {
348 self.state.fiber = undefined
349 }
350 if (
351 Exit.isFailure(exit) &&
352 (
353 options?.propagateInterruption === true ?
354 !isInternalInterruption(exit.cause) :
355 !Cause.hasInterruptsOnly(exit.cause)
356 )
357 ) {
358 Deferred.doneUnsafe(self.deferred, exit as any)
359 }
360 })
361})
362
363/**
364 * Sets the fiber in the `FiberHandle`.
365 *
366 * **Details**
367 *
368 * When the fiber completes, it will be removed from the `FiberHandle`. If a
369 * fiber already exists in the `FiberHandle`, it will be interrupted unless
370 * `options.onlyIfMissing` is set.
371 *
372 * **Example** (Setting a fiber safely)
373 *
374 * ```ts import.meta.vitest
375 * import { Effect, Fiber, FiberHandle } from "effect"
376 *
377 * const program = Effect.gen(function*() {
378 * const handle = yield* FiberHandle.make()
379 * const fiber = Effect.runFork(Effect.succeed("hello"))
380 *
381 * // Set the fiber safely
382 * yield* FiberHandle.set(handle, fiber)
383 *
384 * // The fiber is now managed by the handle
385 * return yield* Fiber.join(fiber)
386 * })
387 *
388 * const actual = await Effect.runPromise(Effect.scoped(program))
389 * actual // => "hello"
390 * ```
391 *
392 * @category combinators
393 * @since 2.0.0
394 */
395export const set: {
396 <A, E, XE extends E, XA extends A>(
397 fiber: Fiber.Fiber<XA, XE>,
398 options?: {
399 readonly onlyIfMissing?: boolean
400 readonly propagateInterruption?: boolean | undefined
401 }
402 ): (self: FiberHandle<A, E>) => Effect.Effect<void>
403 <A, E, XE extends E, XA extends A>(
404 self: FiberHandle<A, E>,
405 fiber: Fiber.Fiber<XA, XE>,
406 options?: {
407 readonly onlyIfMissing?: boolean
408 readonly propagateInterruption?: boolean | undefined
409 }
410 ): Effect.Effect<void>
411} = dual((args) => isFiberHandle(args[0]), <A, E, XE extends E, XA extends A>(
412 self: FiberHandle<A, E>,
413 fiber: Fiber.Fiber<XA, XE>,
414 options?: {
415 readonly onlyIfMissing?: boolean
416 readonly propagateInterruption?: boolean | undefined
417 }
418): Effect.Effect<void> =>
419 Effect.sync(() =>
420 setUnsafe(self, fiber, {
421 onlyIfMissing: options?.onlyIfMissing,
422 propagateInterruption: options?.propagateInterruption
423 })
424 ))
425
426/**
427 * Retrieves the fiber from the FiberHandle synchronously.
428 *
429 * **When to use**
430 *
431 * Use when synchronous inspection of the current fiber is needed and an
432 * `Option` result is enough outside the Effect workflow.
433 *
434 * **Example** (Reading the current fiber unsafely)
435 *
436 * ```ts import.meta.vitest
437 * import { Effect, FiberHandle, Option } from "effect"
438 *
439 * const program = Effect.gen(function*() {
440 * const handle = yield* FiberHandle.make()
441 *
442 * // No fiber initially
443 * const emptyFiber = FiberHandle.getUnsafe(handle)
444 *
445 * // Add a fiber
446 * yield* FiberHandle.run(handle, Effect.never)
447 * const fiber = FiberHandle.getUnsafe(handle)
448 * return [emptyFiber, Option.map(fiber, () => true)]
449 * })
450 *
451 * const actual = await Effect.runPromise(Effect.scoped(program))
452 * actual // => [Option.none(), Option.some(true)]
453 * ```
454 *
455 * @category combinators
456 * @since 4.0.0
457 */
458export function getUnsafe<A, E>(self: FiberHandle<A, E>): Option.Option<Fiber.Fiber<A, E>> {
459 return self.state._tag === "Closed" ? Option.none() : Option.fromUndefinedOr(self.state.fiber)
460}
461
462/**
463 * Retrieves the fiber from the FiberHandle effectfully.
464 *
465 * **Example** (Reading the current fiber)
466 *
467 * ```ts import.meta.vitest
468 * import { Effect, FiberHandle, Option } from "effect"
469 *
470 * const program = Effect.gen(function*() {
471 * const handle = yield* FiberHandle.make()
472 *
473 * // Add a fiber
474 * yield* FiberHandle.run(handle, Effect.never)
475 *
476 * // Get the current fiber if present
477 * const fiber = yield* FiberHandle.get(handle)
478 * return Option.map(fiber, () => true)
479 * })
480 *
481 * const actual = await Effect.runPromise(Effect.scoped(program))
482 * actual // => Option.some(true)
483 * ```
484 *
485 * @category combinators
486 * @since 2.0.0
487 */
488export function get<A, E>(self: FiberHandle<A, E>): Effect.Effect<Option.Option<Fiber.Fiber<A, E>>> {
489 return Effect.suspend(() => Effect.succeed(getUnsafe(self)))
490}
491
492/**
493 * Interrupts the fiber currently stored in the `FiberHandle`, if any, and
494 * leaves the handle empty.
495 *
496 * **Example** (Clearing a fiber handle)
497 *
498 * ```ts import.meta.vitest
499 * import { Effect, FiberHandle, Option } from "effect"
500 *
501 * const program = Effect.gen(function*() {
502 * const handle = yield* FiberHandle.make()
503 *
504 * // Add a fiber
505 * yield* FiberHandle.run(handle, Effect.never)
506 *
507 * // Clear the handle, interrupting the fiber
508 * yield* FiberHandle.clear(handle)
509 *
510 * // The handle is now empty
511 * return FiberHandle.getUnsafe(handle)
512 * })
513 *
514 * const actual = await Effect.runPromise(Effect.scoped(program))
515 * actual // => Option.none()
516 * ```
517 *
518 * @category combinators
519 * @since 2.0.0
520 */
521export const clear = <A, E>(self: FiberHandle<A, E>): Effect.Effect<void> =>
522 Effect.uninterruptibleMask((restore) => {
523 if (self.state._tag === "Closed" || self.state.fiber === undefined) {
524 return Effect.void
525 }
526 const fiber = self.state.fiber
527 return Effect.andThen(
528 restore(Fiber.interruptAs(fiber, internalFiberId)),
529 Effect.sync(() => {
530 if (self.state._tag === "Open" && self.state.fiber === fiber) {
531 self.state.fiber = undefined
532 }
533 })
534 )
535 })
536
537const constInterruptedFiber = (function() {
538 let fiber: Fiber.Fiber<never, never> | undefined = undefined
539 return () => {
540 if (fiber === undefined) {
541 fiber = Effect.runFork(Effect.interrupt)
542 }
543 return fiber
544 }
545})()
546
547/**
548 * Forks an Effect and stores the resulting fiber in the `FiberHandle`.
549 *
550 * **Details**
551 *
552 * The handle manages only one fiber: running a new effect interrupts the
553 * previous fiber unless `onlyIfMissing` is set. When the managed fiber
554 * completes, it is removed from the handle.
555 * Set `startImmediately: false` to defer startup. By default, the effect starts
556 * immediately.
557 *
558 * **Example** (Running an effect in a fiber handle)
559 *
560 * ```ts import.meta.vitest
561 * import { Effect, Fiber, FiberHandle } from "effect"
562 *
563 * const program = Effect.gen(function*() {
564 * const handle = yield* FiberHandle.make()
565 *
566 * // Run an effect and get the fiber
567 * const fiber = yield* FiberHandle.run(handle, Effect.succeed("hello"))
568 * const result = yield* Fiber.join(fiber)
569 *
570 * // Running another effect will interrupt the previous one
571 * const fiber2 = yield* FiberHandle.run(handle, Effect.succeed("world"))
572 * const result2 = yield* Fiber.join(fiber2)
573 * return [result, result2]
574 * })
575 *
576 * const actual = await Effect.runPromise(Effect.scoped(program))
577 * actual // => ["hello", "world"]
578 * ```
579 *
580 * @category combinators
581 * @since 2.0.0
582 */
583export const run: {
584 <A, E>(
585 self: FiberHandle<A, E>,
586 options?: {
587 readonly onlyIfMissing?: boolean
588 readonly propagateInterruption?: boolean | undefined
589 readonly startImmediately?: boolean | undefined
590 }
591 ): <R, XE extends E, XA extends A>(
592 effect: Effect.Effect<XA, XE, R>
593 ) => Effect.Effect<Fiber.Fiber<XA, XE>, never, R>
594 <A, E, R, XE extends E, XA extends A>(
595 self: FiberHandle<A, E>,
596 effect: Effect.Effect<XA, XE, R>,
597 options?: {
598 readonly onlyIfMissing?: boolean
599 readonly propagateInterruption?: boolean | undefined
600 readonly startImmediately?: boolean | undefined
601 }
602 ): Effect.Effect<Fiber.Fiber<XA, XE>, never, R>
603} = function() {
604 const self = arguments[0] as FiberHandle
605 if (Effect.isEffect(arguments[1])) {
606 return runImpl(self, arguments[1], arguments[2]) as any
607 }
608 const options = arguments[1]
609 return (effect: Effect.Effect<unknown, unknown, any>) => runImpl(self, effect, options)
610}
611
612const runImpl = <A, E, R, XE extends E, XA extends A>(
613 self: FiberHandle<A, E>,
614 effect: Effect.Effect<XA, XE, R>,
615 options?: {
616 readonly startImmediately?: boolean | undefined
617 readonly onlyIfMissing?: boolean | undefined
618 }
619): Effect.Effect<Fiber.Fiber<XA, XE>, never, R> =>
620 Effect.withFiber((parent) => {
621 if (self.state._tag === "Closed") {
622 return Effect.interrupt
623 } else if (self.state.fiber !== undefined && options?.onlyIfMissing === true) {
624 return Effect.sync(constInterruptedFiber)
625 }
626 const fiber: Fiber.Fiber<XA, XE> = internalEffect.forkUnsafe(
627 parent,
628 effect,
629 options?.startImmediately ?? true,
630 true
631 )
632 setUnsafe(self, fiber, options)
633 return Effect.succeed(fiber)
634 })
635
636/**
637 * Captures the current runtime and returns a function for forking effects into
638 * an existing `FiberHandle`.
639 *
640 * **Details**
641 *
642 * Each call returns the forked fiber, stores it in the handle, and interrupts
643 * the previous fiber unless `onlyIfMissing` is set.
644 *
645 * **Example** (Capturing a runtime for fiber handles)
646 *
647 * ```ts import.meta.vitest
648 * import { Context, Effect, Fiber, FiberHandle } from "effect"
649 *
650 * class Users extends Context.Service<Users, {
651 * readonly getAll: Effect.Effect<Array<unknown>>
652 * }>()("Users") {}
653 *
654 * const program = Effect.gen(function*() {
655 * const handle = yield* FiberHandle.make()
656 * const run = yield* FiberHandle.runtime(handle)<Users>()
657 *
658 * // run an effect and set the fiber in the handle
659 * const fiberA = run(Effect.andThen(Users, (_) => _.getAll))
660 *
661 * // this will interrupt the previous fiber
662 * const fiberB = run(Effect.andThen(Users, (_) => _.getAll))
663 * yield* Fiber.await(fiberA)
664 * return (yield* Fiber.join(fiberB)).length
665 * }).pipe(
666 * Effect.scoped // The fiber will be interrupted when the scope is closed
667 * )
668 *
669 * const actual = await Effect.runPromise(Effect.provideService(program, Users, {
670 * getAll: Effect.succeed([])
671 * }))
672 * actual // => 0
673 * ```
674 *
675 * @category combinators
676 * @since 2.0.0
677 */
678export const runtime: <A, E>(
679 self: FiberHandle<A, E>
680) => <R = never>() => Effect.Effect<
681 <XE extends E, XA extends A>(
682 effect: Effect.Effect<XA, XE, R>,
683 options?:
684 | {
685 readonly signal?: AbortSignal | undefined
686 readonly scheduler?: Scheduler | undefined
687 readonly onlyIfMissing?: boolean | undefined
688 readonly propagateInterruption?: boolean | undefined
689 }
690 | undefined
691 ) => Fiber.Fiber<XA, XE>,
692 never,
693 R
694> = <A, E>(self: FiberHandle<A, E>) => <R>() =>
695 Effect.map(
696 Effect.context<R>(),
697 (services) => {
698 const runFork = Effect.runForkWith(services)
699 return <XE extends E, XA extends A>(
700 effect: Effect.Effect<XA, XE, R>,
701 options?:
702 | {
703 readonly signal?: AbortSignal | undefined
704 readonly scheduler?: Scheduler | undefined
705 readonly onlyIfMissing?: boolean | undefined
706 readonly propagateInterruption?: boolean | undefined
707 }
708 | undefined
709 ) => {
710 if (self.state._tag === "Closed") {
711 return constInterruptedFiber()
712 } else if (self.state.fiber !== undefined && options?.onlyIfMissing === true) {
713 return constInterruptedFiber()
714 }
715 const fiber = runFork(effect, options)
716 setUnsafe(self, fiber, options)
717 return fiber
718 }
719 }
720 )
721
722/**
723 * Captures the current runtime and returns a function for running effects in
724 * an existing `FiberHandle` as Promises.
725 *
726 * **Details**
727 *
728 * Each call stores the forked fiber in the handle and interrupts the previous
729 * fiber unless `onlyIfMissing` is set. The Promise resolves with the effect's
730 * success value or rejects with the squashed failure cause.
731 *
732 * **Example** (Capturing a runtime for promises)
733 *
734 * ```ts import.meta.vitest
735 * import { Effect, FiberHandle } from "effect"
736 *
737 * const program = Effect.gen(function*() {
738 * const handle = yield* FiberHandle.make()
739 * const runPromise = yield* FiberHandle.runtimePromise(handle)<never>()
740 *
741 * // Run an effect and get a promise
742 * const promise = runPromise(Effect.succeed("hello"))
743 * return yield* Effect.promise(() => promise)
744 * })
745 *
746 * const actual = await Effect.runPromise(Effect.scoped(program))
747 * actual // => "hello"
748 * ```
749 *
750 * @category combinators
751 * @since 3.13.0
752 */
753export const runtimePromise = <A, E>(self: FiberHandle<A, E>): <R = never>() => Effect.Effect<
754 <XE extends E, XA extends A>(
755 effect: Effect.Effect<XA, XE, R>,
756 options?:
757 | {
758 readonly signal?: AbortSignal | undefined
759 readonly scheduler?: Scheduler | undefined
760 readonly onlyIfMissing?: boolean | undefined
761 readonly propagateInterruption?: boolean | undefined
762 }
763 | undefined
764 ) => Promise<XA>,
765 never,
766 R
767> =>
768<R>() =>
769 Effect.map(
770 runtime(self)<R>(),
771 (runFork) =>
772 <XE extends E, XA extends A>(
773 effect: Effect.Effect<XA, XE, R>,
774 options?:
775 | {
776 readonly signal?: AbortSignal | undefined
777 readonly scheduler?: Scheduler | undefined
778 readonly onlyIfMissing?: boolean | undefined
779 readonly propagateInterruption?: boolean | undefined
780 }
781 | undefined
782 ): Promise<XA> =>
783 new Promise((resolve, reject) =>
784 runFork(effect, options).addObserver((exit) => {
785 if (Exit.isSuccess(exit)) {
786 resolve(exit.value)
787 } else {
788 reject(Cause.squash(exit.cause))
789 }
790 })
791 )
792 )
793
794/**
795 * Waits for the `FiberHandle` to fail or close.
796 *
797 * **Details**
798 *
799 * The returned Effect fails with the first managed fiber failure that is not
800 * ignored by the handle's interruption rules. Normal successful completion of
801 * a managed fiber only removes it from the handle; use `awaitEmpty` to wait
802 * for the current fiber to finish.
803 *
804 * **Example** (Propagating fiber failures)
805 *
806 * ```ts import.meta.vitest
807 * import { Effect, Exit, FiberHandle } from "effect"
808 *
809 * const program = Effect.gen(function*() {
810 * const handle = yield* FiberHandle.make()
811 * yield* FiberHandle.set(handle, Effect.runFork(Effect.fail("error")))
812 *
813 * // parent fiber will fail with "error"
814 * yield* FiberHandle.join(handle)
815 * })
816 *
817 * const actual = await Effect.runPromise(Effect.exit(Effect.scoped(program)))
818 * actual // => Exit.fail("error")
819 * ```
820 *
821 * @category combinators
822 * @since 2.0.0
823 */
824export const join = <A, E>(self: FiberHandle<A, E>): Effect.Effect<void, E> =>
825 Deferred.await(self.deferred as Deferred.Deferred<void, E>)
826
827/**
828 * Waits for the fiber in the FiberHandle to complete.
829 *
830 * **Example** (Waiting for a fiber to complete)
831 *
832 * ```ts import.meta.vitest
833 * import { Effect, FiberHandle, Option } from "effect"
834 *
835 * const program = Effect.gen(function*() {
836 * const handle = yield* FiberHandle.make()
837 *
838 * yield* FiberHandle.run(handle, Effect.yieldNow)
839 *
840 * // Wait for the fiber to complete
841 * yield* FiberHandle.awaitEmpty(handle)
842 *
843 * return yield* FiberHandle.get(handle)
844 * })
845 *
846 * const actual = await Effect.runPromise(Effect.scoped(program))
847 * actual // => Option.none()
848 * ```
849 *
850 * @category combinators
851 * @since 3.13.0
852 */
853export const awaitEmpty = <A, E>(self: FiberHandle<A, E>): Effect.Effect<void, E> =>
854 Effect.suspend(() => {
855 if (self.state._tag === "Closed" || self.state.fiber === undefined) {
856 return Effect.void
857 }
858 return Fiber.await(self.state.fiber)
859 })