UNPKG

@effect-ts/system

Version:

Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.

1,338 lines (1,168 loc) 40.2 kB
// ets_tracing: off // cause import * as Cause from "../Cause/core.js" // effect import { RuntimeError } from "../Cause/errors.js" import * as A from "../Collections/Immutable/Array/index.js" import * as L from "../Collections/Immutable/List/index.js" import { forEachUnit_ } from "../Effect/excl-forEach.js" import { IFail, instruction, ISucceed } from "../Effect/primitives.js" // either import * as E from "../Either/index.js" // exit import * as Exit from "../Exit/api.js" // fiberRef import * as FR from "../FiberRef/fiberRef.js" import * as update from "../FiberRef/update.js" import { constVoid } from "../Function/index.js" // option import * as O from "../Option/index.js" // supervisor / scope import * as Scope from "../Scope/index.js" import * as St from "../Structural/index.js" import * as Sup from "../Supervisor/index.js" // support import { AtomicReference } from "../Support/AtomicReference/index.js" import { RingBuffer } from "../Support/RingBuffer/index.js" import { defaultScheduler } from "../Support/Scheduler/index.js" import * as X from "../XPure/index.js" import * as T from "./_internal/effect.js" // fiber import * as Fiber from "./core.js" import type { Platform } from "./platform.js" import type { Callback } from "./state.js" import { FiberStateDone, FiberStateExecuting, initial, interrupting } from "./state.js" import * as Status from "./status.js" import type { TraceElement } from "./tracing.js" import { SourceLocation, Trace, traceLocation, truncatedParentTrace } from "./tracing.js" export type FiberRefLocals = Map<FR.Runtime<any>, any> export class Stack<A> { constructor(readonly value: A, readonly previous?: Stack<A>) {} } export class InterruptExit { readonly _tag = "InterruptExit" constructor( readonly apply: (a: any) => T.Effect<any, any, any>, readonly trace?: string ) {} } export class TracingExit { readonly _tag = "TracingExit" constructor( readonly apply: (a: any) => T.Effect<any, any, any>, readonly trace?: string ) {} } export class HandlerFrame { readonly _tag = "HandlerFrame" constructor( readonly apply: (a: any) => T.Effect<any, any, any>, readonly trace?: string ) {} } export class ApplyFrame { readonly _tag = "ApplyFrame" constructor( readonly apply: (a: any) => T.Effect<any, any, any>, readonly trace?: string ) {} } export type Frame = | InterruptExit | TracingExit | T.IFold<any, any, any, any, any, any, any, any, any> | HandlerFrame | ApplyFrame export const currentFiber = new AtomicReference<FiberContext<any, any> | null>(null) export const unsafeCurrentFiber = () => O.fromNullable(currentFiber.get) const noop = O.some(constVoid) export class FiberContext<E, A> implements Fiber.Runtime<E, A> { readonly _tag = "RuntimeFiber" readonly state = new AtomicReference(initial<E, A>()) asyncEpoch = 0 | 0 stack?: Stack<Frame> | undefined = undefined environments?: Stack<any> | undefined = new Stack(this.startEnv) interruptStatus?: Stack<boolean> | undefined = new Stack(this.startIStatus.toBoolean) supervisors: Stack<Sup.Supervisor<any>> = new Stack(this.supervisor0) forkScopeOverride?: Stack<O.Option<Scope.Scope<Exit.Exit<any, any>>>> | undefined = undefined scopeKey: Scope.Key | undefined = undefined traceStatusEnabled = this.platform.value.traceExecution || this.platform.value.traceStack traceStatusStack: Stack<boolean> | undefined = this.traceStatusEnabled ? new Stack(this.initialTracingStatus) : undefined executionTraces = this.traceStatusEnabled ? new RingBuffer<TraceElement>(this.platform.value.executionTraceLength) : undefined stackTraces = this.traceStatusEnabled ? new RingBuffer<TraceElement>( this.platform.value.stackTraceLength, (x) => x._tag === "NoLocation" ) : undefined constructor( readonly fiberId: Fiber.FiberID, readonly startEnv: any, readonly startIStatus: Fiber.InterruptStatus, readonly fiberRefLocals: FiberRefLocals, readonly supervisor0: Sup.Supervisor<any>, readonly openScope: Scope.Open<Exit.Exit<E, A>>, readonly maxOp: number, readonly reportFailure: (e: Cause.Cause<E>) => void, readonly platform: Platform<unknown>, readonly parentTrace: O.Option<Trace>, readonly initialTracingStatus: boolean ) { this.evaluateNow = this.evaluateNow.bind(this) } get [St.hashSym](): number { return St.hash(this.id) } [St.equalsSym](that: unknown): boolean { return that instanceof FiberContext && St.equals(this.id, that.id) } get poll() { return T.succeedWith(() => this.poll0()) } addTrace(trace?: string) { if (this.inTracingRegion && trace) { this.executionTraces!.push(new SourceLocation(trace)) } } addTraceValue(trace: TraceElement) { if (this.inTracingRegion && trace._tag === "SourceLocation") { this.executionTraces!.push(trace) } } getRef<K>(fiberRef: FR.Runtime<K>): T.UIO<K> { return T.succeedWith(() => this.fiberRefLocals.get(fiberRef) || fiberRef.initial) } poll0() { const state = this.state.get switch (state._tag) { case "Executing": { return O.none } case "Done": { return O.some(state.value) } } } interruptExit = new InterruptExit((v: any) => { if (this.isInterruptible) { this.popInterruptStatus() return instruction(T.succeed(v)) } else { return instruction( T.succeedWith(() => { this.popInterruptStatus() return v }) ) } }) popTracingStatus() { this.traceStatusStack = this.traceStatusStack?.previous } pushTracingStatus(flag: boolean) { this.traceStatusStack = new Stack(flag, this.traceStatusStack) } tracingExit = new TracingExit((v: any) => { this.popTracingStatus() return new ISucceed(v) }) get isInterruptible() { return this.interruptStatus ? this.interruptStatus.value : true } get isInterrupted() { return !Cause.isEmpty(this.state.get.interrupted) } get isInterrupting() { return interrupting(this.state.get) } get shouldInterrupt() { return this.isInterrupted && this.isInterruptible && !this.isInterrupting } get isStackEmpty() { return !this.stack } get id() { return this.fiberId } pushContinuation(k: Frame) { if (this.platform.value.traceStack && this.inTracingRegion) { this.stackTraces!.push(traceLocation(k.trace)) } this.stack = new Stack(k, this.stack) } popStackTrace() { this.stackTraces!.pop() } popContinuation() { const current = this.stack?.value this.stack = this.stack?.previous return current } pushEnv(k: any) { this.environments = new Stack(k, this.environments) } popEnv() { const current = this.environments?.value this.environments = this.environments?.previous return current } pushInterruptStatus(flag: boolean) { this.interruptStatus = new Stack(flag, this.interruptStatus) } popInterruptStatus() { const current = this.interruptStatus?.value this.interruptStatus = this.interruptStatus?.previous return current } runAsync(k: Callback<E, A>) { const v = this.register0((xx) => k(Exit.flatten(xx))) if (v) { k(v) } } /** * Unwinds the stack, looking for the first error handler, and exiting * interruptible / uninterruptible regions. */ unwindStack() { let unwinding = true let discardedFolds = false // Unwind the stack, looking for an error handler: while (unwinding && !this.isStackEmpty) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const frame = this.popContinuation()! switch (frame._tag) { case "InterruptExit": { this.popInterruptStatus() break } case "TracingExit": { this.popTracingStatus() break } case "Fold": { if (this.platform.value.traceStack && this.inTracingRegion) { this.popStackTrace() } if (!this.shouldInterrupt) { // Push error handler back onto the stack and halt iteration: this.pushContinuation(new HandlerFrame(frame.failure, frame.trace)) unwinding = false } else { discardedFolds = true } break } default: { if (this.platform.value.traceStack && this.inTracingRegion) { this.popStackTrace() } } } } return discardedFolds } register0(k: Callback<never, Exit.Exit<E, A>>): Exit.Exit<E, A> | null { const oldState = this.state.get switch (oldState._tag) { case "Done": { return oldState.value } case "Executing": { const observers = [k, ...oldState.observers] this.state.set( new FiberStateExecuting(oldState.status, observers, oldState.interrupted) ) return null } } } nextInstr(value: any): T.Instruction | undefined { if (!this.isStackEmpty) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const k = this.popContinuation()! if (this.inTracingRegion && this.platform.value.traceExecution) { this.addTrace(k.trace) } if ( this.platform.value.traceStack && k._tag !== "InterruptExit" && k._tag !== "TracingExit" ) { this.popStackTrace() } return instruction(k.apply(value)) } else { return this.done(Exit.succeed(value)) } } notifyObservers(v: Exit.Exit<E, A>, observers: Callback<never, Exit.Exit<E, A>>[]) { const result = Exit.succeed(v) observers .slice(0) .reverse() .forEach((k) => k(result)) } observe0(k: Callback<never, Exit.Exit<E, A>>): O.Option<T.UIO<Exit.Exit<E, A>>> { const x = this.register0(k) if (x != null) { return O.some(T.succeed(x)) } return O.none } get await(): T.UIO<Exit.Exit<E, A>> { return T.effectMaybeAsyncInterruptBlockingOn( (k): E.Either<T.UIO<void>, T.UIO<Exit.Exit<E, A>>> => { const cb: Callback<never, Exit.Exit<E, A>> = (x) => k(T.done(x)) return O.fold_( this.observe0(cb), () => E.left(T.succeedWith(() => this.interruptObserver(cb))), E.right ) }, [this.fiberId] ) } interruptObserver(k: Callback<never, Exit.Exit<E, A>>) { const oldState = this.state.get if (oldState._tag === "Executing") { const observers = oldState.observers.filter((o) => o !== k) this.state.set( new FiberStateExecuting(oldState.status, observers, oldState.interrupted) ) } } interruptAs(fiberId: Fiber.FiberID): T.UIO<Exit.Exit<E, A>> { const interruptedCause = Cause.interrupt(fiberId) return T.suspend(() => { const oldState = this.state.get if ( oldState._tag === "Executing" && oldState.status._tag === "Suspended" && oldState.status.interruptible && !interrupting(oldState) ) { const newCause = Cause.combineSeq(oldState.interrupted, interruptedCause) this.state.set( new FiberStateExecuting( Status.withInterrupting(true)(oldState.status), oldState.observers, newCause ) ) this.evaluateLater(instruction(T.interruptAs(fiberId))) } else if (oldState._tag === "Executing") { const newCause = Cause.combineSeq(oldState.interrupted, interruptedCause) this.state.set( new FiberStateExecuting(oldState.status, oldState.observers, newCause) ) } return this.await }) } done(v: Exit.Exit<E, A>): T.Instruction | undefined { const oldState = this.state.get switch (oldState._tag) { case "Done": { // Already done return undefined } case "Executing": { if (this.openScope.scope.unsafeClosed) { /* * We are truly "done" because all the children of this fiber have terminated, * and there are no more pending effects that we have to execute on the fiber. */ this.state.set(new FiberStateDone(v)) this.reportUnhandled(v) this.notifyObservers(v, oldState.observers) return undefined } else { /* * We are not done yet, because we have to close the scope of the fiber. */ this.state.set( new FiberStateExecuting( Status.toFinishing(oldState.status), oldState.observers, oldState.interrupted ) ) this.setInterrupting(true) return instruction(T.chain_(this.openScope.close(v), () => T.done(v))) } } } } reportUnhandled(exit: Exit.Exit<E, A>) { if (exit._tag === "Failure") { this.reportFailure(exit.cause) } } setInterrupting(value: boolean): void { const oldState = this.state.get switch (oldState._tag) { case "Executing": { this.state.set( new FiberStateExecuting( Status.withInterrupting(value)(oldState.status), oldState.observers, oldState.interrupted ) ) return } case "Done": { return } } } enterAsync( epoch: number, blockingOn: readonly Fiber.FiberID[] ): T.Instruction | undefined { const oldState = this.state.get switch (oldState._tag) { case "Done": { throw new RuntimeError(`Unexpected fiber completion ${this.fiberId}`) } case "Executing": { const newState = new FiberStateExecuting( new Status.Suspended( oldState.status, this.isInterruptible, epoch, blockingOn ), oldState.observers, oldState.interrupted ) this.state.set(newState) if (this.shouldInterrupt) { // Fiber interrupted, so go back into running state: this.exitAsync(epoch) return instruction(T.halt(this.state.get.interrupted)) } else { return undefined } } } } exitAsync(epoch: number): boolean { const oldState = this.state.get switch (oldState._tag) { case "Done": { return false } case "Executing": { if (oldState.status._tag === "Suspended" && epoch === oldState.status.epoch) { this.state.set( new FiberStateExecuting( oldState.status.previous, oldState.observers, oldState.interrupted ) ) return true } else { return false } } } } resumeAsync(epoch: number) { return (_: T.Effect<any, any, any>) => { if (this.exitAsync(epoch)) { this.evaluateLater(instruction(_)) } } } evaluateLater(i0: T.Instruction) { defaultScheduler(() => this.evaluateNow(i0)) } get scope(): Scope.Scope<Exit.Exit<E, A>> { return this.openScope.scope } get status(): T.UIO<Status.Status> { return T.succeed(this.state.get.status) } fork( i0: T.Instruction, forkScope: O.Option<Scope.Scope<Exit.Exit<any, any>>>, reportFailure: O.Option<(e: Cause.Cause<E>) => void> ): FiberContext<any, any> { const childFiberRefLocals: FiberRefLocals = new Map() this.fiberRefLocals.forEach((v, k) => { childFiberRefLocals.set(k, k.fork(v)) }) const parentScope: Scope.Scope<Exit.Exit<any, any>> = O.getOrElse_( forkScope._tag === "Some" ? forkScope : this.forkScopeOverride?.value || O.none, () => this.scope ) const currentEnv = this.environments?.value || {} const currentSup = this.supervisors.value const childId = Fiber.newFiberId() const childScope = Scope.unsafeMakeScope<Exit.Exit<E, A>>() const ancestry = this.inTracingRegion && (this.platform.value.traceExecution || this.platform.value.traceStack) ? O.some(this.cutAncestryTrace(this.captureTrace())) : O.none const childContext = new FiberContext( childId, currentEnv, Fiber.interruptStatus(this.isInterruptible), childFiberRefLocals, currentSup, childScope, this.maxOp, O.getOrElse_(reportFailure, () => this.reportFailure), this.platform, ancestry, this.inTracingRegion ) if (currentSup !== Sup.none) { currentSup.unsafeOnStart(currentEnv, i0, O.some(this), childContext) childContext.onDone((exit) => { currentSup.unsafeOnEnd(Exit.flatten(exit), childContext) }) } const toExecute = this.parentScopeOp(parentScope, childContext, i0) childContext.evaluateLater(toExecute) return childContext } private parentScopeOp( parentScope: Scope.Scope<Exit.Exit<any, any>>, childContext: FiberContext<E, A>, i0: T.Instruction ): T.Instruction { if (parentScope !== Scope.globalScope) { const exitOrKey = parentScope.unsafeEnsure((exit) => T.suspend((): T.UIO<any> => { const _interruptors = exit._tag === "Failure" ? Cause.interruptors(exit.cause) : [] const head = _interruptors[0] if (head) { return childContext.interruptAs(head) } else { return childContext.interruptAs(this.fiberId) } }) ) return E.fold_( exitOrKey, (exit) => { switch (exit._tag) { case "Failure": { return instruction( T.interruptAs( O.getOrElse_( A.head(Array.from(Cause.interruptors(exit.cause))), () => this.fiberId ) ) ) } case "Success": { return instruction(T.interruptAs(this.fiberId)) } } }, (key) => { childContext.scopeKey = key // Remove the finalizer key from the parent scope when the child fiber // terminates: childContext.onDone(() => { parentScope.unsafeDeny(key) }) return i0 } ) } else { return i0 } } onDone(k: Callback<never, Exit.Exit<E, A>>): void { const oldState = this.state.get switch (oldState._tag) { case "Done": { k(Exit.succeed(oldState.value)) return } case "Executing": { this.state.set( new FiberStateExecuting( oldState.status, [k, ...oldState.observers], oldState.interrupted ) ) } } } getDescriptor() { return new Fiber.Descriptor( this.fiberId, this.state.get.status, Cause.interruptors(this.state.get.interrupted), Fiber.interruptStatus(this.isInterruptible), this.scope ) } complete<R, R1, R2, E2, A2, R3, E3, A3>( winner: Fiber.Fiber<any, any>, loser: Fiber.Fiber<any, any>, cont: ( exit: Exit.Exit<any, any>, fiber: Fiber.Fiber<any, any> ) => T.Effect<any, any, any>, winnerExit: Exit.Exit<any, any>, ab: AtomicReference<boolean>, cb: (_: T.Effect<R & R1 & R2 & R3, E2 | E3, A2 | A3>) => void ): void { if (ab.compareAndSet(true, false)) { switch (winnerExit._tag) { case "Failure": { cb(cont(winnerExit, loser)) break } case "Success": { cb(T.chain(() => cont(winnerExit, loser))(winner.inheritRefs)) break } } } } get inheritRefs() { return T.suspend(() => { const locals = this.fiberRefLocals if (locals.size === 0) { return T.unit } else { return forEachUnit_(locals, ([fiberRef, value]) => update.update((old) => fiberRef.join(old, value))(fiberRef) ) } }) } get inTracingRegion() { return this.traceStatusStack ? this.traceStatusStack.value : this.initialTracingStatus } raceWithImpl<R, E, A, R1, E1, A1, R2, E2, A2, R3, E3, A3>( race: T.IRaceWith<R, E, A, R1, E1, A1, R2, E2, A2, R3, E3, A3> ): T.Effect<R & R1 & R2 & R3, E2 | E3, A2 | A3> { const raceIndicator = new AtomicReference(true) const left = this.fork(instruction(race.left), race.scope, noop) const right = this.fork(instruction(race.right), race.scope, noop) return T.effectAsyncBlockingOn<R & R1 & R2 & R3, E2 | E3, A2 | A3>( (cb) => { const leftRegister = left.register0((exit) => { switch (exit._tag) { case "Failure": { this.complete(left, right, race.leftWins, exit, raceIndicator, cb) break } case "Success": { this.complete(left, right, race.leftWins, exit.value, raceIndicator, cb) break } } }) if (leftRegister != null) { this.complete(left, right, race.leftWins, leftRegister, raceIndicator, cb) } else { const rightRegister = right.register0((exit) => { switch (exit._tag) { case "Failure": { this.complete(right, left, race.rightWins, exit, raceIndicator, cb) break } case "Success": { this.complete( right, left, race.rightWins, exit.value, raceIndicator, cb ) break } } }) if (rightRegister != null) { this.complete(right, left, race.rightWins, rightRegister, raceIndicator, cb) } } }, [left.fiberId, right.fiberId], race.trace ) } captureTrace(): Trace { const exec = this.executionTraces ? this.executionTraces.listReverse : L.empty() const stack = this.stackTraces ? this.stackTraces.listReverse : L.empty() return new Trace(this.id, exec, stack, this.parentTrace) } cutAncestryTrace(trace: Trace): Trace { const maxExecLength = this.platform.value.ancestorExecutionTraceLength const maxStackLength = this.platform.value.ancestorStackTraceLength const maxAncestors = this.platform.value.ancestryLength - 1 const truncated = truncatedParentTrace(trace, maxAncestors) return new Trace( trace.fiberId, L.take_(trace.executionTrace, maxExecLength), L.take_(trace.stackTrace, maxStackLength), truncated ) } evaluateNow(i0: T.Instruction): void { try { // eslint-disable-next-line prefer-const let current: T.Instruction | undefined = i0 currentFiber.set(this) while (current != null) { try { let opCount = 0 while (current != null) { // Check to see if the fiber should continue executing or not: if (!this.shouldInterrupt) { // Fiber does not need to be interrupted, but might need to yield: if (opCount === this.maxOp) { this.evaluateLater(current) current = undefined } else { // Fiber is neither being interrupted nor needs to yield. Execute // the next instruction in the program: switch (current._tag) { case "FlatMap": { this.pushContinuation(new ApplyFrame(current.f, current.trace)) current = instruction(current.val) break } case "XPure": { const result: E.Either<any, any> = X.runEither( X.provideAll_(current, this.environments?.value || {}) ) current = result._tag === "Left" ? new IFail((t) => Cause.traced(Cause.fail(result.left), t())) : new ISucceed(result.right) break } case "TracingStatus": { if (this.traceStatusStack) { this.pushTracingStatus(current.flag) this.stack = new Stack(this.tracingExit, this.stack) } current = instruction(current.effect) break } case "CheckTracingStatus": { current = instruction(current.f(this.inTracingRegion)) break } case "Trace": { current = this.nextInstr(this.captureTrace()) break } case "Tracer": { current = instruction( current.f((trace) => { if ( trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(trace) } }) ) break } case "Succeed": { if ( current.trace && this.platform.value.traceEffects && this.inTracingRegion ) { this.addTrace(current.trace) } current = this.nextInstr(current.val) break } case "EffectTotal": { if ( current.trace && this.platform.value.traceEffects && this.inTracingRegion ) { this.addTrace(current.trace) } current = this.nextInstr(current.effect()) break } case "Fail": { if ( current.trace && this.platform.value.traceEffects && this.inTracingRegion ) { this.addTrace(current.trace) } const fullCause = current.fill(() => this.captureTrace()) const discardedFolds = this.unwindStack() const maybeRedactedCause = discardedFolds ? // We threw away some error handlers while unwinding the stack because // we got interrupted during this instruction. So it's not safe to return // typed failures from cause0, because they might not be typed correctly. // Instead, we strip the typed failures, and return the remainders and // the interruption. Cause.stripFailures(fullCause) : fullCause if (this.isStackEmpty) { // Error not caught, stack is empty: const cause = () => { const interrupted = this.state.get.interrupted const causeAndInterrupt = !Cause.contains(interrupted)( maybeRedactedCause ) ? Cause.combineSeq(maybeRedactedCause, interrupted) : maybeRedactedCause return causeAndInterrupt } this.setInterrupting(true) current = this.done(Exit.halt(cause())) } else { this.setInterrupting(false) // Error caught, next continuation on the stack will deal // with it, so we just have to compute it here: current = this.nextInstr(maybeRedactedCause) } break } case "Platform": { if ( current.trace && this.inTracingRegion && this.platform.value.traceExecution ) { this.addTrace(current.trace) } current = instruction(current.f(this.platform)) break } case "Fold": { this.pushContinuation(current) current = instruction(current.value) break } case "InterruptStatus": { if ( current.trace && this.inTracingRegion && this.platform.value.traceExecution ) { this.addTrace(current.trace) } this.pushInterruptStatus(current.flag.toBoolean) this.stack = new Stack(this.interruptExit, this.stack) current = instruction(current.effect) break } case "CheckInterrupt": { if ( current.trace && this.inTracingRegion && this.platform.value.traceExecution ) { this.addTrace(current.trace) } current = instruction( current.f(Fiber.interruptStatus(this.isInterruptible)) ) break } case "EffectPartial": { const c = current try { if ( c.trace && this.inTracingRegion && this.platform.value.traceEffects ) { this.addTrace(c.trace) } current = this.nextInstr(c.effect()) } catch (e) { current = instruction(T.fail(c.onThrow(e))) } break } case "EffectAsync": { const epoch = this.asyncEpoch this.asyncEpoch = epoch + 1 const c = current current = this.enterAsync(epoch, c.blockingOn) if (!current) { const k = c.register if ( c.trace && this.platform.value.traceEffects && this.inTracingRegion ) { this.addTrace(c.trace) } const h = k(this.resumeAsync(epoch)) switch (h._tag) { case "None": { current = undefined break } case "Some": { if (this.exitAsync(epoch)) { current = instruction(h.value) } else { current = undefined } } } } break } case "Fork": { if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } current = this.nextInstr( this.fork( instruction(current.value), current.scope, current.reportFailure ) ) break } case "Descriptor": { if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } current = instruction(current.f(this.getDescriptor())) break } case "Yield": { current = undefined this.evaluateLater(instruction(T.unit)) break } case "Read": { if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } current = instruction( current.f(this.environments ? this.environments.value : {}) ) break } case "Provide": { if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } const c = current current = instruction( T.bracket_( T.succeedWith(() => { this.pushEnv(c.r) }), () => c.next, () => T.succeedWith(() => { this.popEnv() }) ) ) break } case "Suspend": { if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } current = instruction(current.factory(this.platform, this.fiberId)) break } case "SuspendPartial": { const c = current try { if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } current = instruction(c.factory(this.platform, this.fiberId)) } catch (e) { current = instruction(T.fail(c.onThrow(e))) } break } case "FiberRefNew": { const fiberRef = new FR.Runtime( current.initial, current.onFork, current.onJoin ) this.fiberRefLocals.set(fiberRef, current.initial) current = this.nextInstr(fiberRef) break } case "FiberRefModify": { const c = current const oldValue = O.fromNullable(this.fiberRefLocals.get(c.fiberRef)) if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } const { tuple: [result, newValue] } = current.f(O.getOrElse_(oldValue, () => c.fiberRef.initial)) this.fiberRefLocals.set(c.fiberRef, newValue) current = this.nextInstr(result) break } case "RaceWith": { current = instruction(this.raceWithImpl(current)) break } case "Supervise": { if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } const c = current const lastSupervisor = this.supervisors.value const newSupervisor = c.supervisor.and(lastSupervisor) const push = T.succeedWith(() => { this.supervisors = new Stack(newSupervisor, this.supervisors) }) const pop = T.succeedWith(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.supervisors = this.supervisors.previous! }) current = instruction( T.bracket_( push, () => c.effect, () => pop ) ) break } case "GetForkScope": { const c = current if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } current = instruction( c.f( O.getOrElse_( this.forkScopeOverride?.value || O.none, () => this.scope ) ) ) break } case "OverrideForkScope": { const c = current if ( current.trace && this.platform.value.traceExecution && this.inTracingRegion ) { this.addTrace(current.trace) } const push = T.succeedWith(() => { this.forkScopeOverride = new Stack( c.forkScope, this.forkScopeOverride ) }) const pop = T.succeedWith(() => { this.forkScopeOverride = this.forkScopeOverride?.previous }) current = instruction( T.bracket_( push, () => c.effect, () => pop ) ) break } default: { throw new RuntimeError( `operation not supported: ${JSON.stringify(current)}` ) } } } } else { current = instruction(T.halt(this.state.get.interrupted)) this.setInterrupting(true) } opCount += 1 } } catch (e) { this.setInterrupting(true) current = instruction(T.die(e)) } } } finally { currentFiber.set(null) } } }