UNPKG

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
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