turbolift.internals.engine.concurrent.FiberImpl.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of turbolift-core_3 Show documentation
Show all versions of turbolift-core_3 Show documentation
Algebraic Effects for Scala 3
The newest version!
package turbolift.internals.engine.concurrent
import scala.annotation.{tailrec, switch}
import turbolift.{Computation, Signature}
import turbolift.io.{Fiber, Zipper, Warp, OnceVar, Snap, Outcome, Cause, Exceptions}
import turbolift.internals.executor.Executor
import turbolift.internals.engine.{Env, Tag, Step}
import turbolift.internals.engine.Misc._
import turbolift.internals.engine.stacked.{Stack, Store, OpCascaded, OpPush}
import Cause.{Cancelled => CancelPayload}
import FiberImpl.Hook
private[turbolift] final class FiberImpl private (
private[engine] val constantBits: Byte,
private[engine] var theParent: WarpImpl | FiberImpl | Hook,
private[engine] var theName: String,
) extends ChildLink with Fiber.Unsealed with Function1[Either[Throwable, Any], Unit]:
private[engine] var theWaiteeOrBlocker: Waitee | Blocker | Null = null
private[engine] var suspendedTag: Byte = 0
private[engine] var suspendedPayload: Any = null
private[engine] var suspendedStep: Step | Null = null
private[engine] var suspendedStack: Stack | Null = null
private[engine] var suspendedStore: Store | Null = null
private def this(constantBits: Byte, theName: String) = this(constantBits, null.asInstanceOf[Hook], theName)
//-------------------------------------------------------------------
// Finalization
//-------------------------------------------------------------------
private[engine] def doFinalize(completion: Int, initialPayload: Any, stack: Stack | Null): FiberImpl | Null =
val payload =
if isExplicit then
ZipperImpl.make(stack, initialPayload, completion)
else
initialPayload
val cancelPayload =
if isExplicit then
ZipperCases.Cancelled
else
CancelPayload
atomically {
if isCancellationUnlatched then
//// If cancellation was signalled before reaching completion, it overrides the completion.
varyingBits = (varyingBits | Bits.Completion_Cancelled /*| Bits.Cancellation_Latch*/).toByte
suspendedPayload = cancelPayload
else
varyingBits = (varyingBits | completion).toByte
suspendedPayload = payload
}
//// As a RACER:
val isLastRacer = if isRacer then endRace() else false
//// As a WAITEE:
finallyNotifyAllWaiters()
//// As a RACER or CHILD:
theParent match
case arbiter: FiberImpl =>
if isLastRacer then
arbiter
else
null
case warp: WarpImpl =>
warp.removeFiber(this)
if isRoot then
warp.unsafeCancelAndForget()
null
case hook: Hook =>
hook.warp.removeFiber(this)
hook.call()
null
def makeOutcome[A]: Outcome[A] = makeOutcome(false)
private def makeOutcome[A](void: Boolean): Outcome[A] =
getCompletion match
case Bits.Completion_Success => Outcome.Success((if void then null else suspendedPayload).asInstanceOf[A])
case Bits.Completion_Failure => Outcome.Failure(suspendedPayload.asInstanceOf[Cause])
case Bits.Completion_Cancelled => Outcome.Cancelled
private[engine] def getOrMakeZipper: ZipperImpl =
if isExplicit then
suspendedPayload.asInstanceOf[ZipperImpl]
else
ZipperImpl.make(null, suspendedPayload, getCompletion)
private def getOrMakeZipper(payload: Any, completion: Int): ZipperImpl =
if isExplicit then
suspendedPayload.asInstanceOf[ZipperImpl]
else
ZipperImpl.make(null, payload, completion)
//-------------------------------------------------------------------
// Awaiting
//-------------------------------------------------------------------
private[engine] def tryGetBlocked(blocker: Blocker, isCancellable: Boolean): Boolean =
atomicallyTry(isCancellable) {
theWaiteeOrBlocker = blocker
}
private[engine] def tryGetAwaitedBy(waiter: FiberImpl, isWaiterCancellable: Boolean): Int =
atomicallyBoth(waiter, isWaiterCancellable) {
if isPending then
subscribeWaiterUnsync(waiter)
Bits.WaiterSubscribed
else
Bits.WaiteeAlreadyCompleted
}
//-------------------------------------------------------------------
// Cancelling
//-------------------------------------------------------------------
private[engine] def cancellationCheck(isCancellable: Boolean): Boolean =
if isCancellable then
!atomicallyTry(true) {}
else
false
private[engine] def cancelBySelf(): Unit =
atomically {
varyingBits = (varyingBits | Bits.Cancellation_Signal | Bits.Cancellation_Latch).toByte
}
private def cancelByWinner(): Unit =
deepCancelLoop(this)
private[engine] def tryGetCancelledBy(canceller: FiberImpl, isCancellerCancellable: Boolean): Int =
var savedLeftRacer: WaiterLink | Null = null
var savedRightRacer: WaiterLink | Null = null
var savedWaiteeOrBlocker: Waitee | Blocker | Null = null
var savedVaryingBits: Byte = 0
var willDescend = false
val result =
atomicallyBoth(canceller, isCancellerCancellable) {
if isPending then
if !isCancelled then
varyingBits = (varyingBits | Bits.Cancellation_Signal).toByte
willDescend = true
savedLeftRacer = prevWaiter
savedRightRacer = nextWaiter
savedWaiteeOrBlocker = theWaiteeOrBlocker
savedVaryingBits = varyingBits
subscribeWaiterUnsync(canceller)
Bits.WaiterSubscribed
else
Bits.WaiteeAlreadyCompleted
}
if willDescend then
val racer = doDescend(savedLeftRacer, savedRightRacer, savedWaiteeOrBlocker, savedVaryingBits)
if racer != null then
racer.deepCancelLoop(this)
result
//// Same as `tryGetCancelledBy(canceller)`, except:
//// - doesn't synchronize on the `canceller`
//// - doesn't subscribe the `canceller`
//// - doesn't initiate `deepCancelLoop`
//// - returns first pending racer, instead of Int code
private[concurrent] override def deepCancelDown(): ChildLink | Null =
var savedLeftRacer: WaiterLink | Null = null
var savedRightRacer: WaiterLink | Null = null
var savedWaiteeOrBlocker: Waitee | Blocker | Null = null
var savedVaryingBits: Byte = 0
val willDescend =
atomically {
if isPendingAndNotCancelled then
varyingBits = (varyingBits | Bits.Cancellation_Signal).toByte
savedLeftRacer = prevWaiter
savedRightRacer = nextWaiter
savedWaiteeOrBlocker = theWaiteeOrBlocker
savedVaryingBits = varyingBits
true
else
false
}
if willDescend then
doDescend(savedLeftRacer, savedRightRacer, savedWaiteeOrBlocker, savedVaryingBits)
else
null
private[concurrent] override def deepCancelRight(): ChildLink | Null =
if whichRacerAmI == Bits.Racer_Left then
getArbiter.tryGetRightRacer
else
//// Equals null for the right RACER, bcoz racer's parent is a fiber, not a warp.
nextChild
private def tryGetRightRacer: FiberImpl | Null =
atomically {
if isPending && (getArbiterBits == Bits.Arbiter_Right) then
getRightRacer
else
null
}
private[concurrent] override def deepCancelUp(): ChildLink =
theParent match
case fiber: FiberImpl => fiber
case warp: WarpImpl => warp
case Hook(warp, _, _) => warp
private def doDescend(
savedLeftRacer: WaiterLink | Null,
savedRightRacer: WaiterLink | Null,
savedWaiteeOrBlocker: Waitee | Blocker | Null,
savedVaryingBits: Byte,
): FiberImpl | Null =
savedWaiteeOrBlocker match
case null =>
Bits.getArbiter(savedVaryingBits) match
case Bits.Arbiter_None => null
case Bits.Arbiter_Right => savedRightRacer.asInstanceOf[FiberImpl]
case _ => savedLeftRacer.asInstanceOf[FiberImpl]
case waitee: Waitee =>
waitee.unsubscribeWaiter(this)
null
case blocker: Blocker =>
blocker.unblock()
null
//-------------------------------------------------------------------
// Race
//-------------------------------------------------------------------
//// Called by the ARBITER on itself
private[engine] def tryStartRace(leftRacer: FiberImpl, rightRacer: FiberImpl, isCancellable: Boolean): Boolean =
tryStartRaceExt(leftRacer, rightRacer, isCancellable, Bits.Racer_Both)
private[engine] def tryStartRaceOfOne(leftRacer: FiberImpl, isCancellable: Boolean): Boolean =
tryStartRaceExt(leftRacer, null, isCancellable, Bits.Racer_Left)
private def tryStartRaceExt(leftRacer: FiberImpl, rightRacer: FiberImpl | Null, isCancellable: Boolean, awaitingBits: Int): Boolean =
atomicallyTry(isCancellable) {
varyingBits = (varyingBits | awaitingBits).toByte
setRacers(leftRacer, rightRacer)
}
//// Called by the RACER on its ARBITER
private def tryWinRace(racerBit: Int): FiberImpl | Null =
//// If win, return the loser
atomically {
val newBits = varyingBits & ~racerBit
varyingBits = newBits.toByte
if (newBits & Bits.Racer_Mask) != 0 then
//// Win
if racerBit == Bits.Racer_Left then
getRightRacer
else
getLeftRacer
else
//// Loss, or there was no competitor
null
}
//// Called by the RACER on itself
private def endRace(): Boolean =
val arbiter = getArbiter
val racerBit = whichRacerAmI
val loser = arbiter.tryWinRace(racerBit)
constantBits & Bits.Tree_Mask match
case Bits.Tree_ZipPar =>
if loser != null then
if getCompletion != Bits.Completion_Success then
loser.cancelByWinner()
false
else
arbiter.endRaceForZipPar()
true
case Bits.Tree_OrPar =>
if loser != null then
if getCompletion != Bits.Completion_Cancelled then
loser.cancelByWinner()
false
else
arbiter.endRaceForOrPar()
true
case Bits.Tree_OrSeq =>
arbiter.endRaceForOrSeq()
true
//// Called by the ARBITER on itself
private def endRaceForZipPar(): Unit =
val racerLeft = getLeftRacer
val racerRight = getRightRacer
val completionLeft = racerLeft.getCompletion
val completionRight = racerRight.getCompletion
val payloadLeft = racerLeft.suspendedPayload
val payloadRight = racerRight.suspendedPayload
clearRacers()
(Bits.makeRacedPair(completionLeft, completionRight): @switch) match
case Bits.Raced_SS => endRaceWithSuccessBoth(payloadLeft, payloadRight)
case Bits.Raced_FC | Bits.Raced_FS => endRaceWithFailure(payloadLeft)
case Bits.Raced_SF | Bits.Raced_CF => endRaceWithFailure(payloadRight)
case _ => endRaceWithCancelled()
private def endRaceForOrPar(): Unit =
val racerLeft = getLeftRacer
val racerRight = getRightRacer
val completionLeft = racerLeft.getCompletion
val completionRight = racerRight.getCompletion
val payloadLeft = racerLeft.suspendedPayload
val payloadRight = racerRight.suspendedPayload
clearRacers()
(Bits.makeRacedPair(completionLeft, completionRight): @switch) match
case Bits.Raced_SC => endRaceWithSuccessOne(payloadLeft)
case Bits.Raced_CS => endRaceWithSuccessOne(payloadRight)
case Bits.Raced_FC => endRaceWithFailure(payloadLeft)
case Bits.Raced_CF => endRaceWithFailure(payloadRight)
case _ => endRaceWithCancelled()
private def endRaceForOrSeq(): Unit =
val racerLeft = getLeftRacer
val completionLeft = racerLeft.getCompletion
val payloadLeft = racerLeft.suspendedPayload
clearRacers()
completionLeft match
case Bits.Completion_Success => endRaceWithSuccessOne(payloadLeft)
case Bits.Completion_Failure => endRaceWithFailure(payloadLeft)
case Bits.Completion_Cancelled => endRaceWithSuccess2nd()
private def endRaceWithSuccessBoth(payloadLeft: Any, payloadRight: Any): Unit =
val comp = OpCascaded.zipAndRestart(
stack = suspendedStack.nn,
ftorLeft = payloadLeft,
ftorRight = payloadRight,
fun = suspendedPayload.asInstanceOf[(Any, Any) => Any]
)
suspendAsSuccessComp(comp)
private def endRaceWithSuccessOne(payload: Any): Unit =
val comp = OpCascaded.restart(
stack = suspendedStack.nn,
ftor = payload,
)
suspendAsSuccessComp(comp)
private def endRaceWithSuccess2nd(): Unit =
val comp = suspendedPayload.asInstanceOf[() => AnyComp]()
suspendAsSuccessComp(comp)
private def endRaceWithCancelled(): Unit =
cancelBySelf()
suspendAsCancelled()
private def endRaceWithFailure(payload: Any): Unit =
suspendAsFailure(payload.asInstanceOf[Cause])
//-------------------------------------------------------------------
// Resume
//-------------------------------------------------------------------
//// `this` == callback for IO.async
override def apply(ee: Either[Throwable, Any]): Unit =
ee match
case Right(a) => suspendedPayload = a
case Left(e) => suspendAsFailure(e)
resume()
def resume(): Unit =
assert(isSuspended)
val env = OpPush.findTopmostEnv(suspendedStack.nn, suspendedStore.nn)
env.executor.resume(this)
private[engine] def standbyWaiter(value: Any): Unit =
theWaiteeOrBlocker = null
suspendedPayload = value
private[engine] def resumeWaiter(): Unit =
theWaiteeOrBlocker = null
resume()
private[engine] def resumeWaiterAsSuccess(value: Any): Unit =
theWaiteeOrBlocker = null
suspendedPayload = value
resume()
private[engine] def resumeWaiterAsCancelled(): Unit =
theWaiteeOrBlocker = null
suspendAsCancelled()
resume()
private[engine] def resumeWaiterAsFailure(cause: Cause): Unit =
theWaiteeOrBlocker = null
suspendAsFailure(cause)
resume()
private[engine] def resumeWaiterAsFailure(throwable: Throwable): Unit =
theWaiteeOrBlocker = null
suspendAsFailure(throwable)
resume()
//-------------------------------------------------------------------
// Suspend
//-------------------------------------------------------------------
private def isSuspended: Boolean = suspendedStack != null
private def suspendInitial(comp: AnyComp, env: Env): Unit =
suspendedTag = comp.tag.toByte
suspendedPayload = comp
suspendedStep = Step.Pop
suspendedStack = Stack.initial
suspendedStore = Store.initial(env)
private[engine] def suspend(
tag: Tag,
payload: Any,
step: Step,
stack: Stack,
store: Store,
): Unit =
assert(!isSuspended)
suspendedTag = tag.toByte
suspendedPayload = payload
suspendedStep = step
suspendedStack = stack
suspendedStore = store
private[engine] def suspendStep(
payload: Any,
step: Step,
stack: Stack,
store: Store,
): Unit =
suspend(step.tag, payload, step, stack, store)
private[engine] def suspendComp(
comp: Computation[?, ?],
step: Step,
stack: Stack,
store: Store,
): Unit =
suspend(comp.tag, comp, step, stack, store)
private[engine] def clearSuspension(): Unit =
suspendedTag = 0
suspendedPayload = null
suspendedStep = null
suspendedStack = null
suspendedStore = null
private[engine] def suspendForRace(
payload: Any,
step: Step,
stack: Stack,
store: Store,
): Unit =
suspendedPayload = payload
suspendedStep = step
suspendedStack = stack
suspendedStore = store
private[engine] def suspendAsSuccessPure(value: Any): Unit =
suspendedTag = suspendedStep.nn.tag.toByte //@#@NNEC? call sites
suspendedPayload = value
private def suspendAsSuccessComp(comp: AnyComp): Unit =
suspendedTag = comp.tag.toByte
suspendedPayload = comp
private[engine] def suspendAsCancelled(): Unit =
suspendedTag = Step.Cancel.tag.toByte
suspendedStep = Step.Cancel
suspendedPayload = CancelPayload
private[engine] def suspendAsFailure(cause: Cause): Unit =
suspendedTag = Step.Throw.tag.toByte
suspendedStep = Step.Throw
suspendedPayload = cause
private[engine] def suspendAsFailure(throwable: Throwable): Unit =
suspendAsFailure(Cause(throwable))
//-------------------------------------------------------------------
// Public API
//-------------------------------------------------------------------
override def toString: String = name
override def name: String =
if theName.isEmpty then
theName = s"Fib#%04X".format(hashCode & 0xFFFF)
theName
override def parent: Fiber.Untyped | Warp =
theParent match
case fiber: FiberImpl => fiber.untyped
case warp: WarpImpl => warp
case Hook(warp, _, _) => warp
override def unsafeCancelAndForget(): Unit = doCancelAndForget()
override def unsafeStatus(): Fiber.Status =
var savedLeftLink: WaiterLink | Null = null
var savedRightLink: WaiterLink | Null = null
var savedWaiteeOrBlocker: Waitee | Blocker | Null = null
var savedVaryingBits: Byte = 0
atomically {
savedLeftLink = prevWaiter
savedRightLink = nextWaiter
savedWaiteeOrBlocker = theWaiteeOrBlocker
savedVaryingBits = varyingBits
}
if Bits.isPending(savedVaryingBits) then
val role =
def l = savedLeftLink.nn.asFiber
def r = savedRightLink.nn.asFiber
def w = savedWaiteeOrBlocker.asInstanceOf[Fiber.Untyped | Warp | OnceVar.Untyped]
import Fiber.Role
savedWaiteeOrBlocker match
case null =>
Bits.getArbiter(savedVaryingBits) match
case Bits.Arbiter_None => if savedLeftLink == null then Role.Runner else Role.Standby
case Bits.Arbiter_Left => Role.Arbiter(List(l))
case Bits.Arbiter_Right => Role.Arbiter(List(r))
case Bits.Arbiter_Both => Role.Arbiter(List(l, r))
case _: Waitee => Role.Waiter(w)
case _: Blocker => Role.Blocker
val isCancelled = Bits.isCancellationSignalled(savedVaryingBits)
Fiber.Status.Pending(role, isCancelled = isCancelled, isRacer = isRacer)
else
Fiber.Status.Completed(makeOutcome(void = true))
override def unsafePoll(): Option[Zipper.Untyped] =
var savedBits: Int = 0
var savedPayload: Any = null
atomically {
savedBits = varyingBits
savedPayload = suspendedPayload
}
Bits.getCompletion(savedBits) match
case Bits.Completion_Pending => None
case completion => Some(getOrMakeZipper(savedPayload, completion))
//-------------------------------------------------------------------
// Misc
//-------------------------------------------------------------------
private def isRoot: Boolean = Bits.isRoot(constantBits)
private def isExplicit: Boolean = Bits.isExplicit(constantBits)
private[internals] def isReentry: Boolean = Bits.isReentry(constantBits)
private def getCompletion: Int = Bits.getCompletion(varyingBits)
private def getArbiterBits: Int = Bits.getArbiter(varyingBits)
private def whichRacerAmI: Int = constantBits & Bits.Racer_Mask
private def isRacer: Boolean = whichRacerAmI != Bits.Racer_None
private def getArbiter: FiberImpl = theParent.asInstanceOf[FiberImpl]
private def getLeftRacer: FiberImpl = prevWaiter.asInstanceOf[FiberImpl]
private def getRightRacer: FiberImpl = nextWaiter.asInstanceOf[FiberImpl]
private def clearRacers(): Unit = clearWaiterLink()
private def setRacers(left: FiberImpl, right: FiberImpl | Null): Unit =
prevWaiter = left
nextWaiter = right
def createChild(bits: Byte): FiberImpl = new FiberImpl(bits, this, "")
private[engine] def payloadAs[T]: T = suspendedPayload.asInstanceOf[T]
private[engine] def payloadAs_=[T](value: T): Unit = suspendedPayload = value
private[turbolift] object FiberImpl:
type Callback = Outcome[Nothing] => Unit
def create(comp: Computation[?, ?], executor: Executor, name: String, isReentry: Boolean, callback: Callback): FiberImpl =
val reentryBit = if isReentry then Bits.Const_Reentry else 0
val constantBits = (Bits.Tree_Root | reentryBit).toByte
val fiber = new FiberImpl(constantBits, name)
val warp = WarpImpl.root
fiber.theParent = Hook(warp, fiber, callback.asInstanceOf[Any => Unit])
warp.tryAddFiber(fiber)
val env = Env.initial(executor)
fiber.suspendInitial(comp.untyped, env)
fiber
def createExplicit(warp: WarpImpl, name: String, callback: (ZipperImpl => Unit) | Null): FiberImpl =
val fiber = new FiberImpl(Bits.Tree_Explicit.toByte, name)
fiber.theParent = if callback == null then warp else Hook(warp, fiber, callback.nn.asInstanceOf[Any => Unit])
fiber
final case class Hook(warp: WarpImpl, fiber: FiberImpl, callback: Any => Unit):
def call(): Unit =
if fiber.isExplicit then
callback(fiber.suspendedPayload)
else
callback(fiber.makeOutcome)