@effect-ts/system
Version:
Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.
1,449 lines (1,352 loc) • 42.5 kB
text/typescript
// ets_tracing: off
import * as cause from "../Cause/index.js"
import * as ChunkCollect from "../Collections/Immutable/Chunk/api/collect.js"
import * as ChunkFilter from "../Collections/Immutable/Chunk/api/filter.js"
import * as ChunkForEach from "../Collections/Immutable/Chunk/api/forEach.js"
import * as ChunkIndexWhere from "../Collections/Immutable/Chunk/api/indexWhere.js"
import * as ChunkSplitAt from "../Collections/Immutable/Chunk/api/splitAt.js"
import * as ChunkZip from "../Collections/Immutable/Chunk/api/zip.js"
import * as Chunk from "../Collections/Immutable/Chunk/core.js"
import * as L from "../Collections/Immutable/List/core.js"
import * as Tp from "../Collections/Immutable/Tuple/index.js"
import type { Exit } from "../Exit/index.js"
import * as Ex from "../Exit/index.js"
import type { FiberContext } from "../Fiber/context.js"
import type * as Fiber from "../Fiber/core.js"
import { interrupt as fiberInterrupt } from "../Fiber/interrupt.js"
import { identity, pipe } from "../Function/index.js"
import * as I from "../Iterable/index.js"
import type { Managed } from "../Managed/managed.js"
import { managedApply } from "../Managed/managed.js"
import { add } from "../Managed/ReleaseMap/add.js"
import { Exited } from "../Managed/ReleaseMap/Exited.js"
import type { ReleaseMap, State } from "../Managed/ReleaseMap/index.js"
import { makeReleaseMap } from "../Managed/ReleaseMap/makeReleaseMap.js"
import * as O from "../Option/index.js"
import type { Promise } from "../Promise/index.js"
import * as Q from "../Queue/core.js"
import { XQueueInternal } from "../Queue/xqueue.js"
import { AtomicBoolean } from "../Support/AtomicBoolean/index.js"
import type { MutableQueue } from "../Support/MutableQueue/index.js"
import { Bounded, EmptyQueue, Unbounded } from "../Support/MutableQueue/index.js"
import * as asUnit from "./asUnit.js"
import * as bracket from "./bracket.js"
import { bracketExit_ } from "./bracketExit.js"
import * as catchAll from "./catchAll.js"
import * as core from "./core.js"
import * as coreScope from "./core-scope.js"
import * as Do from "./do.js"
import { done } from "./done.js"
import type { Effect, UIO } from "./effect.js"
import * as ensuring from "./ensuring.js"
import { environment } from "./environment.js"
import * as Ref from "./excl-deps-ref.js"
import * as promise from "./excl-forEach-promise.js"
import type { ExecutionStrategy } from "./ExecutionStrategy.js"
import { sequential } from "./ExecutionStrategy.js"
import * as fiberId from "./fiberId.js"
import * as flatten from "./flatten.js"
import * as ifM from "./ifM.js"
import * as interruption from "./interruption.js"
import * as map from "./map.js"
import { provideSome_ } from "./provideSome.js"
import * as tap from "./tap.js"
import * as tapCause from "./tapCause.js"
import { toManaged } from "./toManaged.js"
import * as whenM from "./whenM.js"
import * as zips from "./zips.js"
/**
* Applies the function `f` to each element of the `Iterable<A>` and
* returns the results in a new `readonly B[]`.
*
* For a parallel version of this method, see `forEachPar`.
* If you do not need the results, see `forEachUnit` for a more efficient implementation.
*/
export function forEach_<A, R, E, B>(
as: Iterable<A>,
f: (a: A) => Effect<R, E, B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
return core.suspend(() => {
const acc: B[] = []
return map.map_(
forEachUnit_(as, (a) =>
map.map_(f(a), (b) => {
acc.push(b)
})
),
() => Chunk.from(acc)
)
}, __trace)
}
/**
* Same as `forEach_`, except that the function `f` is supplied
* a second argument that corresponds to the index (starting from 0)
* of the current element being iterated over.
*/
export function forEachWithIndex_<A, R, E, B>(
as: Iterable<A>,
f: (a: A, i: number) => Effect<R, E, B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
return core.suspend(() => {
let index = 0
const acc: B[] = []
return map.map_(
forEachUnit_(as, (a) =>
map.map_(f(a, index), (b) => {
acc.push(b)
index++
})
),
() => Chunk.from(acc)
)
}, __trace)
}
/**
* Applies the function `f` to each element of the `Iterable<A>` and
* returns the results in a new `readonly B[]`.
*
* For a parallel version of this method, see `forEachPar`.
* If you do not need the results, see `forEachUnit` for a more efficient implementation.
*
* @ets_data_first forEach_
*/
export function forEach<A, R, E, B>(f: (a: A) => Effect<R, E, B>, __trace?: string) {
return (as: Iterable<A>) => forEach_(as, f, __trace)
}
/**
* Same as `forEach`, except that the function `f` is supplied
* a second argument that corresponds to the index (starting from 0)
* of the current element being iterated over.
*
* @ets_data_first forEachWithIndex_
*/
export function forEachWithIndex<A, R, E, B>(
f: (a: A, i: number) => Effect<R, E, B>,
__trace?: string
) {
return (as: Iterable<A>) => forEachWithIndex_(as, f, __trace)
}
function forEachUnitLoop<R, E, A, X>(
iterator: Iterator<A, any, undefined>,
f: (a: A) => Effect<R, E, X>
): Effect<R, E, void> {
const next = iterator.next()
return next.done
? core.unit
: core.chain_(f(next.value), () => forEachUnitLoop(iterator, f))
}
/**
* Applies the function `f` to each element of the `Iterable<A>` and runs
* produced effects sequentially.
*
* Equivalent to `asUnit(forEach(as, f))`, but without the cost of building
* the list of results.
*/
export function forEachUnit_<R, E, A, X>(
as: Iterable<A>,
f: (a: A) => Effect<R, E, X>,
__trace?: string
): Effect<R, E, void> {
return core.suspend(() => forEachUnitLoop(as[Symbol.iterator](), f), __trace)
}
/**
* Applies the function `f` to each element of the `Iterable<A>` and runs
* produced effects sequentially.
*
* Equivalent to `asUnit(forEach(as, f))`, but without the cost of building
* the list of results.
*
* @ets_data_first forEachUnit_
*/
export function forEachUnit<R, E, A, X>(
f: (a: A) => Effect<R, E, X>,
__trace?: string
): (as: Iterable<A>) => Effect<R, E, void> {
return (as) => forEachUnit_(as, f, __trace)
}
/**
* Applies the function `f` to each element of the `Iterable<A>` and runs
* produced effects in parallel, discarding the results.
*
* For a sequential version of this method, see `forEach_`.
*
* Optimized to avoid keeping full tree of effects, so that method could be
* able to handle large input sequences.
* Behaves almost like this code:
*
* Additionally, interrupts all effects on any failure.
*/
export function forEachUnitPar_<R, E, A, X>(
as: Iterable<A>,
f: (a: A) => Effect<R, E, X>,
__trace?: string
): Effect<R, E, void> {
const collection = L.from(as)
const size = L.size(collection)
if (L.isEmpty(collection)) {
return core.unit
}
return core.suspend(
() =>
pipe(
Do.do,
Do.bind("parentId", () => fiberId.fiberId),
Do.bind("causes", () => Ref.makeRef<cause.Cause<E>>(cause.empty)),
Do.bind("result", () => promise.make<void, void>()),
Do.bind("status", () =>
Ref.makeRef(Tp.tuple<[number, number, boolean]>(0, 0, false))
),
Do.let("startTask", ({ status }) =>
pipe(
status,
Ref.modify(({ tuple: [started, done, failing] }) => {
if (failing) {
return Tp.tuple(false, Tp.tuple(started, done, failing))
}
return Tp.tuple(true, Tp.tuple(started + 1, done, failing))
})
)
),
Do.let("startFailure", ({ result, status }) =>
pipe(
status,
Ref.update(({ tuple: [started, done, _] }) =>
Tp.tuple(started, done, true)
),
zips.zipRight(promise.fail<void>(undefined)(result))
)
),
Do.let(
"task",
({ causes, parentId, result, startFailure, startTask, status }) =>
(a: A) =>
pipe(
ifM.ifM_(
startTask,
() =>
pipe(
core.suspend(() => f(a)),
interruption.interruptible,
tapCause.tapCause((c) =>
pipe(
causes,
Ref.update((_) => cause.combinePar(_, c)),
zips.zipRight(startFailure)
)
),
ensuring.ensuring(
(() => {
const isComplete = pipe(
status,
Ref.modify(({ tuple: [started, done, failing] }) => {
const newDone = done + 1
return Tp.tuple(
(failing ? started : size) === newDone,
Tp.tuple(started, newDone, failing)
)
})
)
return pipe(
promise.succeed<void>(undefined)(result),
whenM.whenM(isComplete)
)
})()
)
),
() =>
pipe(
causes,
Ref.update((_) => cause.combinePar(_, cause.interrupt(parentId)))
)
),
interruption.uninterruptible
)
),
Do.bind("fibers", ({ task }) =>
coreScope.transplant((graft) =>
forEach_(collection, (a) => core.fork(graft(task(a))))
)
),
Do.let("interrupter", ({ fibers, parentId, result }) =>
pipe(
result,
promise.await,
catchAll.catchAll(() =>
pipe(
forEach_(fibers, (_) => core.fork(_.interruptAs(parentId))),
core.chain(fiberJoinAll)
)
),
forkManaged
)
),
tap.tap(({ causes, fibers, interrupter, result }) =>
managedUse_(interrupter, () =>
pipe(
result,
promise.fail<void>(undefined),
zips.zipRight(pipe(causes.get, core.chain(core.halt))),
whenM.whenM(
pipe(
forEach_(fibers, (_) => _.await),
map.map(
(_) =>
ChunkIndexWhere.indexWhere_(_, (ex) => !Ex.succeeded(ex)) !== -1
)
)
)
)
)
),
tap.tap(({ fibers }) => forEach_(fibers, (_) => _.inheritRefs)),
asUnit.asUnit
),
__trace
)
}
/**
* Forks the fiber in a `Managed`. Using the `Managed` value will
* execute the effect in the fiber, while ensuring its interruption when
* the effect supplied to `use` completes.
*/
export function forkManaged<R, E, A>(
self: Effect<R, E, A>,
__trace?: string
): Managed<R, never, FiberContext<E, A>> {
return managedFork(toManaged(self), __trace)
}
/**
* Applies the function `f` to each element of the `Iterable<A>` and runs
* produced effects in parallel, discarding the results.
*
* For a sequential version of this method, see `forEach_`.
*
* Optimized to avoid keeping full tree of effects, so that method could be
* able to handle large input sequences.
* Behaves almost like this code:
*
* Additionally, interrupts all effects on any failure.
*
* @ets_data_first forEachUnitPar_
*/
export function forEachUnitPar<R, E, A, X>(
f: (a: A) => Effect<R, E, X>,
__trace?: string
) {
return (as: Iterable<A>) => forEachUnitPar_(as, f, __trace)
}
/**
* Applies the function `f` to each element of the `Iterable<A>` in parallel,
* and returns the results in a new `readonly B[]`.
*
* For a sequential version of this method, see `forEach`.
*/
export function forEachPar_<R, E, A, B>(
as: Iterable<A>,
f: (a: A) => Effect<R, E, B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
return core.suspend(
() =>
core.chain_(
core.succeedWith<B[]>(() => []),
(array) =>
map.map_(
forEachUnitPar_(
I.map_(as, (a, n) => [a, n] as [A, number]),
([a, n]) =>
core.chain_(
core.suspend(() => f(a)),
(b) =>
core.succeedWith(() => {
array[n] = b
})
)
),
() => Chunk.from(array)
)
),
__trace
)
}
/**
* Same as `forEachPar_`, except that the function `f` is supplied
* a second argument that corresponds to the index (starting from 0)
* of the current element being iterated over.
*/
export function forEachParWithIndex_<R, E, A, B>(
as: Iterable<A>,
f: (a: A, i: number) => Effect<R, E, B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
return core.suspend(
() =>
core.chain_(
core.succeedWith<B[]>(() => []),
(array) =>
map.map_(
forEachUnitPar_(
I.map_(as, (a, n) => [a, n] as [A, number]),
([a, n]) =>
core.chain_(
core.suspend(() => f(a, n)),
(b) =>
core.succeedWith(() => {
array[n] = b
})
)
),
() => Chunk.from(array)
)
),
__trace
)
}
/**
* Applies the function `f` to each element of the `Iterable<A>` in parallel,
* and returns the results in a new `readonly B[]`.
*
* For a sequential version of this method, see `forEach`.
*
* @ets_data_first forEachPar_
*/
export function forEachPar<R, E, A, B>(f: (a: A) => Effect<R, E, B>, __trace?: string) {
return (as: Iterable<A>): Effect<R, E, Chunk.Chunk<B>> => forEachPar_(as, f, __trace)
}
/**
* Same as `forEachPar`, except that the function `f` is supplied
* a second argument that corresponds to the index (starting from 0)
* of the current element being iterated over.
*/
export function forEachParWithIndex<R, E, A, B>(
f: (a: A, i: number) => Effect<R, E, B>,
__trace?: string
) {
return (as: Iterable<A>): Effect<R, E, Chunk.Chunk<B>> =>
forEachParWithIndex_(as, f, __trace)
}
/**
* Applies the function `f` to each element of the `Iterable[A]` and runs
* produced effects in parallel, discarding the results.
*
* Unlike `forEachUnitPar_`, this method will use at most up to `n` fibers.
*/
export function forEachUnitParN_<R, E, A, X>(
as: Iterable<A>,
n: number,
f: (a: A) => Effect<R, E, X>,
__trace?: string
): Effect<R, E, void> {
const as_ = L.from(as)
const size = L.size(as_)
function worker(q: Q.Queue<A>, ref: Ref.Ref<number>): Effect<R, E, void> {
return pipe(
Q.take(q),
core.chain(f),
core.chain(() => worker(q, ref)),
whenM.whenM(
pipe(
ref,
Ref.modify((n) => Tp.tuple(n > 0, n - 1))
)
)
)
}
return core.suspend(
() =>
pipe(
makeBoundedQueue<A>(n),
bracket.bracket(
(q) =>
pipe(
Do.do,
Do.bind("ref", () => Ref.makeRef(size)),
tap.tap(() => core.fork(forEachUnit_(as, (x) => Q.offer_(q, x)))),
Do.bind("fibers", ({ ref }) =>
collectAll(L.map_(L.range_(0, n), () => core.fork(worker(q, ref))))
),
tap.tap(({ fibers }) => forEach_(fibers, (_) => _.await))
),
(q) => Q.shutdown(q)
)
),
__trace
)
}
/**
* Applies the function `f` to each element of the `Iterable[A]` and runs
* produced effects in parallel, discarding the results.
*
* Unlike `forEachUnitPar_`, this method will use at most up to `n` fibers.
*
* @ets_data_first forEachUnitParN_
*/
export function forEachUnitParN<R, E, A, X>(
n: number,
f: (a: A) => Effect<R, E, X>,
__trace?: string
) {
return (as: Iterable<A>) => forEachUnitParN_(as, n, f, __trace)
}
/**
* Applies the functionw `f` to each element of the `Iterable<A>` in parallel,
* and returns the results in a new `readonly B[]`.
*
* Unlike `forEachPar`, this method will use at most up to `n` fibers.
*/
export function forEachParN_<R, E, A, B>(
as: Iterable<A>,
n: number,
f: (a: A) => Effect<R, E, B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
function worker(
q: Q.Queue<Tp.Tuple<[promise.Promise<E, B>, A]>>,
pairs: Iterable<Tp.Tuple<[promise.Promise<E, B>, A]>>,
ref: Ref.Ref<number>
): Effect<R, never, void> {
return pipe(
Q.take(q),
core.chain(({ tuple: [p, a] }) =>
pipe(
core.suspend(() => f(a)),
core.foldCauseM(
(c) => forEach_(pairs, (_) => pipe(_.get(0), promise.halt(c))),
(b) => pipe(p, promise.succeed(b))
)
)
),
core.chain(() => worker(q, pairs, ref)),
whenM.whenM(
pipe(
ref,
Ref.modify((n) => Tp.tuple(n > 0, n - 1))
)
)
)
}
return core.suspend(
() =>
pipe(
makeBoundedQueue<Tp.Tuple<[promise.Promise<E, B>, A]>>(n),
bracket.bracket(
(q) =>
pipe(
Do.do,
Do.bind("pairs", () =>
forEach_(as, (a) =>
pipe(
promise.make<E, B>(),
map.map((p) => Tp.tuple(p, a))
)
)
),
Do.bind("ref", ({ pairs }) => Ref.makeRef(Chunk.size(pairs))),
tap.tap(({ pairs }) =>
core.fork(forEach_(pairs, (pair) => Q.offer_(q, pair)))
),
tap.tap(({ pairs, ref }) =>
collectAllUnit(
pipe(
L.range_(0, n),
L.map(() => core.fork(worker(q, pairs, ref)))
)
)
),
core.chain(({ pairs }) => forEach_(pairs, (_) => promise.await(_.get(0))))
),
Q.shutdown
)
),
__trace
)
}
/**
* Same as `forEachParN_`, except that the function `f` is supplied
* a second argument that corresponds to the index (starting from 0)
* of the current element being iterated over.
*/
export function forEachParWithIndexN_<R, E, A, B>(
as: Iterable<A>,
n: number,
f: (a: A, i: number) => Effect<R, E, B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
function worker(
q: Q.Queue<Tp.Tuple<[promise.Promise<E, B>, A, number]>>,
pairs: Iterable<Tp.Tuple<[promise.Promise<E, B>, A, number]>>,
ref: Ref.Ref<number>
): Effect<R, never, void> {
return pipe(
Q.take(q),
core.chain(({ tuple: [p, a, i] }) =>
pipe(
core.suspend(() => f(a, i)),
core.foldCauseM(
(c) => forEach_(pairs, (_) => pipe(_.get(0), promise.halt(c))),
(b) => pipe(p, promise.succeed(b))
)
)
),
core.chain(() => worker(q, pairs, ref)),
whenM.whenM(
pipe(
ref,
Ref.modify((n) => Tp.tuple(n > 0, n - 1))
)
)
)
}
return core.suspend(
() =>
pipe(
makeBoundedQueue<Tp.Tuple<[promise.Promise<E, B>, A, number]>>(n),
bracket.bracket(
(q) =>
pipe(
Do.do,
Do.bind("pairs", () =>
forEachWithIndex_(as, (a, i) =>
pipe(
promise.make<E, B>(),
map.map((p) => Tp.tuple(p, a, i))
)
)
),
Do.bind("ref", ({ pairs }) => Ref.makeRef(Chunk.size(pairs))),
tap.tap(({ pairs }) =>
core.fork(forEach_(pairs, (pair) => Q.offer_(q, pair)))
),
tap.tap(({ pairs, ref }) =>
collectAllUnit(
pipe(
L.range_(0, n),
L.map(() => core.fork(worker(q, pairs, ref)))
)
)
),
core.chain(({ pairs }) => forEach_(pairs, (_) => promise.await(_.get(0))))
),
Q.shutdown
)
),
__trace
)
}
/**
* Applies the functionw `f` to each element of the `Iterable<A>` in parallel,
* and returns the results in a new `readonly B[]`.
*
* Unlike `forEachPar`, this method will use at most up to `n` fibers.
*
* @ets_data_first forEachParN_
*/
export function forEachParN<R, E, A, B>(
n: number,
f: (a: A) => Effect<R, E, B>,
__trace?: string
) {
return (as: Iterable<A>) => forEachParN_(as, n, f, __trace)
}
/**
* Same as `forEachParN`, except that the function `f` is supplied
* a second argument that corresponds to the index (starting from 0)
* of the current element being iterated over.
*
* @ets_data_first forEachParWithIndexN_
*/
export function forEachParWithIndexN<R, E, A, B>(
n: number,
f: (a: A, i: number) => Effect<R, E, B>,
__trace?: string
) {
return (as: Iterable<A>) => forEachParWithIndexN_(as, n, f, __trace)
}
/**
* Applies the function `f` to each element of the `Iterable<A>` in parallel,
* and returns the results in a new `readonly B[]`.
*
* For a sequential version of this method, see `forEach`.
*/
export function forEachExec_<R, E, A, B>(
as: Iterable<A>,
es: ExecutionStrategy,
f: (a: A) => Effect<R, E, B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
switch (es._tag) {
case "Sequential": {
return forEach_(as, f, __trace) as any
}
case "Parallel": {
return forEachPar_(as, f, __trace) as any
}
case "ParallelN": {
return forEachParN_(as, es.n, f, __trace) as any
}
}
}
/**
* Applies the function `f` to each element of the `Iterable<A>` in parallel,
* and returns the results in a new `readonly B[]`.
*
* For a sequential version of this method, see `forEach`.
*
* @ets_data_first forEachExec_
*/
export function forEachExec<R, E, A, B>(
es: ExecutionStrategy,
f: (a: A) => Effect<R, E, B>,
__trace?: string
): (as: Iterable<A>) => Effect<R, E, Chunk.Chunk<B>> {
return (as) => forEachExec_(as, es, f, __trace)
}
/**
* Evaluate each effect in the structure from left to right, and collect the
* results. For a parallel version, see `collectAllPar`.
*/
export function collectAll<R, E, A>(as: Iterable<Effect<R, E, A>>, __trace?: string) {
return forEach_(as, identity, __trace)
}
/**
* Evaluate each effect in the structure in parallel, and collect the
* results. For a sequential version, see `collectAll`.
*/
export function collectAllPar<R, E, A>(
as: Iterable<Effect<R, E, A>>,
__trace?: string
) {
return forEachPar_(as, identity, __trace)
}
/**
* Evaluate each effect in the structure in parallel, and collect the
* results. For a sequential version, see `collectAll`.
*
* Unlike `collectAllPar`, this method will use at most `n` fibers.
*
* @ets_data_first collectAllParN_
*/
export function collectAllParN(n: number, __trace?: string) {
return <R, E, A>(as: Iterable<Effect<R, E, A>>) =>
forEachParN_(as, n, identity, __trace)
}
/**
* Evaluate each effect in the structure in parallel, and collect the
* results. For a sequential version, see `collectAll`.
*
* Unlike `collectAllPar`, this method will use at most `n` fibers.
*/
export function collectAllParN_<R, E, A>(
as: Iterable<Effect<R, E, A>>,
n: number,
__trace?: string
) {
return forEachParN_(as, n, identity, __trace)
}
/**
* Evaluate each effect in the structure from left to right, and discard the
* results. For a parallel version, see `collectAllUnitPar`.
*/
export function collectAllUnit<R, E, A>(
as: Iterable<Effect<R, E, A>>,
__trace?: string
) {
return forEachUnit_(as, identity, __trace)
}
/**
* Evaluate each effect in the structure in parallel, and discard the
* results. For a sequential version, see `collectAllUnit`.
*/
export function collectAllUnitPar<R, E, A>(
as: Iterable<Effect<R, E, A>>,
__trace?: string
) {
return forEachUnitPar_(as, identity, __trace)
}
/**
* Evaluate each effect in the structure in parallel, and discard the
* results. For a sequential version, see `collectAllUnit`.
*
* Unlike `collectAllUnitPar`, this method will use at most `n` fibers.
*
* @ets_data_first collectAllUnitParN_
*/
export function collectAllUnitParN(n: number, __trace?: string) {
return <R, E, A>(as: Iterable<Effect<R, E, A>>) =>
forEachUnitParN_(as, n, identity, __trace)
}
/**
* Evaluate each effect in the structure in parallel, and discard the
* results. For a sequential version, see `collectAllUnit`.
*
* Unlike `collectAllUnitPar`, this method will use at most `n` fibers.
*/
export function collectAllUnitParN_<R, E, A>(
as: Iterable<Effect<R, E, A>>,
n: number,
__trace?: string
) {
return forEachUnitParN_(as, n, identity, __trace)
}
/**
* Evaluate each effect in the structure with `collectAll`, and collect
* the results with given partial function.
*/
export function collectAllWith_<R, E, A, B>(
as: Iterable<Effect<R, E, A>>,
pf: (a: A) => O.Option<B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
return map.map_(collectAll(as, __trace), ChunkCollect.collect(pf))
}
/**
* Evaluate each effect in the structure with `collectAll`, and collect
* the results with given partial function.
*
* @ets_data_first collectAllWith_
*/
export function collectAllWith<A, B>(pf: (a: A) => O.Option<B>, __trace?: string) {
return <R, E>(as: Iterable<Effect<R, E, A>>) => collectAllWith_(as, pf, __trace)
}
/**
* Evaluate each effect in the structure with `collectAll`, and collect
* the results with given partial function.
*/
export function collectAllWithPar_<R, E, A, B>(
as: Iterable<Effect<R, E, A>>,
pf: (a: A) => O.Option<B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
return map.map_(collectAllPar(as, __trace), ChunkCollect.collect(pf))
}
/**
* Evaluate each effect in the structure with `collectAll`, and collect
* the results with given partial function.
*
* @ets_data_first collectAllWithPar_
*/
export function collectAllWithPar<A, B>(pf: (a: A) => O.Option<B>, __trace?: string) {
return <R, E>(as: Iterable<Effect<R, E, A>>) => collectAllWithPar_(as, pf, __trace)
}
/**
* Evaluate each effect in the structure with `collectAllPar`, and collect
* the results with given partial function.
*
* Unlike `collectAllWithPar`, this method will use at most up to `n` fibers.
*/
export function collectAllWithParN_<R, E, A, B>(
as: Iterable<Effect<R, E, A>>,
n: number,
pf: (a: A) => O.Option<B>,
__trace?: string
): Effect<R, E, Chunk.Chunk<B>> {
return map.map_(collectAllParN_(as, n, __trace), ChunkCollect.collect(pf))
}
/**
* Evaluate each effect in the structure with `collectAllPar`, and collect
* the results with given partial function.
*
* Unlike `collectAllWithPar`, this method will use at most up to `n` fibers.
*
* @ets_data_first collectAllWithParN_
*/
export function collectAllWithParN<A, B>(
n: number,
pf: (a: A) => O.Option<B>,
__trace?: string
): <R, E>(as: Iterable<Effect<R, E, A>>) => Effect<R, E, Chunk.Chunk<B>> {
return (as) => collectAllWithParN_(as, n, pf, __trace)
}
/**
* Evaluate and run each effect in the structure and collect discarding failed ones.
*/
export function collectAllSuccesses<R, E, A>(
as: Iterable<Effect<R, E, A>>,
__trace?: string
) {
return collectAllWith_(
I.map_(as, (x) => core.result(x)),
(e) => (e._tag === "Success" ? O.some(e.value) : O.none),
__trace
)
}
/**
* Evaluate and run each effect in the structure in parallel, and collect discarding failed ones.
*/
export function collectAllSuccessesPar<R, E, A>(
as: Iterable<Effect<R, E, A>>,
__trace?: string
) {
return collectAllWithPar_(
I.map_(as, (x) => core.result(x)),
(e) => (e._tag === "Success" ? O.some(e.value) : O.none),
__trace
)
}
/**
* Evaluate and run each effect in the structure in parallel, and collect discarding failed ones.
*
* Unlike `collectAllSuccessesPar`, this method will use at most up to `n` fibers.
*/
export function collectAllSuccessesParN_<R, E, A>(
as: Iterable<Effect<R, E, A>>,
n: number,
__trace?: string
) {
return collectAllWithParN_(
I.map_(as, (x) => core.result(x)),
n,
(e) => (e._tag === "Success" ? O.some(e.value) : O.none),
__trace
)
}
/**
* Evaluate and run each effect in the structure in parallel, and collect discarding failed ones.
*
* Unlike `collectAllSuccessesPar`, this method will use at most up to `n` fibers.
*
* @ets_data_first collectAllSuccessesParN_
*/
export function collectAllSuccessesParN(n: number, __trace?: string) {
return <R, E, A>(as: Iterable<Effect<R, E, A>>) =>
collectAllSuccessesParN_(as, n, __trace)
}
/**
* Joins all fibers, awaiting their _successful_ completion.
* Attempting to join a fiber that has erred will result in
* a catchable error, _if_ that error does not result from interruption.
*/
export function fiberJoinAll<E, A>(as: Iterable<Fiber.Fiber<E, A>>, __trace?: string) {
return tap.tap_(
core.chain_(fiberWaitAll(as), done),
() => forEach_(as, (f) => f.inheritRefs),
__trace
)
}
/**
* Awaits on all fibers to be completed, successfully or not.
*/
export function fiberWaitAll<E, A>(as: Iterable<Fiber.Fiber<E, A>>, __trace?: string) {
return core.result(
forEachPar_(as, (f) => core.chain_(f.await, done)),
__trace
)
}
/**
* Releases all the finalizers in the releaseMap according to the ExecutionStrategy
*/
export function releaseMapReleaseAll(
exit: Exit<any, any>,
execStrategy: ExecutionStrategy,
__trace?: string
): (_: ReleaseMap) => UIO<any> {
return (_: ReleaseMap) =>
pipe(
_.ref,
Ref.modify((s): Tp.Tuple<[UIO<any>, State]> => {
switch (s._tag) {
case "Exited": {
return Tp.tuple(core.unit, s)
}
case "Running": {
switch (execStrategy._tag) {
case "Sequential": {
return Tp.tuple(
core.chain_(
forEach_(
Array.from(s.finalizers()).reverse(),
([_, f]) => core.result(f(exit)),
__trace
),
(e) => done(O.getOrElse_(Ex.collectAll(...e), () => Ex.succeed([])))
),
new Exited(s.nextKey, exit)
)
}
case "Parallel": {
return Tp.tuple(
core.chain_(
forEachPar_(
Array.from(s.finalizers()).reverse(),
([_, f]) => core.result(f(exit)),
__trace
),
(e) =>
done(O.getOrElse_(Ex.collectAllPar(...e), () => Ex.succeed([])))
),
new Exited(s.nextKey, exit)
)
}
case "ParallelN": {
return Tp.tuple(
core.chain_(
forEachParN_(
Array.from(s.finalizers()).reverse(),
execStrategy.n,
([_, f]) => core.result(f(exit)),
__trace
),
(e) =>
done(O.getOrElse_(Ex.collectAllPar(...e), () => Ex.succeed([])))
),
new Exited(s.nextKey, exit)
)
}
}
}
}
}),
flatten.flatten
)
}
/**
* Creates a `Managed` value that acquires the original resource in a fiber,
* and provides that fiber. The finalizer for this value will interrupt the fiber
* and run the original finalizer.
*/
export function managedFork<R, E, A>(
self: Managed<R, E, A>,
__trace?: string
): Managed<R, never, FiberContext<E, A>> {
return managedApply(
interruption.uninterruptibleMask(({ restore }) =>
pipe(
Do.do,
Do.bind("tp", () => environment<Tp.Tuple<[R, ReleaseMap]>>()),
Do.let("r", ({ tp }) => tp.get(0)),
Do.let("outerReleaseMap", ({ tp }) => tp.get(1)),
Do.bind("innerReleaseMap", () => makeReleaseMap),
Do.bind("fiber", ({ innerReleaseMap, r }) =>
restore(
pipe(
self.effect,
map.map((_) => _.get(1)),
coreScope.forkDaemon,
core.provideAll(Tp.tuple(r, innerReleaseMap), __trace)
)
)
),
Do.bind("releaseMapEntry", ({ fiber, innerReleaseMap, outerReleaseMap }) =>
add((e) =>
pipe(
fiber,
fiberInterrupt,
core.chain(
() => releaseMapReleaseAll(e, sequential)(innerReleaseMap),
__trace
)
)
)(outerReleaseMap)
),
map.map(({ fiber, releaseMapEntry }) => Tp.tuple(releaseMapEntry, fiber))
)
)
)
}
/**
* Run an effect while acquiring the resource before and releasing it after
*/
export function managedUse_<R, E, A, R2, E2, B>(
self: Managed<R, E, A>,
f: (a: A) => Effect<R2, E2, B>,
__trace?: string
): Effect<R & R2, E | E2, B> {
return bracketExit_(
makeReleaseMap,
(rm) =>
core.chain_(
provideSome_(self.effect, (r: R) => Tp.tuple(r, rm)),
(a) => f(a.get(1)),
__trace
),
(rm, ex) => releaseMapReleaseAll(ex, sequential, __trace)(rm)
)
}
export class BackPressureStrategy<A> implements Q.Strategy<A> {
private putters = new Unbounded<Tp.Tuple<[A, Promise<never, boolean>, boolean]>>()
handleSurplus(
as: Chunk.Chunk<A>,
queue: MutableQueue<A>,
takers: MutableQueue<Promise<never, A>>,
isShutdown: AtomicBoolean
): UIO<boolean> {
return core.suspend((_, fiberId) => {
const p = promise.unsafeMake<never, boolean>(fiberId)
return interruption.onInterrupt_(
core.suspend(() => {
this.unsafeOffer(as, p)
this.unsafeOnQueueEmptySpace(queue, takers)
Q.unsafeCompleteTakers(this, queue, takers)
if (isShutdown.get) {
return interruption.interrupt
} else {
return promise.await(p)
}
}),
() => core.succeedWith(() => this.unsafeRemove(p))
)
})
}
unsafeRemove(p: Promise<never, boolean>) {
Q.unsafeOfferAll(
this.putters,
ChunkFilter.filter_(Q.unsafePollAll(this.putters), ([_, __]) => __ !== p)
)
}
unsafeOffer(as: Chunk.Chunk<A>, p: Promise<never, boolean>) {
let bs = as
while (Chunk.size(bs) > 0) {
const head = Chunk.unsafeGet_(bs, 0)!
bs = Chunk.drop_(bs, 1)
if (Chunk.size(bs) === 0) {
this.putters.offer(Tp.tuple(head, p, true))
return
} else {
this.putters.offer(Tp.tuple(head, p, false))
}
}
}
unsafeOnQueueEmptySpace(
queue: MutableQueue<A>,
takers: MutableQueue<Promise<never, A>>
) {
let keepPolling = true
while (keepPolling && !queue.isFull) {
const putter = this.putters.poll(EmptyQueue)
if (putter !== EmptyQueue) {
const offered = queue.offer(putter.get(0))
if (offered && putter.get(2)) {
Q.unsafeCompletePromise(putter.get(1), true)
} else if (!offered) {
Q.unsafeOfferAll(
this.putters,
Chunk.prepend_(Q.unsafePollAll(this.putters), putter)
)
}
Q.unsafeCompleteTakers(this, queue, takers)
} else {
keepPolling = false
}
}
}
get shutdown(): UIO<void> {
return pipe(
Do.do,
Do.bind("fiberId", () => fiberId.fiberId),
Do.bind("putters", () => core.succeedWith(() => Q.unsafePollAll(this.putters))),
tap.tap((s) =>
forEachPar_(s.putters, ({ tuple: [_, p, lastItem] }) =>
lastItem ? promise.interruptAs(s.fiberId)(p) : core.unit
)
),
asUnit.asUnit
)
}
get surplusSize(): number {
return this.putters.size
}
}
/**
* Creates a bounded queue
*/
export function makeBoundedQueue<A>(
capacity: number,
__trace?: string
): UIO<Q.Queue<A>> {
return core.chain_(
core.succeedWith(() => new Bounded<A>(capacity)),
(x) => createQueue_(x, new BackPressureStrategy()),
__trace
)
}
/**
* Unsafely creates a queue
*/
export function unsafeCreateQueue<A>(
queue: MutableQueue<A>,
takers: MutableQueue<Promise<never, A>>,
shutdownHook: Promise<never, void>,
shutdownFlag: AtomicBoolean,
strategy: Q.Strategy<A>
): Q.Queue<A> {
return new UnsafeCreate(queue, takers, shutdownHook, shutdownFlag, strategy)
}
class UnsafeCreate<A> extends XQueueInternal<unknown, unknown, never, never, A, A> {
constructor(
readonly queue: MutableQueue<A>,
readonly takers: MutableQueue<Promise<never, A>>,
readonly shutdownHook: Promise<never, void>,
readonly shutdownFlag: AtomicBoolean,
readonly strategy: Q.Strategy<A>
) {
super()
}
awaitShutdown: UIO<void> = promise.await(this.shutdownHook)
capacity: number = this.queue.capacity
isShutdown: UIO<boolean> = core.succeedWith(() => this.shutdownFlag.get)
offer(a: A): Effect<unknown, never, boolean> {
return core.suspend(() => {
if (this.shutdownFlag.get) {
return interruption.interrupt
} else {
const noRemaining = (() => {
if (this.queue.isEmpty) {
const taker = this.takers.poll(EmptyQueue)
if (taker === EmptyQueue) {
return false
} else {
Q.unsafeCompletePromise(taker, a)
return true
}
} else {
return false
}
})()
if (noRemaining) {
return core.succeed(true)
}
const succeeded = this.queue.offer(a)
Q.unsafeCompleteTakers(this.strategy, this.queue, this.takers)
if (succeeded) {
return core.succeed(true)
} else {
return this.strategy.handleSurplus(
Chunk.single(a),
this.queue,
this.takers,
this.shutdownFlag
)
}
}
})
}
offerAll(as: Iterable<A>): Effect<unknown, never, boolean> {
const arr = Chunk.from(as)
return core.suspend(() => {
if (this.shutdownFlag.get) {
return interruption.interrupt
} else {
const pTakers = this.queue.isEmpty
? Q.unsafePollN(this.takers, Chunk.size(arr))
: Chunk.empty<Promise<never, A>>()
const {
tuple: [forTakers, remaining]
} = ChunkSplitAt.splitAt_(arr, Chunk.size(pTakers))
ChunkForEach.forEach_(
ChunkZip.zip_(pTakers, forTakers),
({ tuple: [taker, item] }) => {
Q.unsafeCompletePromise(taker, item)
}
)
if (Chunk.size(remaining) === 0) {
return core.succeed(true)
}
const surplus = Q.unsafeOfferAll(this.queue, remaining)
Q.unsafeCompleteTakers(this.strategy, this.queue, this.takers)
if (Chunk.size(surplus) === 0) {
return core.succeed(true)
} else {
return this.strategy.handleSurplus(
surplus,
this.queue,
this.takers,
this.shutdownFlag
)
}
}
})
}
shutdown: UIO<void> = interruption.uninterruptible(
core.suspend((_, fiberId) => {
this.shutdownFlag.set(true)
return whenM.whenM(promise.succeed<void>(undefined)(this.shutdownHook))(
core.chain_(
forEachPar_(Q.unsafePollAll(this.takers), promise.interruptAs(fiberId)),
() => this.strategy.shutdown
)
)
})
)
size: UIO<number> = core.suspend(() => {
if (this.shutdownFlag.get) {
return interruption.interrupt
} else {
return core.succeed(
this.queue.size - this.takers.size + this.strategy.surplusSize
)
}
})
take: Effect<unknown, never, A> = core.suspend((_, fiberId) => {
if (this.shutdownFlag.get) {
return interruption.interrupt
}
const item = this.queue.poll(EmptyQueue)
if (item !== EmptyQueue) {
this.strategy.unsafeOnQueueEmptySpace(this.queue, this.takers)
return core.succeed(item)
} else {
const p = promise.unsafeMake<never, A>(fiberId)
return interruption.onInterrupt_(
core.suspend(() => {
this.takers.offer(p)
Q.unsafeCompleteTakers(this.strategy, this.queue, this.takers)
if (this.shutdownFlag.get) {
return interruption.interrupt
} else {
return promise.await(p)
}
}),
() => core.succeedWith(() => Q.unsafeRemove(this.takers, p))
)
}
})
takeAll: Effect<unknown, never, Chunk.Chunk<A>> = core.suspend(() => {
if (this.shutdownFlag.get) {
return interruption.interrupt
} else {
return core.succeedWith(() => {
const as = Q.unsafePollAll(this.queue)
this.strategy.unsafeOnQueueEmptySpace(this.queue, this.takers)
return as
})
}
})
takeUpTo(n: number): Effect<unknown, never, Chunk.Chunk<A>> {
return core.suspend(() => {
if (this.shutdownFlag.get) {
return interruption.interrupt
} else {
return core.succeedWith(() => {
const as = Q.unsafePollN(this.queue, n)
this.strategy.unsafeOnQueueEmptySpace(this.queue, this.takers)
return as
})
}
})
}
}
/**
* Creates a queue
*/
export function createQueue_<A>(
queue: MutableQueue<A>,
strategy: Q.Strategy<A>,
__trace?: string
) {
return map.map_(
promise.make<never, void>(),
(p) =>
unsafeCreateQueue(queue, new Unbounded(), p, new AtomicBoolean(false), strategy),
__trace
)
}
/**
* Creates a queue
*
* @ets_data_first createQueue_
*/
export function createQueue<A>(strategy: Q.Strategy<A>, __trace?: string) {
return (queue: MutableQueue<A>) => createQueue_(queue, strategy, __trace)
}