import * as Context from "../Context.ts" import * as Duration from "../Duration.ts" import * as Effect from "../Effect.ts" import * as Exit from "../Exit.ts" import * as Fiber from "../Fiber.ts" import { identity } from "../Function.ts" import { pipeArguments } from "../Pipeable.ts" import type * as RcRef from "../RcRef.ts" import * as Scope from "../Scope.ts" import * as Semaphore from "../Semaphore.ts" const TypeId = "~effect/RcRef" type State = State.Empty | State.Acquired | State.Closed declare namespace State { interface Empty { readonly _tag: "Empty" } interface Acquired { readonly _tag: "Acquired" readonly value: A readonly scope: Scope.Closeable fiber: Fiber.Fiber | undefined refCount: number invalidated: boolean } interface Closed { readonly _tag: "Closed" } } const stateEmpty: State = { _tag: "Empty" } const stateClosed: State = { _tag: "Closed" } const variance: RcRef.RcRef.Variance = { _A: identity, _E: identity } class RcRefImpl implements RcRef.RcRef { readonly [TypeId]: RcRef.RcRef.Variance = variance pipe() { return pipeArguments(this, arguments) } state: State = stateEmpty readonly semaphore = Semaphore.makeUnsafe(1) readonly acquire: Effect.Effect readonly context: Context.Context readonly scope: Scope.Scope readonly idleTimeToLive: Duration.Duration | undefined constructor( acquire: Effect.Effect, context: Context.Context, scope: Scope.Scope, idleTimeToLive: Duration.Duration | undefined ) { this.acquire = acquire this.context = context this.scope = scope this.idleTimeToLive = idleTimeToLive } } /** @internal */ export const make = (options: { readonly acquire: Effect.Effect readonly idleTimeToLive?: Duration.Input | undefined }) => Effect.withFiber, never, R | Scope.Scope>((fiber) => { const context = fiber.context as Context.Context const scope = Context.get(context, Scope.Scope) const ref = new RcRefImpl( options.acquire as Effect.Effect, context, scope, options.idleTimeToLive ? Duration.fromInputUnsafe(options.idleTimeToLive) : undefined ) return Effect.as( Scope.addFinalizerExit(scope, () => { const close = ref.state._tag === "Acquired" ? Scope.close(ref.state.scope, Exit.void) : Effect.void ref.state = stateClosed return close }), ref ) }) const getState = (self: RcRefImpl) => Effect.uninterruptibleMask((restore) => { switch (self.state._tag) { case "Closed": { return Effect.interrupt } case "Acquired": { self.state.refCount++ return self.state.fiber ? Effect.as(Fiber.interrupt(self.state.fiber), self.state) : Effect.succeed(self.state) } case "Empty": { const scope = Scope.makeUnsafe() return self.semaphore.withPermits(1)( restore(Effect.provideContext( self.acquire as Effect.Effect, Context.add(self.context, Scope.Scope, scope) )).pipe(Effect.map((value) => { const state: State.Acquired = { _tag: "Acquired", value, scope, fiber: undefined, refCount: 1, invalidated: false } self.state = state return state })) ) } } }) /** @internal */ export const get = Effect.fnUntraced(function*( self_: RcRef.RcRef ) { const self = self_ as RcRefImpl const state = yield* getState(self) const scope = yield* Effect.scope const isFinite = self.idleTimeToLive !== undefined && Duration.isFinite(self.idleTimeToLive) yield* Scope.addFinalizerExit(scope, () => { state.refCount-- if (state.refCount > 0) { return Effect.void } if (self.idleTimeToLive === undefined) { self.state = stateEmpty return Scope.close(state.scope, Exit.void) } else if (state.invalidated) { return Scope.close(state.scope, Exit.void) } else if (!isFinite) { return Effect.void } state.fiber = Effect.sleep(self.idleTimeToLive).pipe( Effect.flatMap(() => { if (self.state._tag === "Acquired" && self.state.refCount === 0) { self.state = stateEmpty return Scope.close(state.scope, Exit.void) } return Effect.void }), Effect.ensuring(Effect.sync(() => { state.fiber = undefined })), Effect.runForkWith(self.context), Fiber.runIn(self.scope) ) return Effect.void }) return state.value }) /** @internal */ export const invalidate = ( self_: RcRef.RcRef ): Effect.Effect => { const self = self_ as RcRefImpl return Effect.uninterruptible(Effect.suspend(() => { if (self.state._tag !== "Acquired") { return Effect.void } const state = self.state self.state = stateEmpty state.invalidated = true if (state.refCount > 0) { return Effect.void } state.fiber?.interruptUnsafe() return Scope.close(state.scope, Exit.void) })) }