veffect
Version:
powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha
1,528 lines (1,412 loc) • 122 kB
text/typescript
import * as Boolean from "../Boolean.js"
import type * as Cause from "../Cause.js"
import * as Chunk from "../Chunk.js"
import type * as Clock from "../Clock.js"
import type { ConfigProvider } from "../ConfigProvider.js"
import * as Context from "../Context.js"
import * as Deferred from "../Deferred.js"
import type * as Duration from "../Duration.js"
import type * as Effect from "../Effect.js"
import { EffectTypeId } from "../Effectable.js"
import type * as Either from "../Either.js"
import * as ExecutionStrategy from "../ExecutionStrategy.js"
import type * as Exit from "../Exit.js"
import type * as Fiber from "../Fiber.js"
import * as FiberId from "../FiberId.js"
import type * as FiberRef from "../FiberRef.js"
import * as FiberRefs from "../FiberRefs.js"
import * as FiberRefsPatch from "../FiberRefsPatch.js"
import * as FiberStatus from "../FiberStatus.js"
import type { LazyArg } from "../Function.js"
import { dual, identity, pipe } from "../Function.js"
import { globalValue } from "../GlobalValue.js"
import * as HashMap from "../HashMap.js"
import * as HashSet from "../HashSet.js"
import * as Inspectable from "../Inspectable.js"
import type { Logger } from "../Logger.js"
import * as LogLevel from "../LogLevel.js"
import type * as MetricLabel from "../MetricLabel.js"
import * as MRef from "../MutableRef.js"
import * as Option from "../Option.js"
import { pipeArguments } from "../Pipeable.js"
import * as Predicate from "../Predicate.js"
import type * as Random from "../Random.js"
import * as RA from "../ReadonlyArray.js"
import * as Ref from "../Ref.js"
import type { Entry, Request } from "../Request.js"
import type * as RequestBlock from "../RequestBlock.js"
import type * as RuntimeFlags from "../RuntimeFlags.js"
import * as RuntimeFlagsPatch from "../RuntimeFlagsPatch.js"
import { currentScheduler, type Scheduler } from "../Scheduler.js"
import type * as Scope from "../Scope.js"
import type * as Supervisor from "../Supervisor.js"
import type * as Tracer from "../Tracer.js"
import type { Concurrency, NoInfer } from "../Types.js"
import * as _RequestBlock from "./blockedRequests.js"
import * as internalCause from "./cause.js"
import * as clock from "./clock.js"
import { currentRequestMap } from "./completedRequestMap.js"
import * as concurrency from "./concurrency.js"
import { configProviderTag } from "./configProvider.js"
import * as internalEffect from "./core-effect.js"
import * as core from "./core.js"
import * as defaultServices from "./defaultServices.js"
import { consoleTag } from "./defaultServices/console.js"
import * as executionStrategy from "./executionStrategy.js"
import * as internalFiber from "./fiber.js"
import * as FiberMessage from "./fiberMessage.js"
import * as fiberRefs from "./fiberRefs.js"
import * as fiberScope from "./fiberScope.js"
import * as internalLogger from "./logger.js"
import * as metric from "./metric.js"
import * as metricBoundaries from "./metric/boundaries.js"
import * as metricLabel from "./metric/label.js"
import * as OpCodes from "./opCodes/effect.js"
import { randomTag } from "./random.js"
import { complete } from "./request.js"
import * as _runtimeFlags from "./runtimeFlags.js"
import { OpSupervision } from "./runtimeFlags.js"
import * as supervisor from "./supervisor.js"
import * as SupervisorPatch from "./supervisor/patch.js"
import * as tracer from "./tracer.js"
import * as version from "./version.js"
/** @internal */
export const fiberStarted = metric.counter("effect_fiber_started", { incremental: true })
/** @internal */
export const fiberActive = metric.counter("effect_fiber_active")
/** @internal */
export const fiberSuccesses = metric.counter("effect_fiber_successes", { incremental: true })
/** @internal */
export const fiberFailures = metric.counter("effect_fiber_failures", { incremental: true })
/** @internal */
export const fiberLifetimes = metric.tagged(
metric.histogram(
"effect_fiber_lifetimes",
metricBoundaries.exponential({
start: 0.5,
factor: 2,
count: 35
})
),
"time_unit",
"milliseconds"
)
/** @internal */
type EvaluationSignal =
| EvaluationSignalContinue
| EvaluationSignalDone
| EvaluationSignalYieldNow
/** @internal */
const EvaluationSignalContinue = "Continue" as const
/** @internal */
type EvaluationSignalContinue = typeof EvaluationSignalContinue
/** @internal */
const EvaluationSignalDone = "Done" as const
/** @internal */
type EvaluationSignalDone = typeof EvaluationSignalDone
/** @internal */
const EvaluationSignalYieldNow = "Yield" as const
/** @internal */
type EvaluationSignalYieldNow = typeof EvaluationSignalYieldNow
const runtimeFiberVariance = {
/* c8 ignore next */
_E: (_: never) => _,
/* c8 ignore next */
_A: (_: never) => _
}
const absurd = (_: never): never => {
throw new Error(
`BUG: FiberRuntime - ${
Inspectable.toStringUnknown(_)
} - please report an issue at https://github.com/Effect-TS/effect/issues`
)
}
const YieldedOp = Symbol.for("effect/internal/fiberRuntime/YieldedOp")
type YieldedOp = typeof YieldedOp
const yieldedOpChannel: {
currentOp: core.Primitive | null
} = globalValue("effect/internal/fiberRuntime/yieldedOpChannel", () => ({
currentOp: null
}))
const contOpSuccess = {
[OpCodes.OP_ON_SUCCESS]: (
_: FiberRuntime<any, any>,
cont: core.OnSuccess,
value: unknown
) => {
return cont.effect_instruction_i1(value)
},
["OnStep"]: (
_: FiberRuntime<any, any>,
_cont: core.OnStep,
value: unknown
) => {
return core.exitSucceed(core.exitSucceed(value))
},
[OpCodes.OP_ON_SUCCESS_AND_FAILURE]: (
_: FiberRuntime<any, any>,
cont: core.OnSuccessAndFailure,
value: unknown
) => {
return cont.effect_instruction_i2(value)
},
[OpCodes.OP_REVERT_FLAGS]: (
self: FiberRuntime<any, any>,
cont: core.RevertFlags,
value: unknown
) => {
self.patchRuntimeFlags(self._runtimeFlags, cont.patch)
if (_runtimeFlags.interruptible(self._runtimeFlags) && self.isInterrupted()) {
return core.exitFailCause(self.getInterruptedCause())
} else {
return core.exitSucceed(value)
}
},
[OpCodes.OP_WHILE]: (
self: FiberRuntime<any, any>,
cont: core.While,
value: unknown
) => {
cont.effect_instruction_i2(value)
if (cont.effect_instruction_i0()) {
self.pushStack(cont)
return cont.effect_instruction_i1()
} else {
return core.unit
}
}
}
const drainQueueWhileRunningTable = {
[FiberMessage.OP_INTERRUPT_SIGNAL]: (
self: FiberRuntime<any, any>,
runtimeFlags: RuntimeFlags.RuntimeFlags,
cur: Effect.Effect<any, any, any>,
message: FiberMessage.FiberMessage & { _tag: FiberMessage.OP_INTERRUPT_SIGNAL }
) => {
self.processNewInterruptSignal(message.cause)
return _runtimeFlags.interruptible(runtimeFlags) ? core.exitFailCause(message.cause) : cur
},
[FiberMessage.OP_RESUME]: (
_self: FiberRuntime<any, any>,
_runtimeFlags: RuntimeFlags.RuntimeFlags,
_cur: Effect.Effect<any, any, any>,
_message: FiberMessage.FiberMessage
) => {
throw new Error("It is illegal to have multiple concurrent run loops in a single fiber")
},
[FiberMessage.OP_STATEFUL]: (
self: FiberRuntime<any, any>,
runtimeFlags: RuntimeFlags.RuntimeFlags,
cur: Effect.Effect<any, any, any>,
message: FiberMessage.FiberMessage & { _tag: FiberMessage.OP_STATEFUL }
) => {
message.onFiber(self, FiberStatus.running(runtimeFlags))
return cur
},
[FiberMessage.OP_YIELD_NOW]: (
_self: FiberRuntime<any, any>,
_runtimeFlags: RuntimeFlags.RuntimeFlags,
cur: Effect.Effect<any, any, any>,
_message: FiberMessage.FiberMessage & { _tag: FiberMessage.OP_YIELD_NOW }
) => {
return core.flatMap(core.yieldNow(), () => cur)
}
}
/**
* Executes all requests, submitting requests to each data source in parallel.
*/
const runBlockedRequests = (self: RequestBlock.RequestBlock) =>
core.forEachSequentialDiscard(
_RequestBlock.flatten(self),
(requestsByRequestResolver) =>
forEachConcurrentDiscard(
_RequestBlock.sequentialCollectionToChunk(requestsByRequestResolver),
([dataSource, sequential]) => {
const map = new Map<Request<any, any>, Entry<any>>()
const arr: Array<Array<Entry<any>>> = []
for (const block of sequential) {
arr.push(Chunk.toReadonlyArray(block) as any)
for (const entry of block) {
map.set(entry.request as Request<any, any>, entry)
}
}
const flat = arr.flat()
return core.fiberRefLocally(
invokeWithInterrupt(dataSource.runAll(arr), flat, () =>
flat.forEach((entry) => {
entry.listeners.interrupted = true
})),
currentRequestMap,
map
)
},
false,
false
)
)
/** @internal */
export interface Snapshot {
refs: FiberRefs.FiberRefs
flags: RuntimeFlags.RuntimeFlags
}
/** @internal */
export class FiberRuntime<in out A, in out E = never> implements Fiber.RuntimeFiber<A, E> {
readonly [internalFiber.FiberTypeId] = internalFiber.fiberVariance
readonly [internalFiber.RuntimeFiberTypeId] = runtimeFiberVariance
pipe() {
return pipeArguments(this, arguments)
}
private _fiberRefs: FiberRefs.FiberRefs
private _fiberId: FiberId.Runtime
public _runtimeFlags: RuntimeFlags.RuntimeFlags
private _queue = new Array<FiberMessage.FiberMessage>()
private _children: Set<FiberRuntime<any, any>> | null = null
private _observers = new Array<(exit: Exit.Exit<A, E>) => void>()
private _running = false
private _stack: Array<core.Continuation> = []
private _asyncInterruptor: ((effect: Effect.Effect<any, any, any>) => any) | null = null
private _asyncBlockingOn: FiberId.FiberId | null = null
private _exitValue: Exit.Exit<A, E> | null = null
private _steps: Array<Snapshot> = []
public _supervisor: Supervisor.Supervisor<any>
public _scheduler: Scheduler
private _tracer: Tracer.Tracer
public currentOpCount: number = 0
private isYielding = false
constructor(
fiberId: FiberId.Runtime,
fiberRefs0: FiberRefs.FiberRefs,
runtimeFlags0: RuntimeFlags.RuntimeFlags
) {
this._runtimeFlags = runtimeFlags0
this._fiberId = fiberId
this._fiberRefs = fiberRefs0
this._supervisor = this.getFiberRef(currentSupervisor)
this._scheduler = this.getFiberRef(currentScheduler)
if (_runtimeFlags.runtimeMetrics(runtimeFlags0)) {
const tags = this.getFiberRef(core.currentMetricLabels)
fiberStarted.unsafeUpdate(1, tags)
fiberActive.unsafeUpdate(1, tags)
}
this._tracer = Context.get(this.getFiberRef(defaultServices.currentServices), tracer.tracerTag)
}
/**
* The identity of the fiber.
*/
id(): FiberId.Runtime {
return this._fiberId
}
/**
* Begins execution of the effect associated with this fiber on in the
* background. This can be called to "kick off" execution of a fiber after
* it has been created.
*/
resume<A, E>(effect: Effect.Effect<A, E, any>): void {
this.tell(FiberMessage.resume(effect))
}
/**
* The status of the fiber.
*/
get status(): Effect.Effect<FiberStatus.FiberStatus> {
return this.ask((_, status) => status)
}
/**
* Gets the fiber runtime flags.
*/
get runtimeFlags(): Effect.Effect<RuntimeFlags.RuntimeFlags> {
return this.ask((state, status) => {
if (FiberStatus.isDone(status)) {
return state._runtimeFlags
}
return status.runtimeFlags
})
}
/**
* Returns the current `FiberScope` for the fiber.
*/
scope(): fiberScope.FiberScope {
return fiberScope.unsafeMake(this)
}
/**
* Retrieves the immediate children of the fiber.
*/
get children(): Effect.Effect<Array<Fiber.RuntimeFiber<any, any>>> {
return this.ask((fiber) => Array.from(fiber.getChildren()))
}
/**
* Gets the fiber's set of children.
*/
getChildren(): Set<FiberRuntime<any, any>> {
if (this._children === null) {
this._children = new Set()
}
return this._children
}
/**
* Retrieves the interrupted cause of the fiber, which will be `Cause.empty`
* if the fiber has not been interrupted.
*
* **NOTE**: This method is safe to invoke on any fiber, but if not invoked
* on this fiber, then values derived from the fiber's state (including the
* log annotations and log level) may not be up-to-date.
*/
getInterruptedCause() {
return this.getFiberRef(core.currentInterruptedCause)
}
/**
* Retrieves the whole set of fiber refs.
*/
fiberRefs(): Effect.Effect<FiberRefs.FiberRefs> {
return this.ask((fiber) => fiber.getFiberRefs())
}
/**
* Returns an effect that will contain information computed from the fiber
* state and status while running on the fiber.
*
* This allows the outside world to interact safely with mutable fiber state
* without locks or immutable data.
*/
ask<Z>(
f: (runtime: FiberRuntime<any, any>, status: FiberStatus.FiberStatus) => Z
): Effect.Effect<Z> {
return core.suspend(() => {
const deferred = core.deferredUnsafeMake<Z>(this._fiberId)
this.tell(
FiberMessage.stateful((fiber, status) => {
core.deferredUnsafeDone(deferred, core.sync(() => f(fiber, status)))
})
)
return core.deferredAwait(deferred)
})
}
/**
* Adds a message to be processed by the fiber on the fiber.
*/
tell(message: FiberMessage.FiberMessage): void {
this._queue.push(message)
if (!this._running) {
this._running = true
this.drainQueueLaterOnExecutor()
}
}
get await(): Effect.Effect<Exit.Exit<A, E>> {
return core.async((resume) => {
const cb = (exit: Exit.Exit<A, E>) => resume(core.succeed(exit))
this.tell(
FiberMessage.stateful((fiber, _) => {
if (fiber._exitValue !== null) {
cb(this._exitValue!)
} else {
fiber.addObserver(cb)
}
})
)
return core.sync(() =>
this.tell(
FiberMessage.stateful((fiber, _) => {
fiber.removeObserver(cb)
})
)
)
}, this.id())
}
get inheritAll(): Effect.Effect<void> {
return core.withFiberRuntime((parentFiber, parentStatus) => {
const parentFiberId = parentFiber.id()
const parentFiberRefs = parentFiber.getFiberRefs()
const parentRuntimeFlags = parentStatus.runtimeFlags
const childFiberRefs = this.getFiberRefs()
const updatedFiberRefs = fiberRefs.joinAs(parentFiberRefs, parentFiberId, childFiberRefs)
parentFiber.setFiberRefs(updatedFiberRefs)
const updatedRuntimeFlags = parentFiber.getFiberRef(currentRuntimeFlags)
const patch = pipe(
_runtimeFlags.diff(parentRuntimeFlags, updatedRuntimeFlags),
// Do not inherit WindDown or Interruption!
RuntimeFlagsPatch.exclude(_runtimeFlags.Interruption),
RuntimeFlagsPatch.exclude(_runtimeFlags.WindDown)
)
return core.updateRuntimeFlags(patch)
})
}
/**
* Tentatively observes the fiber, but returns immediately if it is not
* already done.
*/
get poll(): Effect.Effect<Option.Option<Exit.Exit<A, E>>> {
return core.sync(() => Option.fromNullable(this._exitValue))
}
/**
* Unsafely observes the fiber, but returns immediately if it is not
* already done.
*/
unsafePoll(): Exit.Exit<A, E> | null {
return this._exitValue
}
/**
* In the background, interrupts the fiber as if interrupted from the specified fiber.
*/
interruptAsFork(fiberId: FiberId.FiberId): Effect.Effect<void> {
return core.sync(() => this.tell(FiberMessage.interruptSignal(internalCause.interrupt(fiberId))))
}
/**
* In the background, interrupts the fiber as if interrupted from the specified fiber.
*/
unsafeInterruptAsFork(fiberId: FiberId.FiberId) {
this.tell(FiberMessage.interruptSignal(internalCause.interrupt(fiberId)))
}
/**
* Adds an observer to the list of observers.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
addObserver(observer: (exit: Exit.Exit<A, E>) => void): void {
if (this._exitValue !== null) {
observer(this._exitValue!)
} else {
this._observers.push(observer)
}
}
/**
* Removes the specified observer from the list of observers that will be
* notified when the fiber exits.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
removeObserver(observer: (exit: Exit.Exit<A, E>) => void): void {
this._observers = this._observers.filter((o) => o !== observer)
}
/**
* Retrieves all fiber refs of the fiber.
*
* **NOTE**: This method is safe to invoke on any fiber, but if not invoked
* on this fiber, then values derived from the fiber's state (including the
* log annotations and log level) may not be up-to-date.
*/
getFiberRefs(): FiberRefs.FiberRefs {
this.setFiberRef(currentRuntimeFlags, this._runtimeFlags)
return this._fiberRefs
}
/**
* Deletes the specified fiber ref.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
unsafeDeleteFiberRef<X>(fiberRef: FiberRef.FiberRef<X>): void {
this._fiberRefs = fiberRefs.delete_(this._fiberRefs, fiberRef)
}
/**
* Retrieves the state of the fiber ref, or else its initial value.
*
* **NOTE**: This method is safe to invoke on any fiber, but if not invoked
* on this fiber, then values derived from the fiber's state (including the
* log annotations and log level) may not be up-to-date.
*/
getFiberRef<X>(fiberRef: FiberRef.FiberRef<X>): X {
if (this._fiberRefs.locals.has(fiberRef)) {
return this._fiberRefs.locals.get(fiberRef)![0][1] as X
}
return fiberRef.initial
}
/**
* Sets the fiber ref to the specified value.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
setFiberRef<X>(fiberRef: FiberRef.FiberRef<X>, value: X): void {
this._fiberRefs = fiberRefs.updateAs(this._fiberRefs, {
fiberId: this._fiberId,
fiberRef,
value
})
this.refreshRefCache()
}
refreshRefCache() {
this._tracer = Context.get(this.getFiberRef(defaultServices.currentServices), tracer.tracerTag)
this._supervisor = this.getFiberRef(currentSupervisor)
this._scheduler = this.getFiberRef(currentScheduler)
}
/**
* Wholesale replaces all fiber refs of this fiber.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
setFiberRefs(fiberRefs: FiberRefs.FiberRefs): void {
this._fiberRefs = fiberRefs
this.refreshRefCache()
}
/**
* Adds a reference to the specified fiber inside the children set.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
addChild(child: FiberRuntime<any, any>) {
this.getChildren().add(child)
}
/**
* Removes a reference to the specified fiber inside the children set.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
removeChild(child: FiberRuntime<any, any>) {
this.getChildren().delete(child)
}
/**
* On the current thread, executes all messages in the fiber's inbox. This
* method may return before all work is done, in the event the fiber executes
* an asynchronous operation.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
drainQueueOnCurrentThread() {
let recurse = true
while (recurse) {
let evaluationSignal: EvaluationSignal = EvaluationSignalContinue
const prev = (globalThis as any)[internalFiber.currentFiberURI]
;(globalThis as any)[internalFiber.currentFiberURI] = this
try {
while (evaluationSignal === EvaluationSignalContinue) {
evaluationSignal = this._queue.length === 0 ?
EvaluationSignalDone :
this.evaluateMessageWhileSuspended(this._queue.splice(0, 1)[0]!)
}
} finally {
this._running = false
;(globalThis as any)[internalFiber.currentFiberURI] = prev
}
// Maybe someone added something to the queue between us checking, and us
// giving up the drain. If so, we need to restart the draining, but only
// if we beat everyone else to the restart:
if (this._queue.length > 0 && !this._running) {
this._running = true
if (evaluationSignal === EvaluationSignalYieldNow) {
this.drainQueueLaterOnExecutor()
recurse = false
} else {
recurse = true
}
} else {
recurse = false
}
}
}
/**
* Schedules the execution of all messages in the fiber's inbox.
*
* This method will return immediately after the scheduling
* operation is completed, but potentially before such messages have been
* executed.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
drainQueueLaterOnExecutor() {
this._scheduler.scheduleTask(
this.run,
this.getFiberRef(core.currentSchedulingPriority)
)
}
/**
* Drains the fiber's message queue while the fiber is actively running,
* returning the next effect to execute, which may be the input effect if no
* additional effect needs to be executed.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
drainQueueWhileRunning(
runtimeFlags: RuntimeFlags.RuntimeFlags,
cur0: Effect.Effect<any, any, any>
) {
let cur = cur0
while (this._queue.length > 0) {
const message = this._queue.splice(0, 1)[0]
// @ts-expect-error
cur = drainQueueWhileRunningTable[message._tag](this, runtimeFlags, cur, message)
}
return cur
}
/**
* Determines if the fiber is interrupted.
*
* **NOTE**: This method is safe to invoke on any fiber, but if not invoked
* on this fiber, then values derived from the fiber's state (including the
* log annotations and log level) may not be up-to-date.
*/
isInterrupted(): boolean {
return !internalCause.isEmpty(this.getFiberRef(core.currentInterruptedCause))
}
/**
* Adds an interruptor to the set of interruptors that are interrupting this
* fiber.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
addInterruptedCause(cause: Cause.Cause<never>) {
const oldSC = this.getFiberRef(core.currentInterruptedCause)
this.setFiberRef(core.currentInterruptedCause, internalCause.sequential(oldSC, cause))
}
/**
* Processes a new incoming interrupt signal.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
processNewInterruptSignal(cause: Cause.Cause<never>): void {
this.addInterruptedCause(cause)
this.sendInterruptSignalToAllChildren()
}
/**
* Interrupts all children of the current fiber, returning an effect that will
* await the exit of the children. This method will return null if the fiber
* has no children.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
sendInterruptSignalToAllChildren(): boolean {
if (this._children === null || this._children.size === 0) {
return false
}
let told = false
for (const child of this._children) {
child.tell(FiberMessage.interruptSignal(internalCause.interrupt(this.id())))
told = true
}
return told
}
/**
* Interrupts all children of the current fiber, returning an effect that will
* await the exit of the children. This method will return null if the fiber
* has no children.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
interruptAllChildren() {
if (this.sendInterruptSignalToAllChildren()) {
const it = this._children!.values()
this._children = null
let isDone = false
const body = () => {
const next = it.next()
if (!next.done) {
return core.asUnit(next.value.await)
} else {
return core.sync(() => {
isDone = true
})
}
}
return core.whileLoop({
while: () => !isDone,
body,
step: () => {
//
}
})
}
return null
}
reportExitValue(exit: Exit.Exit<A, E>) {
if (_runtimeFlags.runtimeMetrics(this._runtimeFlags)) {
const tags = this.getFiberRef(core.currentMetricLabels)
const startTimeMillis = this.id().startTimeMillis
const endTimeMillis = Date.now()
fiberLifetimes.unsafeUpdate(endTimeMillis - startTimeMillis, tags)
fiberActive.unsafeUpdate(-1, tags)
switch (exit._tag) {
case OpCodes.OP_SUCCESS: {
fiberSuccesses.unsafeUpdate(1, tags)
break
}
case OpCodes.OP_FAILURE: {
fiberFailures.unsafeUpdate(1, tags)
break
}
}
}
if (exit._tag === "Failure") {
const level = this.getFiberRef(core.currentUnhandledErrorLogLevel)
if (!internalCause.isInterruptedOnly(exit.cause) && level._tag === "Some") {
this.log("Fiber terminated with an unhandled error", exit.cause, level)
}
}
}
setExitValue(exit: Exit.Exit<A, E>) {
this._exitValue = exit
this.reportExitValue(exit)
for (let i = this._observers.length - 1; i >= 0; i--) {
this._observers[i](exit)
}
}
getLoggers() {
return this.getFiberRef(currentLoggers)
}
log(
message: unknown,
cause: Cause.Cause<any>,
overrideLogLevel: Option.Option<LogLevel.LogLevel>
): void {
const logLevel = Option.isSome(overrideLogLevel) ?
overrideLogLevel.value :
this.getFiberRef(core.currentLogLevel)
const minimumLogLevel = this.getFiberRef(currentMinimumLogLevel)
if (LogLevel.greaterThan(minimumLogLevel, logLevel)) {
return
}
const spans = this.getFiberRef(core.currentLogSpan)
const annotations = this.getFiberRef(core.currentLogAnnotations)
const loggers = this.getLoggers()
const contextMap = this.getFiberRefs()
if (HashSet.size(loggers) > 0) {
const clockService = Context.get(this.getFiberRef(defaultServices.currentServices), clock.clockTag)
const date = new Date(clockService.unsafeCurrentTimeMillis())
for (const logger of loggers) {
logger.log({
fiberId: this.id(),
logLevel,
message,
cause,
context: contextMap,
spans,
annotations,
date
})
}
}
}
/**
* Evaluates a single message on the current thread, while the fiber is
* suspended. This method should only be called while evaluation of the
* fiber's effect is suspended due to an asynchronous operation.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
evaluateMessageWhileSuspended(message: FiberMessage.FiberMessage): EvaluationSignal {
switch (message._tag) {
case FiberMessage.OP_YIELD_NOW: {
return EvaluationSignalYieldNow
}
case FiberMessage.OP_INTERRUPT_SIGNAL: {
this.processNewInterruptSignal(message.cause)
if (this._asyncInterruptor !== null) {
this._asyncInterruptor(core.exitFailCause(message.cause))
this._asyncInterruptor = null
}
return EvaluationSignalContinue
}
case FiberMessage.OP_RESUME: {
this._asyncInterruptor = null
this._asyncBlockingOn = null
this.evaluateEffect(message.effect)
return EvaluationSignalContinue
}
case FiberMessage.OP_STATEFUL: {
message.onFiber(
this,
this._exitValue !== null ?
FiberStatus.done :
FiberStatus.suspended(this._runtimeFlags, this._asyncBlockingOn!)
)
return EvaluationSignalContinue
}
default: {
return absurd(message)
}
}
}
/**
* Evaluates an effect until completion, potentially asynchronously.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
evaluateEffect(effect0: Effect.Effect<any, any, any>) {
this._supervisor.onResume(this)
try {
let effect: Effect.Effect<any, any, any> | null =
_runtimeFlags.interruptible(this._runtimeFlags) && this.isInterrupted() ?
core.exitFailCause(this.getInterruptedCause()) :
effect0
while (effect !== null) {
const eff: Effect.Effect<any, any, any> = effect
const exit = this.runLoop(eff)
if (exit === YieldedOp) {
const op = yieldedOpChannel.currentOp!
yieldedOpChannel.currentOp = null
if (op._op === OpCodes.OP_YIELD) {
if (_runtimeFlags.cooperativeYielding(this._runtimeFlags)) {
this.tell(FiberMessage.yieldNow())
this.tell(FiberMessage.resume(core.exitUnit))
effect = null
} else {
effect = core.exitUnit
}
} else if (op._op === OpCodes.OP_ASYNC) {
// Terminate this evaluation, async resumption will continue evaluation:
effect = null
}
} else {
this._runtimeFlags = pipe(this._runtimeFlags, _runtimeFlags.enable(_runtimeFlags.WindDown))
const interruption = this.interruptAllChildren()
if (interruption !== null) {
effect = core.flatMap(interruption, () => exit)
} else {
if (this._queue.length === 0) {
// No more messages to process, so we will allow the fiber to end life:
this.setExitValue(exit)
} else {
// There are messages, possibly added by the final op executed by
// the fiber. To be safe, we should execute those now before we
// allow the fiber to end life:
this.tell(FiberMessage.resume(exit))
}
effect = null
}
}
}
} finally {
this._supervisor.onSuspend(this)
}
}
/**
* Begins execution of the effect associated with this fiber on the current
* thread. This can be called to "kick off" execution of a fiber after it has
* been created, in hopes that the effect can be executed synchronously.
*
* This is not the normal way of starting a fiber, but it is useful when the
* express goal of executing the fiber is to synchronously produce its exit.
*/
start<R>(effect: Effect.Effect<A, E, R>): void {
if (!this._running) {
this._running = true
const prev = (globalThis as any)[internalFiber.currentFiberURI]
;(globalThis as any)[internalFiber.currentFiberURI] = this
try {
this.evaluateEffect(effect)
} finally {
this._running = false
;(globalThis as any)[internalFiber.currentFiberURI] = prev
// Because we're special casing `start`, we have to be responsible
// for spinning up the fiber if there were new messages added to
// the queue between the completion of the effect and the transition
// to the not running state.
if (this._queue.length > 0) {
this.drainQueueLaterOnExecutor()
}
}
} else {
this.tell(FiberMessage.resume(effect))
}
}
/**
* Begins execution of the effect associated with this fiber on in the
* background, and on the correct thread pool. This can be called to "kick
* off" execution of a fiber after it has been created, in hopes that the
* effect can be executed synchronously.
*/
startFork<R>(effect: Effect.Effect<A, E, R>): void {
this.tell(FiberMessage.resume(effect))
}
/**
* Takes the current runtime flags, patches them to return the new runtime
* flags, and then makes any changes necessary to fiber state based on the
* specified patch.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
patchRuntimeFlags(oldRuntimeFlags: RuntimeFlags.RuntimeFlags, patch: RuntimeFlagsPatch.RuntimeFlagsPatch) {
const newRuntimeFlags = _runtimeFlags.patch(oldRuntimeFlags, patch)
;(globalThis as any)[internalFiber.currentFiberURI] = this
this._runtimeFlags = newRuntimeFlags
return newRuntimeFlags
}
/**
* Initiates an asynchronous operation, by building a callback that will
* resume execution, and then feeding that callback to the registration
* function, handling error cases and repeated resumptions appropriately.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
initiateAsync(
runtimeFlags: RuntimeFlags.RuntimeFlags,
asyncRegister: (resume: (effect: Effect.Effect<any, any, any>) => void) => void
) {
let alreadyCalled = false
const callback = (effect: Effect.Effect<any, any, any>) => {
if (!alreadyCalled) {
alreadyCalled = true
this.tell(FiberMessage.resume(effect))
}
}
if (_runtimeFlags.interruptible(runtimeFlags)) {
this._asyncInterruptor = callback
}
try {
asyncRegister(callback)
} catch (e) {
callback(core.failCause(internalCause.die(e)))
}
}
pushStack(cont: core.Continuation) {
this._stack.push(cont)
if (cont._op === "OnStep") {
this._steps.push({ refs: this.getFiberRefs(), flags: this._runtimeFlags })
}
}
popStack() {
const item = this._stack.pop()
if (item) {
if (item._op === "OnStep") {
this._steps.pop()
}
return item
}
return
}
getNextSuccessCont() {
let frame = this.popStack()
while (frame) {
if (frame._op !== OpCodes.OP_ON_FAILURE) {
return frame
}
frame = this.popStack()
}
}
getNextFailCont() {
let frame = this.popStack()
while (frame) {
if (frame._op !== OpCodes.OP_ON_SUCCESS && frame._op !== OpCodes.OP_WHILE) {
return frame
}
frame = this.popStack()
}
}
[OpCodes.OP_TAG](op: core.Primitive & { _op: OpCodes.OP_SYNC }) {
return core.map(
core.fiberRefGet(core.currentContext),
(context) => Context.unsafeGet(context, op as unknown as Context.Tag<any, any>)
)
}
["Left"](op: core.Primitive & { _op: "Left" }) {
return core.fail(op.left)
}
["None"](_: core.Primitive & { _op: "None" }) {
return core.fail(new core.NoSuchElementException())
}
["Right"](op: core.Primitive & { _op: "Right" }) {
return core.exitSucceed(op.right)
}
["Some"](op: core.Primitive & { _op: "Some" }) {
return core.exitSucceed(op.value)
}
[OpCodes.OP_SYNC](op: core.Primitive & { _op: OpCodes.OP_SYNC }) {
const value = op.effect_instruction_i0()
const cont = this.getNextSuccessCont()
if (cont !== undefined) {
if (!(cont._op in contOpSuccess)) {
// @ts-expect-error
absurd(cont)
}
// @ts-expect-error
return contOpSuccess[cont._op](this, cont, value)
} else {
yieldedOpChannel.currentOp = core.exitSucceed(value) as any
return YieldedOp
}
}
[OpCodes.OP_SUCCESS](op: core.Primitive & { _op: OpCodes.OP_SUCCESS }) {
const oldCur = op
const cont = this.getNextSuccessCont()
if (cont !== undefined) {
if (!(cont._op in contOpSuccess)) {
// @ts-expect-error
absurd(cont)
}
// @ts-expect-error
return contOpSuccess[cont._op](this, cont, oldCur.effect_instruction_i0)
} else {
yieldedOpChannel.currentOp = oldCur
return YieldedOp
}
}
[OpCodes.OP_FAILURE](op: core.Primitive & { _op: OpCodes.OP_FAILURE }) {
const cause = op.effect_instruction_i0
const cont = this.getNextFailCont()
if (cont !== undefined) {
switch (cont._op) {
case OpCodes.OP_ON_FAILURE:
case OpCodes.OP_ON_SUCCESS_AND_FAILURE: {
if (!(_runtimeFlags.interruptible(this._runtimeFlags) && this.isInterrupted())) {
return cont.effect_instruction_i1(cause)
} else {
return core.exitFailCause(internalCause.stripFailures(cause))
}
}
case "OnStep": {
if (!(_runtimeFlags.interruptible(this._runtimeFlags) && this.isInterrupted())) {
return core.exitSucceed(core.exitFailCause(cause))
} else {
return core.exitFailCause(internalCause.stripFailures(cause))
}
}
case OpCodes.OP_REVERT_FLAGS: {
this.patchRuntimeFlags(this._runtimeFlags, cont.patch)
if (_runtimeFlags.interruptible(this._runtimeFlags) && this.isInterrupted()) {
return core.exitFailCause(internalCause.sequential(cause, this.getInterruptedCause()))
} else {
return core.exitFailCause(cause)
}
}
default: {
absurd(cont)
}
}
} else {
yieldedOpChannel.currentOp = core.exitFailCause(cause) as any
return YieldedOp
}
}
[OpCodes.OP_WITH_RUNTIME](op: core.Primitive & { _op: OpCodes.OP_WITH_RUNTIME }) {
return op.effect_instruction_i0(
this as FiberRuntime<unknown, unknown>,
FiberStatus.running(this._runtimeFlags) as FiberStatus.Running
)
}
["Blocked"](op: core.Primitive & { _op: "Blocked" }) {
const refs = this.getFiberRefs()
const flags = this._runtimeFlags
if (this._steps.length > 0) {
const frames: Array<core.Continuation> = []
const snap = this._steps[this._steps.length - 1]
let frame = this.popStack()
while (frame && frame._op !== "OnStep") {
frames.push(frame)
frame = this.popStack()
}
this.setFiberRefs(snap.refs)
this._runtimeFlags = snap.flags
const patchRefs = FiberRefsPatch.diff(snap.refs, refs)
const patchFlags = _runtimeFlags.diff(snap.flags, flags)
return core.exitSucceed(core.blocked(
op.effect_instruction_i0,
core.withFiberRuntime<unknown, unknown>((newFiber) => {
while (frames.length > 0) {
newFiber.pushStack(frames.pop()!)
}
newFiber.setFiberRefs(
FiberRefsPatch.patch(newFiber.id(), newFiber.getFiberRefs())(patchRefs)
)
newFiber._runtimeFlags = _runtimeFlags.patch(patchFlags)(newFiber._runtimeFlags)
return op.effect_instruction_i1
})
))
}
return core.uninterruptibleMask((restore) =>
core.flatMap(
forkDaemon(core.runRequestBlock(op.effect_instruction_i0)),
() => restore(op.effect_instruction_i1)
)
)
}
["RunBlocked"](op: core.Primitive & { _op: "RunBlocked" }) {
return runBlockedRequests(op.effect_instruction_i0)
}
[OpCodes.OP_UPDATE_RUNTIME_FLAGS](op: core.Primitive & { _op: OpCodes.OP_UPDATE_RUNTIME_FLAGS }) {
const updateFlags = op.effect_instruction_i0
const oldRuntimeFlags = this._runtimeFlags
const newRuntimeFlags = _runtimeFlags.patch(oldRuntimeFlags, updateFlags)
// One more chance to short circuit: if we're immediately going
// to interrupt. Interruption will cause immediate reversion of
// the flag, so as long as we "peek ahead", there's no need to
// set them to begin with.
if (_runtimeFlags.interruptible(newRuntimeFlags) && this.isInterrupted()) {
return core.exitFailCause(this.getInterruptedCause())
} else {
// Impossible to short circuit, so record the changes
this.patchRuntimeFlags(this._runtimeFlags, updateFlags)
if (op.effect_instruction_i1) {
// Since we updated the flags, we need to revert them
const revertFlags = _runtimeFlags.diff(newRuntimeFlags, oldRuntimeFlags)
this.pushStack(new core.RevertFlags(revertFlags, op))
return op.effect_instruction_i1(oldRuntimeFlags)
} else {
return core.exitUnit
}
}
}
[OpCodes.OP_ON_SUCCESS](op: core.Primitive & { _op: OpCodes.OP_ON_SUCCESS }) {
this.pushStack(op)
return op.effect_instruction_i0
}
["OnStep"](op: core.Primitive & { _op: "OnStep" }) {
this.pushStack(op)
return op.effect_instruction_i0
}
[OpCodes.OP_ON_FAILURE](op: core.Primitive & { _op: OpCodes.OP_ON_FAILURE }) {
this.pushStack(op)
return op.effect_instruction_i0
}
[OpCodes.OP_ON_SUCCESS_AND_FAILURE](op: core.Primitive & { _op: OpCodes.OP_ON_SUCCESS_AND_FAILURE }) {
this.pushStack(op)
return op.effect_instruction_i0
}
[OpCodes.OP_ASYNC](op: core.Primitive & { _op: OpCodes.OP_ASYNC }) {
this._asyncBlockingOn = op.effect_instruction_i1
this.initiateAsync(this._runtimeFlags, op.effect_instruction_i0)
yieldedOpChannel.currentOp = op
return YieldedOp
}
[OpCodes.OP_YIELD](op: core.Primitive & { op: OpCodes.OP_YIELD }) {
this.isYielding = false
yieldedOpChannel.currentOp = op
return YieldedOp
}
[OpCodes.OP_WHILE](op: core.Primitive & { _op: OpCodes.OP_WHILE }) {
const check = op.effect_instruction_i0
const body = op.effect_instruction_i1
if (check()) {
this.pushStack(op)
return body()
} else {
return core.exitUnit
}
}
[OpCodes.OP_COMMIT](op: core.Primitive & { _op: OpCodes.OP_COMMIT }) {
return op.commit()
}
/**
* The main run-loop for evaluating effects.
*
* **NOTE**: This method must be invoked by the fiber itself.
*/
runLoop(effect0: Effect.Effect<any, any, any>): Exit.Exit<any, any> | YieldedOp {
let cur: Effect.Effect<any, any, any> | YieldedOp = effect0
this.currentOpCount = 0
// eslint-disable-next-line no-constant-condition
while (true) {
if ((this._runtimeFlags & OpSupervision) !== 0) {
this._supervisor.onEffect(this, cur)
}
if (this._queue.length > 0) {
cur = this.drainQueueWhileRunning(this._runtimeFlags, cur)
}
if (!this.isYielding) {
this.currentOpCount += 1
const shouldYield = this._scheduler.shouldYield(this)
if (shouldYield !== false) {
this.isYielding = true
this.currentOpCount = 0
const oldCur = cur
cur = core.flatMap(core.yieldNow({ priority: shouldYield }), () => oldCur)
}
}
try {
if (!("_op" in cur) || !((cur as core.Primitive)._op in this)) {
// @ts-expect-error
absurd(cur)
}
// @ts-expect-error
cur = this._tracer.context(
() => {
if (version.getCurrentVersion() !== (cur as core.Primitive)[EffectTypeId]._V) {
return core.dieMessage(
`Cannot execute an Effect versioned ${
(cur as core.Primitive)[EffectTypeId]._V
} with a Runtime of version ${version.getCurrentVersion()}`
)
}
// @ts-expect-error
return this[(cur as core.Primitive)._op](cur as core.Primitive)
},
this
)
if (cur === YieldedOp) {
const op = yieldedOpChannel.currentOp!
if (
op._op === OpCodes.OP_YIELD ||
op._op === OpCodes.OP_ASYNC
) {
return YieldedOp
}
yieldedOpChannel.currentOp = null
return (
op._op === OpCodes.OP_SUCCESS ||
op._op === OpCodes.OP_FAILURE
) ?
op as unknown as Exit.Exit<A, E> :
core.exitFailCause(internalCause.die(op))
}
} catch (e) {
if (core.isEffectError(e)) {
cur = core.exitFailCause(e.cause)
} else if (core.isInterruptedException(e)) {
cur = core.exitFailCause(
internalCause.sequential(internalCause.die(e), internalCause.interrupt(FiberId.none))
)
} else {
cur = core.exitFailCause(internalCause.die(e))
}
}
}
}
run = () => {
this.drainQueueOnCurrentThread()
}
}
// circular with Logger
/** @internal */
export const currentMinimumLogLevel: FiberRef.FiberRef<LogLevel.LogLevel> = globalValue(
"effect/FiberRef/currentMinimumLogLevel",
() => core.fiberRefUnsafeMake<LogLevel.LogLevel>(LogLevel.fromLiteral("Info"))
)
/** @internal */
export const loggerWithConsoleLog = <M, O>(self: Logger<M, O>): Logger<M, void> =>
internalLogger.makeLogger((opts) => {
const services = FiberRefs.getOrDefault(opts.context, defaultServices.currentServices)
Context.get(services, consoleTag).unsafe.log(self.log(opts))
})
/** @internal */
export const loggerWithConsoleError = <M, O>(self: Logger<M, O>): Logger<M, void> =>
internalLogger.makeLogger((opts) => {
const services = FiberRefs.getOrDefault(opts.context, defaultServices.currentServices)
Context.get(services, consoleTag).unsafe.error(self.log(opts))
})
/** @internal */
export const defaultLogger: Logger<unknown, void> = globalValue(
Symbol.for("effect/Logger/defaultLogger"),
() => loggerWithConsoleLog(internalLogger.stringLogger)
)
/** @internal */
export const jsonLogger: Logger<unknown, void> = globalValue(
Symbol.for("effect/Logger/jsonLogger"),
() => loggerWithConsoleLog(internalLogger.jsonLogger)
)
/** @internal */
export const logFmtLogger: Logger<unknown, void> = globalValue(
Symbol.for("effect/Logger/logFmtLogger"),
() => loggerWithConsoleLog(internalLogger.logfmtLogger)
)
/** @internal */
export const structuredLogger: Logger<unknown, void> = globalValue(
Symbol.for("effect/Logger/structuredLogger"),
() => loggerWithConsoleLog(internalLogger.structuredLogger)
)
/** @internal */
export const tracerLogger = globalValue(
Symbol.for("effect/Logger/tracerLogger"),
() =>
internalLogger.makeLogger<unknown, void>(({
annotations,
cause,
context,
fiberId,
logLevel,
message
}) => {
const span = Option.flatMap(fiberRefs.get(context, core.currentContext), Context.getOption(tracer.spanTag))
const clockService = Option.map(
fiberRefs.get(context, defaultServices.currentServices),
(_) => Context.get(_, clock.clockTag)
)
if (span._tag === "None" || span.value._tag === "ExternalSpan" || clockService._tag === "None") {
return
}
const attributes = Object.fromEntries(HashMap.map(annotations, Inspectable.toStringUnknown))
attributes["effect.fiberId"] = FiberId.threadName(fiberId)
attributes["effect.logLevel"] = logLevel.label
if (cause !== null && cause._tag !== "Empty") {
attributes["effect.cause"] = internalCause.pretty(cause)
}
span.value.event(
String(message),
clockService.value.unsafeCurrentTimeNanos(),
attributes
)
})
)
/** @internal */
export const loggerWithSpanAnnotations = <Message, Output>(self: Logger<Message, Output>): Logger<Message, Output> =>
internalLogger.mapInputOptions(self, (options: Logger.Options<Message>) => {
const span = Option.flatMap(fiberRefs.get(options.context, core.currentContext), Context.getOption(tracer.spanTag))
if (span._tag === "None") {
return options
}
return {
...options,
annotations: pipe(
options.annotations,
HashMap.set("effect.traceId", span.value.traceId as unknown),
HashMap.set("effect.spanId", span.value.spanId as unknown),
span.value._tag === "Span" ? HashMap.set("effect.spanName", span.value.name as unknown) : identity
)
}
})
/** @internal */
export const currentLoggers: FiberRef.FiberRef<
HashSet.HashSet<Logger<unknown, any>>
> = globalValue(
Symbol.for("effect/FiberRef/currentLoggers"),
() => core.fiberRefUnsafeMakeHashSet(HashSet.make(defaultLogger, tracerLogger))
)
/** @internal */
export const batchedLogger = dual<
<Output, R>(
window: Duration.DurationInput,
f: (messages: Array<NoInfer<Output>>) => Effect.Effect<void, never, R>
) => <Message>(
self: Logger<Message, Output>
) => Effect.Effect<Logger<Message, void>, never, Scope.Scope | R>,
<Message, Output, R>(
self: Logger<Message, Output>,
window: Duration.DurationInput,
f: (messages: Array<NoInfer<Output>>) => Effect.Effect<void, never, R>
) => Effect.Effect<Logger<Message, void>, never, Scope.Scope | R>
>(3, <Message, Output, R>(
self: Logger<Message, Output>,
window: Duration.DurationInput,
f: (messages: Array<NoInfer<Output>>) => Effect.Effect<void, never, R>
): Effect.Effect<Logger<Message, void>, never, Scope.Scope | R> =>
core.flatMap(scope, (scope) => {
let buffer: Array<Output> = []
const flush = core.suspend(() => {
if (buffer.length === 0) {
return core.unit
}
const arr = buffer
buffer = []
return f(arr)
})
return core.uninterruptibleMask((restore) =>
pipe(
internalEffect.sleep(window),
core.zipRight(flush),
internalEffect.forever,
restore,
forkDaemon,
core.flatMap((fiber) => core.scopeAddFinalizer(scope, core.interruptFiber(fiber))),
core.zipRight(addFinalizer(() => flush)),
core.as(
internalLogger.makeLogger((options) => {
buffer.push(self.log(options))
})
)
)
)
}))
// circular with Effect
/* @internal */
export const acquireRelease: {
<A, X, R2>(
release: (a: A, exit: Exit.Exit<unknown, unknown>) => Effect.Effect<X, never, R2>
): <E, R>(acquire: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R2 | R | Scope.Scope>
<A, E, R, X, R2>(
acquire: Effect.Effect<A, E, R>,
release: (a: A, exit: Exit.Exit<unknown, unknown>) => Effect.Effect<X, never, R2>
): Effect.Effect<A, E, R2 | R | Scope.Scope>
} = dual((args) => core.isEffect(args[0]), (acquire, release) =>
core.uninterruptible(
core.tap(acquire, (a) => addFinalizer((exit) => release(a, exit)))
))
/* @internal */
export const acquireReleaseInterruptible: {
<X, R2>(
release: (exit: Exit.Exit<unknown, unknown>) => Effect.Effect<X, never, R2>
): <A, E, R>(acquire: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Scope.Scope | R2 | R>
<A, E, R, X, R2>(
acquire: Effect.Effect<A, E, R>,
release: (exit: Exit.Exit<unkno