
zio.stm.ZSTM.scala Maven / Gradle / Ivy
/*
* Copyright 2019-2024 John A. De Goes and the ZIO Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package zio.stm
import zio.stacktracer.TracingImplicits.disableAutoTrace
import zio._
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
import scala.collection.{SortedSet, immutable}
import scala.collection.mutable.TreeMap
import scala.util.control.ControlThrowable
import scala.util.{Failure, Success, Try}
/**
* `STM[E, A]` represents an effect that can be performed transactionally,
* resulting in a failure `E` or a value `A`.
*
* {{{
* def transfer(receiver: TRef[Int],
* sender: TRef[Int], much: Int): UIO[Int] =
* STM.atomically {
* for {
* balance <- sender.get
* _ <- STM.check(balance >= much)
* _ <- receiver.update(_ + much)
* _ <- sender.update(_ - much)
* newAmnt <- receiver.get
* } yield newAmnt
* }
*
* val action: UIO[Int] =
* for {
* t <- STM.atomically(TRef.make(0).zip(TRef.make(20000)))
* (receiver, sender) = t
* balance <- transfer(receiver, sender, 1000)
* } yield balance
* }}}
*
* Software Transactional Memory is a technique which allows composition of
* arbitrary atomic operations. It is the software analog of transactions in
* database systems.
*
* The API is lifted directly from the Haskell package Control.Concurrent.STM
* although the implementation does not resemble the Haskell one at all.
* [[http://hackage.haskell.org/package/stm-2.5.0.0/docs/Control-Concurrent-STM.html]]
*
* STM in Haskell was introduced in: Composable memory transactions, by Tim
* Harris, Simon Marlow, Simon Peyton Jones, and Maurice Herlihy, in ACM
* Conference on Principles and Practice of Parallel Programming 2005.
* [[https://www.microsoft.com/en-us/research/publication/composable-memory-transactions/]]
*
* See also: Lock Free Data Structures using STMs in Haskell, by Anthony
* Discolo, Tim Harris, Simon Marlow, Simon Peyton Jones, Satnam Singh) FLOPS
* 2006: Eighth International Symposium on Functional and Logic Programming,
* Fuji Susono, JAPAN, April 2006
* [[https://www.microsoft.com/en-us/research/publication/lock-free-data-structures-using-stms-in-haskell/]]
*/
sealed trait ZSTM[-R, +E, +A] extends Serializable { self =>
import ZSTM._
import ZSTM.internal.{Journal, TExit}
/**
* A symbolic alias for `orDie`.
*/
final def !(implicit ev: E <:< Throwable, ev2: CanFail[E]): ZSTM[R, Nothing, A] =
self.orDie
/**
* Sequentially zips this value with the specified one, discarding the first
* element of the tuple.
*/
def *>[R1 <: R, E1 >: E, B](that: => ZSTM[R1, E1, B]): ZSTM[R1, E1, B] =
self zipRight that
/**
* Sequentially zips this value with the specified one, discarding the second
* element of the tuple.
*/
def <*[R1 <: R, E1 >: E, B](that: => ZSTM[R1, E1, B]): ZSTM[R1, E1, A] =
self zipLeft that
/**
* Sequentially zips this value with the specified one.
*/
def <*>[R1 <: R, E1 >: E, B](that: => ZSTM[R1, E1, B])(implicit
zippable: Zippable[A, B]
): ZSTM[R1, E1, zippable.Out] =
self zip that
/**
* A symbolic alias for `orElseEither`.
*/
def <+>[R1 <: R, E1, B](that: => ZSTM[R1, E1, B]): ZSTM[R1, E1, Either[A, B]] =
self.orElseEither(that)
/**
* Tries this effect first, and if it fails or retries, tries the other
* effect.
*/
def <>[R1 <: R, E1, A1 >: A](that: => ZSTM[R1, E1, A1]): ZSTM[R1, E1, A1] =
orElse(that)
/**
* Tries this effect first, and if it enters retry, then it tries the other
* effect. This is an equivalent of haskell's orElse.
*/
def <|>[R1 <: R, E1 >: E, A1 >: A](that: => ZSTM[R1, E1, A1]): ZSTM[R1, E1, A1] =
orTry(that)
/**
* Returns an effect that submerges the error case of an `Either` into the
* `STM`. The inverse operation of `STM.either`.
*/
def absolve[E1 >: E, B](implicit ev: A <:< Either[E1, B]): ZSTM[R, E1, B] =
ZSTM.absolve(self.map(ev))
/**
* Maps the success value of this effect to the specified constant value.
*/
def as[B](b: => B): ZSTM[R, E, B] = self map (_ => b)
/**
* Maps the success value of this effect to an optional value.
*/
def asSome: ZSTM[R, E, Option[A]] =
map(Some(_))
/**
* Maps the error value of this effect to an optional value.
*/
def asSomeError: ZSTM[R, Option[E], A] =
mapError(Some(_))
/**
* Recovers from all errors.
*/
def catchAll[R1 <: R, E2, A1 >: A](h: E => ZSTM[R1, E2, A1])(implicit ev: CanFail[E]): ZSTM[R1, E2, A1] =
OnFailure(self, h)
/**
* Recovers from some or all of the error cases.
*/
def catchSome[R1 <: R, E1 >: E, A1 >: A](
pf: PartialFunction[E, ZSTM[R1, E1, A1]]
)(implicit ev: CanFail[E]): ZSTM[R1, E1, A1] =
catchAll(pf.applyOrElse(_, (e: E) => ZSTM.fail(e)))
/**
* Simultaneously filters and maps the value produced by this effect.
*/
def collect[B](pf: PartialFunction[A, B]): ZSTM[R, E, B] =
collectSTM(pf.andThen(ZSTM.succeedNow(_)))
/**
* Simultaneously filters and flatMaps the value produced by this effect.
* Continues on the effect returned from pf.
*/
def collectSTM[R1 <: R, E1 >: E, B](pf: PartialFunction[A, ZSTM[R1, E1, B]]): ZSTM[R1, E1, B] =
foldSTM(ZSTM.fail(_), (a: A) => if (pf.isDefinedAt(a)) pf(a) else ZSTM.retry)
/**
* Commits this transaction atomically.
*/
def commit(implicit trace: Trace): ZIO[R, E, A] = ZSTM.atomically(self)
/**
* Commits this transaction atomically, regardless of whether the transaction
* is a success or a failure.
*/
def commitEither(implicit trace: Trace): ZIO[R, E, A] =
either.commit.absolve
/**
* Repeats this `STM` effect until its result satisfies the specified
* predicate. '''WARNING''': `repeatUntil` uses a busy loop to repeat the
* effect and will consume a thread until it completes (it cannot yield). This
* is because STM describes a single atomic transaction which must either
* complete, retry or fail a transaction before yielding back to the ZIO
* Runtime.
* - Use [[retryUntil]] instead if you don't need to maintain transaction
* state for repeats.
* - Ensure repeating the STM effect will eventually satisfy the predicate.
* - Consider using the Blocking thread pool for execution of the
* transaction.
*/
def repeatUntil(f: A => Boolean): ZSTM[R, E, A] =
flatMap(a => if (f(a)) ZSTM.succeedNow(a) else repeatUntil(f))
/**
* Repeats this `STM` effect while its result satisfies the specified
* predicate. '''WARNING''': `repeatWhile` uses a busy loop to repeat the
* effect and will consume a thread until it completes (it cannot yield). This
* is because STM describes a single atomic transaction which must either
* complete, retry or fail a transaction before yielding back to the ZIO
* Runtime.
* - Use [[retryWhile]] instead if you don't need to maintain transaction
* state for repeats.
* - Ensure repeating the STM effect will eventually not satisfy the
* predicate.
* - Consider using the Blocking thread pool for execution of the
* transaction.
*/
def repeatWhile(f: A => Boolean): ZSTM[R, E, A] =
flatMap(a => if (f(a)) repeatWhile(f) else ZSTM.succeedNow(a))
/**
* Converts the failure channel into an `Either`.
*/
def either(implicit ev: CanFail[E]): URSTM[R, Either[E, A]] =
fold(Left(_), Right(_))
/**
* Executes the specified finalization transaction whether or not this effect
* succeeds. Note that as with all STM transactions, if the full transaction
* fails, everything will be rolled back.
*/
def ensuring[R1 <: R](finalizer: ZSTM[R1, Nothing, Any]): ZSTM[R1, E, A] =
foldSTM(e => finalizer *> ZSTM.fail(e), a => finalizer *> ZSTM.succeedNow(a))
/**
* Returns an effect that ignores errors and runs repeatedly until it
* eventually succeeds.
*/
def eventually(implicit ev: CanFail[E]): URSTM[R, A] =
foldSTM(_ => eventually, ZSTM.succeedNow)
/**
* Dies with specified `Throwable` if the predicate fails.
*/
def filterOrDie(p: A => Boolean)(t: => Throwable): ZSTM[R, E, A] =
filterOrElse(p)(ZSTM.die(t))
/**
* Dies with a [[java.lang.RuntimeException]] having the specified text
* message if the predicate fails.
*/
def filterOrDieMessage(p: A => Boolean)(msg: => String): ZSTM[R, E, A] =
filterOrElse(p)(ZSTM.dieMessage(msg))
/**
* Supplies `zstm` if the predicate fails.
*/
def filterOrElse[R1 <: R, E1 >: E, A1 >: A](p: A => Boolean)(zstm: => ZSTM[R1, E1, A1]): ZSTM[R1, E1, A1] =
filterOrElseWith[R1, E1, A1](p)(_ => zstm)
/**
* Applies `f` if the predicate fails.
*/
def filterOrElseWith[R1 <: R, E1 >: E, A1 >: A](p: A => Boolean)(f: A => ZSTM[R1, E1, A1]): ZSTM[R1, E1, A1] =
flatMap(v => if (!p(v)) f(v) else ZSTM.succeedNow(v))
/**
* Fails with `e` if the predicate fails.
*/
def filterOrFail[E1 >: E](p: A => Boolean)(e: => E1): ZSTM[R, E1, A] =
filterOrElse[R, E1, A](p)(ZSTM.fail(e))
/**
* Feeds the value produced by this effect to the specified function, and then
* runs the returned effect as well to produce its results.
*/
def flatMap[R1 <: R, E1 >: E, B](f: A => ZSTM[R1, E1, B]): ZSTM[R1, E1, B] =
OnSuccess(self, f)
/**
* Creates a composite effect that represents this effect followed by another
* one that may depend on the error produced by this one.
*/
def flatMapError[R1 <: R, E2](f: E => ZSTM[R1, Nothing, E2])(implicit ev: CanFail[E]): ZSTM[R1, E2, A] =
foldSTM(e => f(e).flip, ZSTM.succeedNow)
/**
* Flattens out a nested `STM` effect.
*/
def flatten[R1 <: R, E1 >: E, B](implicit ev: A <:< ZSTM[R1, E1, B]): ZSTM[R1, E1, B] =
self flatMap ev
/**
* Unwraps the optional error, defaulting to the provided value.
*/
def flattenErrorOption[E1, E2 <: E1](default: => E2)(implicit ev: E <:< Option[E1]): ZSTM[R, E1, A] =
mapError(e => ev(e).getOrElse(default))
/**
* Flips the success and failure channels of this transactional effect. This
* allows you to use all methods on the error channel, possibly before
* flipping back.
*/
def flip: ZSTM[R, A, E] =
foldSTM(ZSTM.succeedNow, ZSTM.fail(_))
/**
* Swaps the error/value parameters, applies the function `f` and flips the
* parameters back
*/
def flipWith[R1, A1, E1](f: ZSTM[R, A, E] => ZSTM[R1, A1, E1]): ZSTM[R1, E1, A1] =
f(flip).flip
/**
* Folds over the `STM` effect, handling both failure and success, but not
* retry.
*/
def fold[B](f: E => B, g: A => B)(implicit ev: CanFail[E]): URSTM[R, B] =
foldSTM(f andThen ZSTM.succeedNow, g andThen ZSTM.succeedNow)
/**
* Effectfully folds over the `STM` effect, handling both failure and success.
*/
def foldSTM[R1 <: R, E1, B](f: E => ZSTM[R1, E1, B], g: A => ZSTM[R1, E1, B])(implicit
ev: CanFail[E]
): ZSTM[R1, E1, B] =
self
.map(Right(_))
.catchAll(f(_).map(Left(_)))
.flatMap {
case Left(b) => ZSTM.succeedNow(b)
case Right(a) => g(a)
}
/**
* Returns a successful effect with the head of the list if the list is
* non-empty or fails with the error `None` if the list is empty.
*/
def head[B](implicit ev: A <:< List[B]): ZSTM[R, Option[E], B] =
foldSTM(
e => ZSTM.fail(Some(e)),
ev(_).headOption.fold[ZSTM[R, Option[E], B]](ZSTM.fail(None))(ZSTM.succeedNow)
)
/**
* Returns a new effect that ignores the success or failure of this effect.
*/
def ignore: URSTM[R, Unit] = self.fold(ZIO.unitFn, ZIO.unitFn)
/**
* Returns whether this transactional effect is a failure.
*/
def isFailure: ZSTM[R, Nothing, Boolean] =
fold(_ => true, _ => false)
/**
* Returns whether this transactional effect is a success.
*/
def isSuccess: ZSTM[R, Nothing, Boolean] =
fold(_ => false, _ => true)
/**
* "Zooms in" on the value in the `Left` side of an `Either`, moving the
* possibility that the value is a `Right` to the error channel.
*/
def left[B, C](implicit ev: A IsSubtypeOfOutput Either[B, C]): ZSTM[R, Either[E, C], B] =
self.foldSTM(
e => ZSTM.fail(Left(e)),
a => ev(a).fold(b => ZSTM.succeedNow(b), c => ZSTM.fail(Right(c)))
)
/**
* Maps the value produced by the effect.
*/
def map[B](f: A => B): ZSTM[R, E, B] = flatMap(f andThen ZSTM.succeedNow)
/**
* Maps the value produced by the effect with the specified function that may
* throw exceptions but is otherwise pure, translating any thrown exceptions
* into typed failed effects.
*/
def mapAttempt[B](f: A => B)(implicit ev: E IsSubtypeOfError Throwable): ZSTM[R, Throwable, B] =
foldSTM(e => ZSTM.fail(ev(e)), a => ZSTM.attempt(f(a)))
/**
* Returns an `STM` effect whose failure and success channels have been mapped
* by the specified pair of functions, `f` and `g`.
*/
def mapBoth[E2, B](f: E => E2, g: A => B)(implicit ev: CanFail[E]): ZSTM[R, E2, B] =
foldSTM(e => ZSTM.fail(f(e)), a => ZSTM.succeedNow(g(a)))
/**
* Maps from one error type to another.
*/
def mapError[E1](f: E => E1)(implicit ev: CanFail[E]): ZSTM[R, E1, A] =
foldSTM(e => ZSTM.fail(f(e)), ZSTM.succeedNow)
/**
* Returns a new effect where the error channel has been merged into the
* success channel to their common combined type.
*/
def merge[A1 >: A](implicit ev1: E <:< A1, ev2: CanFail[E]): URSTM[R, A1] =
foldSTM(e => ZSTM.succeedNow(ev1(e)), ZSTM.succeedNow)
/**
* Requires the option produced by this value to be `None`.
*/
def none[B](implicit ev: A <:< Option[B]): ZSTM[R, Option[E], Unit] =
self.foldSTM(
e => ZSTM.fail(Some(e)),
_.fold[ZSTM[R, Option[E], Unit]](ZSTM.unit)(_ => ZSTM.fail(None))
)
/**
* Converts the failure channel into an `Option`.
*/
def option(implicit ev: CanFail[E]): URSTM[R, Option[A]] =
fold(_ => None, Some(_))
/**
* Translates `STM` effect failure into death of the fiber, making all
* failures unchecked and not a part of the type of the effect.
*/
def orDie(implicit ev1: E IsSubtypeOfError Throwable, ev2: CanFail[E]): URSTM[R, A] =
orDieWith(ev1)
/**
* Keeps none of the errors, and terminates the fiber running the `STM` effect
* with them, using the specified function to convert the `E` into a
* `Throwable`.
*/
def orDieWith(f: E => Throwable)(implicit ev: CanFail[E]): URSTM[R, A] =
mapError(f).catchAll(ZSTM.die(_))
/**
* Named alias for `<>`.
*/
def orElse[R1 <: R, E1, A1 >: A](that: => ZSTM[R1, E1, A1]): ZSTM[R1, E1, A1] =
Effect[Any, Nothing, () => Unit]((journal, _, _) => journal.resetFn()).flatMap { reset =>
self.orTry(ZSTM.succeed(reset()) *> that).catchAll(_ => ZSTM.succeed(reset()) *> that)
}
/**
* Returns a transactional effect that will produce the value of this effect
* in left side, unless it fails or retries, in which case, it will produce
* the value of the specified effect in right side.
*/
def orElseEither[R1 <: R, E1, B](that: => ZSTM[R1, E1, B]): ZSTM[R1, E1, Either[A, B]] =
(self map (Left[A, B](_))) orElse (that map (Right[A, B](_)))
/**
* Tries this effect first, and if it fails or retries, fails with the
* specified error.
*/
def orElseFail[E1](e1: => E1): ZSTM[R, E1, A] =
orElse(ZSTM.fail(e1))
/**
* Returns an effect that will produce the value of this effect, unless it
* fails with the `None` value, in which case it will produce the value of the
* specified effect.
*/
def orElseOptional[R1 <: R, E1, A1 >: A](that: => ZSTM[R1, Option[E1], A1])(implicit
ev: E <:< Option[E1]
): ZSTM[R1, Option[E1], A1] =
catchAll(ev(_).fold(that)(e => ZSTM.fail(Some(e))))
/**
* Tries this effect first, and if it fails or retries, succeeds with the
* specified value.
*/
def orElseSucceed[A1 >: A](a1: => A1): URSTM[R, A1] =
orElse(ZSTM.succeedNow(a1))
/**
* Named alias for `<|>`.
*/
def orTry[R1 <: R, E1 >: E, A1 >: A](that: => ZSTM[R1, E1, A1]): ZSTM[R1, E1, A1] =
OnRetry(self, that)
/**
* Provides the transaction its required environment, which eliminates its
* dependency on `R`.
*/
def provideEnvironment(r: ZEnvironment[R]): STM[E, A] =
provideSomeEnvironment(_ => r)
/**
* Transforms the environment being provided to this effect with the specified
* function.
*/
def provideSomeEnvironment[R0](f: ZEnvironment[R0] => ZEnvironment[R]): ZSTM[R0, E, A] =
Provide(self, f)
/**
* Keeps some of the errors, and terminates the fiber with the rest.
*/
def refineOrDie[E1](
pf: PartialFunction[E, E1]
)(implicit ev1: E IsSubtypeOfError Throwable, ev2: CanFail[E]): ZSTM[R, E1, A] =
refineOrDieWith(pf)(ev1)
/**
* Keeps some of the errors, and terminates the fiber with the rest, using the
* specified function to convert the `E` into a `Throwable`.
*/
def refineOrDieWith[E1](pf: PartialFunction[E, E1])(f: E => Throwable)(implicit ev: CanFail[E]): ZSTM[R, E1, A] =
self.catchAll(err => (pf.lift(err)).fold[ZSTM[R, E1, A]](ZSTM.die(f(err)))(ZSTM.fail(_)))
/**
* Fail with the returned value if the `PartialFunction` matches, otherwise
* continue with our held value.
*/
def reject[E1 >: E](pf: PartialFunction[A, E1]): ZSTM[R, E1, A] =
rejectSTM(pf.andThen(ZSTM.fail(_)))
/**
* Continue with the returned computation if the `PartialFunction` matches,
* translating the successful match into a failure, otherwise continue with
* our held value.
*/
def rejectSTM[R1 <: R, E1 >: E](pf: PartialFunction[A, ZSTM[R1, E1, E1]]): ZSTM[R1, E1, A] =
self.flatMap { v =>
pf.andThen[ZSTM[R1, E1, A]](_.flatMap(ZSTM.fail(_)))
.applyOrElse[A, ZSTM[R1, E1, A]](v, ZSTM.succeedNow)
}
/**
* Filters the value produced by this effect, retrying the transaction until
* the predicate returns true for the value.
*/
def retryUntil(f: A => Boolean): ZSTM[R, E, A] =
collect {
case a if f(a) => a
}
/**
* Filters the value produced by this effect, retrying the transaction while
* the predicate returns true for the value.
*/
def retryWhile(f: A => Boolean): ZSTM[R, E, A] =
collect {
case a if !f(a) => a
}
/**
* "Zooms in" on the value in the `Right` side of an `Either`, moving the
* possibility that the value is a `Left` to the error channel.
*/
final def right[B, C](implicit ev: A IsSubtypeOfOutput Either[B, C]): ZSTM[R, Either[B, E], C] =
self.foldSTM(
e => ZSTM.fail(Right(e)),
a => ev(a).fold(b => ZSTM.fail(Left(b)), c => ZSTM.succeedNow(c))
)
/**
* Converts an option on values into an option on errors.
*/
def some[B](implicit ev: A <:< Option[B]): ZSTM[R, Option[E], B] =
self.foldSTM(
e => ZSTM.fail(Some(e)),
_.fold[ZSTM[R, Option[E], B]](ZSTM.fail(Option.empty[E]))(ZSTM.succeedNow)
)
/**
* Extracts the optional value, or returns the given 'default'. Superseded by
* `someOrElse` with better type inference. This method was left for binary
* compatibility.
*/
protected def someOrElse[B](default: => B)(implicit ev: A <:< Option[B]): ZSTM[R, E, B] =
map(_.getOrElse(default))
/**
* Extracts the optional value, or returns the given 'default'.
*/
def someOrElse[B, C](default: => C)(implicit ev0: A <:< Option[B], ev1: C <:< B): ZSTM[R, E, B] =
map(_.getOrElse(default))
/**
* Extracts the optional value, or executes the effect 'default'. Superseded
* by `someOrElseSTM` with better type inference. This method was left for
* binary compatibility.
*/
protected def someOrElseSTM[B, R1 <: R, E1 >: E](
default: ZSTM[R1, E1, B]
)(implicit ev: A <:< Option[B]): ZSTM[R1, E1, B] =
self.flatMap(ev(_) match {
case Some(value) => ZSTM.succeedNow(value)
case None => default
})
/**
* Extracts the optional value, or executes the effect 'default'.
*/
def someOrElseSTM[B, R1 <: R, E1 >: E, C](
default: ZSTM[R1, E1, C]
)(implicit ev0: A <:< Option[B], ev1: C <:< B): ZSTM[R1, E1, B] =
self.flatMap(ev0(_) match {
case Some(value) => ZSTM.succeedNow(value)
case None => default.map(ev1)
})
/**
* Extracts the optional value, or fails with the given error 'e'.
*/
def someOrFail[B, E1 >: E](e: => E1)(implicit ev: A <:< Option[B]): ZSTM[R, E1, B] =
flatMap(_.fold[ZSTM[R, E1, B]](ZSTM.fail(e))(ZSTM.succeedNow))
/**
* Extracts the optional value, or fails with a
* [[java.util.NoSuchElementException]]
*/
def someOrFailException[B, E1 >: E](implicit
ev: A <:< Option[B],
ev2: NoSuchElementException <:< E1
): ZSTM[R, E1, B] =
foldSTM(
ZSTM.fail(_),
_.fold[ZSTM[R, E1, B]](ZSTM.fail(ev2(new NoSuchElementException("None.get"))))(ZSTM.succeedNow)
)
/**
* Summarizes a `STM` effect by computing a provided value before and after
* execution, and then combining the values to produce a summary, together
* with the result of execution.
*/
def summarized[R1 <: R, E1 >: E, B, C](summary: ZSTM[R1, E1, B])(f: (B, B) => C): ZSTM[R1, E1, (C, A)] =
for {
start <- summary
value <- self
end <- summary
} yield (f(start, end), value)
@deprecated("use pattern-matching on types instead", "2.1.8")
def tag: Int
/**
* "Peeks" at the success of transactional effect.
*/
def tap[R1 <: R, E1 >: E](f: A => ZSTM[R1, E1, Any]): ZSTM[R1, E1, A] =
flatMap(a => f(a).as(a))
/**
* "Peeks" at both sides of an transactional effect.
*/
def tapBoth[R1 <: R, E1 >: E](f: E => ZSTM[R1, E1, Any], g: A => ZSTM[R1, E1, Any])(implicit
ev: CanFail[E]
): ZSTM[R1, E1, A] =
foldSTM(e => f(e) *> ZSTM.fail(e), a => g(a) as a)
/**
* "Peeks" at the error of the transactional effect.
*/
def tapError[R1 <: R, E1 >: E](f: E => ZSTM[R1, E1, Any])(implicit ev: CanFail[E]): ZSTM[R1, E1, A] =
foldSTM(e => f(e) *> ZSTM.fail(e), ZSTM.succeedNow)
/**
* Maps the success value of this effect to unit.
*/
def unit: ZSTM[R, E, Unit] = as(())
/**
* The moral equivalent of `if (!p) exp`
*/
def unless(b: => Boolean): ZSTM[R, E, Option[A]] =
ZSTM.unless(b)(self)
/**
* The moral equivalent of `if (!p) exp` when `p` has side-effects
*/
def unlessSTM[R1 <: R, E1 >: E](b: ZSTM[R1, E1, Boolean]): ZSTM[R1, E1, Option[A]] =
ZSTM.unlessSTM(b)(self)
/**
* Converts a `ZSTM[R, Either[E, B], A]` into a `ZSTM[R, E, Either[A, B]]`.
* The inverse of `left`.
*/
final def unleft[E1, B](implicit ev: E IsSubtypeOfError Either[E1, B]): ZSTM[R, E1, Either[A, B]] =
self.foldSTM(
e => ev(e).fold(e1 => ZSTM.fail(e1), b => ZSTM.succeedNow(Right(b))),
a => ZSTM.succeedNow(Left(a))
)
/**
* Converts a `ZSTM[R, Either[B, E], A]` into a `ZSTM[R, E, Either[B, A]]`.
* The inverse of `right`.
*/
final def unright[E1, B](implicit ev: E IsSubtypeOfError Either[B, E1]): ZSTM[R, E1, Either[B, A]] =
self.foldSTM(
e => ev(e).fold(b => ZSTM.succeedNow(Left(b)), e1 => ZSTM.fail(e1)),
a => ZSTM.succeedNow(Right(a))
)
/**
* Converts an option on errors into an option on values.
*/
def unsome[E1](implicit ev: E <:< Option[E1]): ZSTM[R, E1, Option[A]] =
foldSTM(
_.fold[ZSTM[R, E1, Option[A]]](ZSTM.succeedNow(Option.empty[A]))(ZSTM.fail(_)),
a => ZSTM.succeedNow(Some(a))
)
/**
* Updates a service in the environment of this effect.
*/
def updateService[M] =
new ZSTM.UpdateService[R, E, A, M](self)
/**
* Updates a service at the specified key in the environment of this effect.
*/
final def updateServiceAt[Service]: ZSTM.UpdateServiceAt[R, E, A, Service] =
new ZSTM.UpdateServiceAt[R, E, A, Service](self)
/**
* The moral equivalent of `if (p) exp`
*/
def when(b: => Boolean): ZSTM[R, E, Option[A]] =
ZSTM.when(b)(self)
/**
* The moral equivalent of `if (p) exp` when `p` has side-effects
*/
def whenSTM[R1 <: R, E1 >: E](b: ZSTM[R1, E1, Boolean]): ZSTM[R1, E1, Option[A]] =
ZSTM.whenSTM(b)(self)
/**
* Same as [[retryUntil]].
*/
def withFilter(f: A => Boolean): ZSTM[R, E, A] = retryUntil(f)
/**
* Named alias for `<*>`.
*/
def zip[R1 <: R, E1 >: E, B](that: => ZSTM[R1, E1, B])(implicit
zippable: Zippable[A, B]
): ZSTM[R1, E1, zippable.Out] =
(self zipWith that)((a, b) => zippable.zip(a, b))
/**
* Named alias for `<*`.
*/
def zipLeft[R1 <: R, E1 >: E, B](that: => ZSTM[R1, E1, B]): ZSTM[R1, E1, A] =
(self zip that) map (_._1)
/**
* Named alias for `*>`.
*/
def zipRight[R1 <: R, E1 >: E, B](that: => ZSTM[R1, E1, B]): ZSTM[R1, E1, B] =
(self zip that) map (_._2)
/**
* Sequentially zips this value with the specified one, combining the values
* using the specified combiner function.
*/
def zipWith[R1 <: R, E1 >: E, B, C](that: => ZSTM[R1, E1, B])(f: (A, B) => C): ZSTM[R1, E1, C] =
self flatMap (a => that map (b => f(a, b)))
private def run(journal: Journal, fiberId: FiberId, r0: ZEnvironment[R]): TExit[E, A] = {
import internal._
type Erased = ZSTM[Any, Any, Any]
type Cont = Any => Erased
var contStack = List.empty[Cont]
var onCommitStack = List.empty[ZIO[Any, Nothing, Any]]
var env = r0.asInstanceOf[ZEnvironment[Any]]
var exit = null.asInstanceOf[TExit[Any, Any]]
var curr = self.asInstanceOf[Erased]
var opCount = 0
def unwindStack(stack: List[Cont], error: Any, isRetry: Boolean): (List[Cont], Erased) = {
var result = null.asInstanceOf[Erased]
var rem = stack
while ((rem ne Nil) && (result eq null)) {
rem.head match {
case OnFailure(_, onFailure) => if (!isRetry) result = onFailure.asInstanceOf[Cont].apply(error)
case OnRetry(_, onRetry) => if (isRetry) result = onRetry
case _ =>
}
rem = rem.tail
}
(rem, result)
}
while (exit eq null) {
if (opCount == YieldOpCount) {
if (journal.isInvalid) exit = TExit.Retry
else opCount = 0
} else {
try {
curr match {
case effect: Effect[Any, Any, Any] =>
val a = effect.f(journal, fiberId, env)
if (contStack eq Nil) exit = TExit.Succeed(a, onCommitStack)
else {
curr = contStack.head(a)
contStack = contStack.tail
}
case onSuccess: OnSuccess[Any, Any, Any, Any] =>
contStack ::= onSuccess.k
curr = onSuccess.stm
case onFailure: OnFailure[Any, Any, Any, Any] =>
contStack ::= onFailure
curr = onFailure.stm
case onRetry: OnRetry[Any, Any, Any] =>
contStack ::= onRetry
curr = onRetry.stm
case provide: Provide[Any, Any, Any, Any] =>
val oldEnv = env
env = provide.f(oldEnv)
val cleanup = ZSTM.succeed({ env = oldEnv })
curr = provide.effect.ensuring(cleanup)
case succeedNow0: SucceedNow[Any] =>
val a = succeedNow0.a
if (contStack eq Nil) exit = TExit.Succeed(a, onCommitStack)
else {
curr = contStack.head(a)
contStack = contStack.tail
}
case succeed0: Succeed[Any] =>
val a = succeed0.a()
if (contStack eq Nil) exit = TExit.Succeed(a, onCommitStack)
else {
curr = contStack.head(a)
contStack = contStack.tail
}
case onCommit: OnCommit[Any] =>
onCommitStack ::= onCommit.zio.provideEnvironment(env)(onCommit.trace)
if (contStack eq Nil) exit = TExit.Succeed((), onCommitStack)
else {
curr = contStack.head(())
contStack = contStack.tail
}
}
} catch {
case ZSTM.RetryException =>
val (newStack, newCurr) = unwindStack(contStack, null, true)
curr = newCurr
contStack = newStack
if (curr eq null) exit = TExit.Retry
case ZSTM.FailException(e) =>
val (newStack, newCurr) = unwindStack(contStack, e, false)
curr = newCurr
contStack = newStack
if (curr eq null) exit = TExit.Fail(e, onCommitStack)
case ZSTM.DieException(t) =>
exit = TExit.Die(t, onCommitStack)
case ZSTM.InterruptException(fiberId) =>
exit = TExit.Interrupt(fiberId, onCommitStack)
case t: Throwable =>
exit = TExit.Die(t, onCommitStack)
}
opCount += 1
}
}
exit.asInstanceOf[TExit[E, A]]
}
}
object ZSTM {
import internal._
/**
* Submerges the error case of an `Either` into the `STM`. The inverse
* operation of `STM.either`.
*/
def absolve[R, E, A](z: ZSTM[R, E, Either[E, A]]): ZSTM[R, E, A] =
z.flatMap(fromEither(_))
/**
* Treats the specified `acquire` transaction as the acquisition of a
* resource. The `acquire` transaction will be executed interruptibly. If it
* is a success and is committed the specified `release` workflow will be
* executed uninterruptibly as soon as the `use` workflow completes execution.
*/
def acquireReleaseWith[R, E, A](acquire: ZSTM[R, E, A]): ZSTM.Acquire[R, E, A] =
new ZSTM.Acquire[R, E, A](() => acquire)
/**
* Atomically performs a batch of operations in a single transaction.
*/
def atomically[R, E, A](stm: ZSTM[R, E, A])(implicit trace: Trace): ZIO[R, E, A] =
unsafeAtomically(stm)(_ => (), () => ())
private def commitEffects(onCommit: List[UIO[Any]])(implicit trace: Trace): UIO[Any] =
onCommit match {
case Nil => Exit.unit
case one :: Nil => one
case many =>
val it = many.reverseIterator
ZIO.whileLoop(it.hasNext)(it.next())(_ => ())
}
private def unsafeAtomically[R, E, A](
stm: ZSTM[R, E, A]
)(onDone: Exit[E, A] => Any, onInterrupt: () => Any)(implicit trace: Trace): ZIO[R, E, A] =
ZIO.withFiberRuntime[R, E, A] { (fiberState, _) =>
implicit val unsafe: Unsafe = Unsafe
val executor = fiberState.getCurrentExecutor()
val r = fiberState.getFiberRef(FiberRef.currentEnvironment).asInstanceOf[ZEnvironment[R]]
val fiberId = fiberState.id
tryCommitSync(fiberId, stm, null, r, executor) match {
case TryCommit.Done(exit, onCommit) =>
onDone(exit)
commitEffects(onCommit) *> exit
case TryCommit.Suspend(journal) =>
val txId = TxnId.make()
val state = new AtomicReference[State[E, A]](State.Running)
val async = ZIO.async(tryCommitAsync(journal, executor, fiberId, stm, txId, state, r))
ZIO.uninterruptibleMask { restore =>
restore(async).foldCauseZIO(
cause => {
state.compareAndSet(State.Running, State.Interrupted)
state.get match {
case State.Done(exit, onCommit) =>
onDone(exit)
commitEffects(onCommit) *> exit
case _ =>
onInterrupt()
Exit.failCause(cause)
}
},
s => {
val exit = Exit.succeed(s)
onDone(exit)
exit
}
)
}
}
}
/**
* Creates an `STM` value from a partial (but pure) function.
*/
def attempt[A](a: => A): STM[Throwable, A] =
fromTry(Try(a))
/**
* Checks the condition, and if it's true, returns unit, otherwise, retries.
*/
def check[R](p: => Boolean): URSTM[R, Unit] =
suspend(if (p) STM.unit else retry)
/**
* Evaluate each effect in the structure from left to right, collecting the
* successful values and discarding the empty cases.
*/
def collect[R, E, A, B, Collection[+Element] <: Iterable[Element]](
in: Collection[A]
)(f: A => ZSTM[R, Option[E], B])(implicit bf: BuildFrom[Collection[A], B, Collection[B]]): ZSTM[R, E, Collection[B]] =
foreach[R, E, A, Option[B], Iterable](in)(a => f(a).unsome).map(_.flatten).map(bf.fromSpecific(in))
/**
* Collects all the transactional effects in a collection, returning a single
* transactional effect that produces a collection of values.
*/
def collectAll[R, E, A, Collection[+Element] <: Iterable[Element]](
in: Collection[ZSTM[R, E, A]]
)(implicit bf: BuildFrom[Collection[ZSTM[R, E, A]], A, Collection[A]]): ZSTM[R, E, Collection[A]] =
foreach(in)(ZIO.identityFn)
/**
* Collects all the transactional effects in a set, returning a single
* transactional effect that produces a set of values.
*/
def collectAll[R, E, A](in: Set[ZSTM[R, E, A]]): ZSTM[R, E, Set[A]] =
foreach(in)(ZIO.identityFn)
/**
* Collects all the transactional effects, returning a single transactional
* effect that produces `Unit`.
*
* Equivalent to `collectAll(i).unit`, but without the cost of building the
* list of results.
*/
def collectAllDiscard[R, E, A](in: Iterable[ZSTM[R, E, A]]): ZSTM[R, E, Unit] =
foreachDiscard(in)(ZIO.identityFn)
/**
* Collects the first element of the `Iterable[A]` for which the effectual
* function `f` returns `Some`.
*/
def collectFirst[R, E, A, B](as: Iterable[A])(f: A => ZSTM[R, E, Option[B]]): ZSTM[R, E, Option[B]] =
succeedNow(as.iterator).flatMap { iterator =>
def loop: ZSTM[R, E, Option[B]] =
if (iterator.hasNext) f(iterator.next()).flatMap(_.fold(loop)(some(_)))
else none
loop
}
/**
* Similar to Either.cond, evaluate the predicate, return the given A as
* success if predicate returns true, and the given E as error otherwise
*/
def cond[E, A](predicate: Boolean, result: => A, error: => E): STM[E, A] =
if (predicate) succeed(result) else fail(error)
/**
* Kills the fiber running the effect.
*/
def die(t: => Throwable): USTM[Nothing] =
Effect((_, _, _) => throw DieException(t))
/**
* Kills the fiber running the effect with a `RuntimeException` that contains
* the specified message.
*/
def dieMessage(m: => String): USTM[Nothing] =
die(new RuntimeException(m))
/**
* Returns a value modelled on provided exit status.
*/
def done[R, E, A](exit: => TExit[E, A]): ZSTM[R, E, A] =
suspend(done(exit))
/**
* Retrieves the environment inside an stm.
*/
def environment[R]: URSTM[R, ZEnvironment[R]] = Effect((_, _, r) => r)
/**
* Accesses the environment of the transaction to perform a transaction.
*/
def environmentWith[R]: EnvironmentWithPartiallyApplied[R] =
new EnvironmentWithPartiallyApplied
/**
* Accesses the environment of the transaction to perform a transaction.
*/
def environmentWithSTM[R]: EnvironmentWithSTMPartiallyApplied[R] =
new EnvironmentWithSTMPartiallyApplied
/**
* Determines whether any element of the `Iterable[A]` satisfies the effectual
* predicate `f`.
*/
def exists[R, E, A](as: Iterable[A])(f: A => ZSTM[R, E, Boolean]): ZSTM[R, E, Boolean] =
succeedNow(as.iterator).flatMap { iterator =>
def loop: ZSTM[R, E, Boolean] =
if (iterator.hasNext) f(iterator.next()).flatMap(b => if (b) succeedNow(b) else loop)
else succeedNow(false)
loop
}
/**
* Returns a value that models failure in the transaction.
*/
def fail[E](e: => E): STM[E, Nothing] = Effect((_, _, _) => throw FailException(e))
/**
* Returns the fiber id of the fiber committing the transaction.
*/
val fiberId: USTM[FiberId] = Effect((_, fiberId, _) => fiberId)
/**
* Filters the collection using the specified effectual predicate.
*/
def filter[R, E, A, Collection[+Element] <: Iterable[Element]](
as: Collection[A]
)(f: A => ZSTM[R, E, Boolean])(implicit bf: BuildFrom[Collection[A], A, Collection[A]]): ZSTM[R, E, Collection[A]] =
ZSTM.suspend {
ZSTM
.foldLeft(as)(bf.newBuilder(as)) { (builder, a) =>
f(a).map(b => if (b) builder += a else builder)
}
.map(_.result())
}
/**
* Filters the set using the specified effectual predicate.
*/
def filter[R, E, A](as: Set[A])(f: A => ZSTM[R, E, Boolean]): ZSTM[R, E, Set[A]] =
filter[R, E, A, Iterable](as)(f).map(_.toSet)
/**
* Filters the collection using the specified effectual predicate, removing
* all elements that satisfy the predicate.
*/
def filterNot[R, E, A, Collection[+Element] <: Iterable[Element]](
as: Collection[A]
)(f: A => ZSTM[R, E, Boolean])(implicit bf: BuildFrom[Collection[A], A, Collection[A]]): ZSTM[R, E, Collection[A]] =
filter(as)(f(_).map(!_))
/**
* Filters the set using the specified effectual predicate, removing all
* elements that satisfy the predicate.
*/
def filterNot[R, E, A](as: Set[A])(f: A => ZSTM[R, E, Boolean]): ZSTM[R, E, Set[A]] =
filterNot[R, E, A, Iterable](as)(f).map(_.toSet)
/**
* Returns an effect that first executes the outer effect, and then executes
* the inner effect, returning the value from the inner effect, and
* effectively flattening a nested effect.
*/
def flatten[R, E, A](tx: ZSTM[R, E, ZSTM[R, E, A]]): ZSTM[R, E, A] =
tx.flatMap(ZIO.identityFn)
/**
* Folds an Iterable[A] using an effectual function f, working sequentially
* from left to right.
*/
def foldLeft[R, E, S, A](
in: Iterable[A]
)(zero: S)(f: (S, A) => ZSTM[R, E, S]): ZSTM[R, E, S] =
ZSTM.suspend {
val iterator = in.iterator
def loop(s: S): ZSTM[R, E, S] =
if (iterator.hasNext) f(s, iterator.next()).flatMap(loop)
else ZSTM.succeed(s)
loop(zero)
}
/**
* Folds an Iterable[A] using an effectual function f, working sequentially
* from right to left.
*/
def foldRight[R, E, S, A](
in: Iterable[A]
)(zero: S)(f: (A, S) => ZSTM[R, E, S]): ZSTM[R, E, S] =
foldLeft(in.toSeq.reverse)(zero)((s, a) => f(a, s))
/**
* Determines whether all elements of the `Iterable[A]` satisfy the effectual
* predicate `f`.
*/
def forall[R, E, A](as: Iterable[A])(f: A => ZSTM[R, E, Boolean]): ZSTM[R, E, Boolean] =
succeedNow(as.iterator).flatMap { iterator =>
def loop: ZSTM[R, E, Boolean] =
if (iterator.hasNext) f(iterator.next()).flatMap(b => if (b) loop else succeedNow(b))
else succeedNow(true)
loop
}
/**
* Applies the function `f` to each element of the `Collection[A]` and returns
* a transactional effect that produces a new `Collection[B]`.
*/
def foreach[R, E, A, B, Collection[+Element] <: Iterable[Element]](
in: Collection[A]
)(f: A => ZSTM[R, E, B])(implicit bf: BuildFrom[Collection[A], B, Collection[B]]): ZSTM[R, E, Collection[B]] =
ZSTM.suspend {
val iterator = in.iterator
val builder = bf.newBuilder(in)
def loop: ZSTM[R, E, Collection[B]] =
if (iterator.hasNext) f(iterator.next()).flatMap { b => builder += b; loop }
else ZSTM.succeed(builder.result())
loop
}
/**
* Applies the function `f` to each element of the `Set[A]` and returns a
* transactional effect that produces a new `Set[B]`.
*/
def foreach[R, E, A, B](in: Set[A])(f: A => ZSTM[R, E, B]): ZSTM[R, E, Set[B]] =
foreach[R, E, A, B, Iterable](in)(f).map(_.toSet)
/**
* Applies the function `f` to each element of the `Iterable[A]` and returns a
* transactional effect that produces `Unit`.
*
* Equivalent to `foreach(as)(f).unit`, but without the cost of building the
* list of results.
*/
def foreachDiscard[R, E, A](in: Iterable[A])(f: A => ZSTM[R, E, Any]): ZSTM[R, E, Unit] =
ZSTM.succeedNow(in.iterator).flatMap[R, E, Unit] { it =>
def loop: ZSTM[R, E, Unit] =
if (it.hasNext) f(it.next()) *> loop
else ZSTM.unit
loop
}
/**
* Lifts an `Either` into a `STM`.
*/
def fromEither[E, A](e: => Either[E, A]): STM[E, A] =
STM.suspend {
e match {
case Left(t) => STM.fail(t)
case Right(a) => STM.succeedNow(a)
}
}
/**
* Lifts an `Option` into a `STM`.
*/
def fromOption[A](v: => Option[A]): STM[Option[Nothing], A] =
STM.suspend(v.fold[STM[Option[Nothing], A]](STM.fail(None))(STM.succeedNow))
/**
* Lifts a `Try` into a `STM`.
*/
def fromTry[A](a: => Try[A]): TaskSTM[A] =
STM.suspend {
a match {
case Failure(t) => STM.fail(t)
case Success(a) => STM.succeedNow(a)
}
}
/**
* Runs `onTrue` if the result of `b` is `true` and `onFalse` otherwise.
*/
def ifSTM[R, E](b: ZSTM[R, E, Boolean]): ZSTM.IfSTM[R, E] =
new ZSTM.IfSTM(b)
/**
* Interrupts the fiber running the effect.
*/
val interrupt: USTM[Nothing] =
ZSTM.fiberId.flatMap(fiberId => interruptAs(fiberId))
/**
* Interrupts the fiber running the effect with the specified fiber id.
*/
def interruptAs(fiberId: => FiberId): USTM[Nothing] =
Effect((_, _, _) => throw InterruptException(fiberId))
/**
* Iterates with the specified transactional function. The moral equivalent
* of:
*
* {{{
* var s = initial
*
* while (cont(s)) {
* s = body(s)
* }
*
* s
* }}}
*/
def iterate[R, E, S](initial: S)(cont: S => Boolean)(body: S => ZSTM[R, E, S]): ZSTM[R, E, S] =
if (cont(initial)) body(initial).flatMap(iterate(_)(cont)(body))
else ZSTM.succeedNow(initial)
/**
* Returns an effect with the value on the left part.
*/
def left[A](a: => A): USTM[Either[A, Nothing]] =
succeed(Left(a))
/**
* Loops with the specified transactional function, collecting the results
* into a list. The moral equivalent of:
*
* {{{
* var s = initial
* var as = List.empty[A]
*
* while (cont(s)) {
* as = body(s) :: as
* s = inc(s)
* }
*
* as.reverse
* }}}
*/
def loop[R, E, A, S](initial: S)(cont: S => Boolean, inc: S => S)(body: S => ZSTM[R, E, A]): ZSTM[R, E, List[A]] =
if (cont(initial))
body(initial).flatMap(a => loop(inc(initial))(cont, inc)(body).map(as => a :: as))
else
ZSTM.succeedNow(List.empty[A])
/**
* Loops with the specified transactional function purely for its
* transactional effects. The moral equivalent of:
*
* {{{
* var s = initial
*
* while (cont(s)) {
* body(s)
* s = inc(s)
* }
* }}}
*/
def loopDiscard[R, E, S](initial: S)(cont: S => Boolean, inc: S => S)(body: S => ZSTM[R, E, Any]): ZSTM[R, E, Unit] =
if (cont(initial)) body(initial) *> loopDiscard(inc(initial))(cont, inc)(body)
else ZSTM.unit
/**
* Merges an `Iterable[ZSTM]` to a single ZSTM, working sequentially.
*/
def mergeAll[R, E, A, B](
in: Iterable[ZSTM[R, E, A]]
)(zero: B)(f: (B, A) => B): ZSTM[R, E, B] =
ZSTM.foldLeft(in)(zero)((b, stm) => stm.map(a => f(b, a)))
/**
* Returns an effect with the empty value.
*/
val none: USTM[Option[Nothing]] = succeedNow(None)
/**
* Executes the specified workflow when this transaction is committed.
*/
def onCommit[R](zio: ZIO[R, Nothing, Any])(implicit trace: Trace): ZSTM[R, Nothing, Unit] =
ZSTM.OnCommit(zio, trace)
/**
* Feeds elements of type `A` to a function `f` that returns an effect.
* Collects all successes and failures in a tupled fashion.
*/
def partition[R, E, A, B](
in: Iterable[A]
)(f: A => ZSTM[R, E, B])(implicit ev: CanFail[E]): ZSTM[R, Nothing, (Iterable[E], Iterable[B])] =
ZSTM.foreach(in)(f(_).either).map(ZIO.partitionMap(_)(ZIO.identityFn))
/**
* Reduces an `Iterable[ZSTM]` to a single `ZSTM`, working sequentially.
*/
def reduceAll[R, R1 <: R, E, A](a: ZSTM[R, E, A], as: Iterable[ZSTM[R1, E, A]])(
f: (A, A) => A
): ZSTM[R1, E, A] =
a.flatMap(ZSTM.mergeAll(as)(_)(f))
/**
* Replicates the given effect n times. If 0 or negative numbers are given, an
* empty `Iterable` will return.
*/
def replicate[R, E, A](n: Int)(tx: ZSTM[R, E, A]): Iterable[ZSTM[R, E, A]] =
new Iterable[ZSTM[R, E, A]] {
override def iterator: Iterator[ZSTM[R, E, A]] = Iterator.range(0, n).map(_ => tx)
}
/**
* Performs this transaction the specified number of times and collects the
* results.
*/
def replicateSTM[R, E, A](n: Int)(transaction: ZSTM[R, E, A]): ZSTM[R, E, Iterable[A]] =
ZSTM.collectAll(ZSTM.replicate(n)(transaction))
/**
* Performs this transaction the specified number of times, discarding the
* results.
*/
def replicateSTMDiscard[R, E, A](n: Int)(transaction: ZSTM[R, E, A]): ZSTM[R, E, Unit] =
ZSTM.collectAllDiscard(ZSTM.replicate(n)(transaction))
/**
* Abort and retry the whole transaction when any of the underlying
* transactional variables have changed.
*/
val retry: USTM[Nothing] = Effect((_, _, _) => throw RetryException)
/**
* Returns an effect with the value on the right part.
*/
def right[A](a: => A): USTM[Either[Nothing, A]] =
succeed(Right(a))
/**
* Accesses the specified service in the environment of the effect.
*/
def service[A: Tag]: ZSTM[A, Nothing, A] =
ZSTM.environmentWith(_.get[A])
/**
* Accesses the service corresponding to the specified key in the environment.
*/
def serviceAt[Service]: ZSTM.ServiceAtPartiallyApplied[Service] =
new ZSTM.ServiceAtPartiallyApplied[Service]
/**
* Effectfully accesses the specified service in the environment of the
* effect.
*/
def serviceWith[Service]: ServiceWithPartiallyApplied[Service] =
new ServiceWithPartiallyApplied[Service]
/**
* Effectfully accesses the specified service in the environment of the
* effect.
*/
def serviceWithSTM[Service]: ServiceWithSTMPartiallyApplied[Service] =
new ServiceWithSTMPartiallyApplied[Service]
/**
* Returns an effect with the optional value.
*/
def some[A](a: => A): USTM[Option[A]] =
succeed(Some(a))
/**
* Returns an `STM` effect that succeeds with the specified value.
*/
def succeed[A](a: => A): USTM[A] = Succeed(() => a)
/**
* Suspends creation of the specified transaction lazily.
*/
def suspend[R, E, A](stm: => ZSTM[R, E, A]): ZSTM[R, E, A] =
STM.succeed(stm).flatten
/**
* Returns an `STM` effect that succeeds with `Unit`.
*/
val unit: USTM[Unit] = succeedNow(())
/**
* The moral equivalent of `if (!p) exp`
*/
def unless[R, E, A](b: => Boolean)(stm: => ZSTM[R, E, A]): ZSTM[R, E, Option[A]] =
suspend(if (b) none else stm.asSome)
/**
* The moral equivalent of `if (!p) exp` when `p` has side-effects
*/
def unlessSTM[R, E](b: ZSTM[R, E, Boolean]): ZSTM.UnlessSTM[R, E] =
new ZSTM.UnlessSTM(b)
/**
* Feeds elements of type `A` to `f` and accumulates all errors in error
* channel or successes in success channel.
*
* This combinator is lossy meaning that if there are errors all successes
* will be lost. To retain all information please use [[partition]].
*/
def validate[R, E, A, B, Collection[+Element] <: Iterable[Element]](in: Collection[A])(
f: A => ZSTM[R, E, B]
)(implicit bf: BuildFrom[Collection[A], B, Collection[B]], ev: CanFail[E]): ZSTM[R, ::[E], Collection[B]] =
partition(in)(f).flatMap {
case (e :: es, _) => fail(::(e, es))
case (_, bs) => succeedNow(bf.fromSpecific(in)(bs))
}
/**
* Feeds elements of type `A` to `f` and accumulates all errors in error
* channel or successes in success channel.
*
* This combinator is lossy meaning that if there are errors all successes
* will be lost. To retain all information please use [[partition]].
*/
def validate[R, E, A, B](in: NonEmptyChunk[A])(
f: A => ZSTM[R, E, B]
)(implicit ev: CanFail[E]): ZSTM[R, ::[E], NonEmptyChunk[B]] =
partition(in)(f).flatMap {
case (e :: es, _) => fail(::(e, es))
case (_, bs) => succeedNow(NonEmptyChunk.nonEmpty(Chunk.fromIterable(bs)))
}
/**
* Feeds elements of type `A` to `f` until it succeeds. Returns first success
* or the accumulation of all errors.
*/
def validateFirst[R, E, A, B, Collection[+Element] <: Iterable[Element]](in: Collection[A])(
f: A => ZSTM[R, E, B]
)(implicit bf: BuildFrom[Collection[A], E, Collection[E]], ev: CanFail[E]): ZSTM[R, Collection[E], B] =
ZSTM.foreach(in)(f(_).flip).flip
/**
* The moral equivalent of `if (p) exp`
*/
def when[R, E, A](b: => Boolean)(stm: => ZSTM[R, E, A]): ZSTM[R, E, Option[A]] =
suspend(if (b) stm.asSome else none)
/**
* Runs an effect when the supplied `PartialFunction` matches for the given
* value, otherwise does nothing.
*/
def whenCase[R, E, A, B](a: => A)(pf: PartialFunction[A, ZSTM[R, E, B]]): ZSTM[R, E, Option[B]] =
suspend(pf.andThen(_.asSome).applyOrElse(a, (_: A) => none))
/**
* Runs an effect when the supplied `PartialFunction` matches for the given
* effectful value, otherwise does nothing.
*/
def whenCaseSTM[R, E, A, B](a: ZSTM[R, E, A])(pf: PartialFunction[A, ZSTM[R, E, B]]): ZSTM[R, E, Option[B]] =
a.flatMap(whenCase(_)(pf))
/**
* The moral equivalent of `if (p) exp` when `p` has side-effects
*/
def whenSTM[R, E](b: ZSTM[R, E, Boolean]): ZSTM.WhenSTM[R, E] =
new ZSTM.WhenSTM(b)
final class Acquire[-R, +E, +A](private val acquire: () => ZSTM[R, E, A]) extends AnyVal {
def apply[R1](release: A => URIO[R1, Any]): Release[R with R1, E, A] =
new Release[R with R1, E, A](acquire, release)
}
final class Release[-R, +E, +A](acquire: () => ZSTM[R, E, A], release: A => URIO[R, Any]) {
def apply[R1 <: R, E1 >: E, B](use: A => ZIO[R1, E1, B])(implicit trace: Trace): ZIO[R1, E1, B] =
ZIO.uninterruptibleMask { restore =>
var state: State[E, A] = State.Running
restore(
unsafeAtomically(acquire())(exit => state = State.Done(exit, List.empty), () => state = State.Interrupted)
)
.foldCauseZIO(
cause => {
state match {
case State.Done(Exit.Success(a), _) =>
release(a).foldCauseZIO(
cause2 => Exit.failCause(cause ++ cause2),
_ => Exit.failCause(cause)
)
case _ => Exit.failCause(cause)
}
},
a =>
restore(use(a)).foldCauseZIO(
cause =>
release(a).foldCauseZIO(
cause2 => Exit.failCause(cause ++ cause2),
_ => Exit.failCause(cause)
),
b => release(a) *> ZIO.succeed(b)
)
)
}
}
final class EnvironmentWithPartiallyApplied[R](private val dummy: Boolean = true) extends AnyVal {
def apply[A](f: ZEnvironment[R] => A): ZSTM[R, Nothing, A] =
ZSTM.environment.map(f)
}
final class EnvironmentWithSTMPartiallyApplied[R](private val dummy: Boolean = true) extends AnyVal {
def apply[R1 <: R, E, A](f: ZEnvironment[R] => ZSTM[R1, E, A]): ZSTM[R with R1, E, A] =
ZSTM.environment.flatMap(f)
}
final class ServiceAtPartiallyApplied[Service](private val dummy: Boolean = true) extends AnyVal {
def apply[Key](
key: => Key
)(implicit tag: EnvironmentTag[Map[Key, Service]]): ZSTM[Map[Key, Service], Nothing, Option[Service]] =
ZSTM.environmentWith(_.getAt(key))
}
final class ServiceWithPartiallyApplied[Service](private val dummy: Boolean = true) extends AnyVal {
def apply[A](f: Service => A)(implicit
tag: Tag[Service]
): ZSTM[Service, Nothing, A] =
ZSTM.service[Service].map(f)
}
final class ServiceWithSTMPartiallyApplied[Service](private val dummy: Boolean = true) extends AnyVal {
def apply[R <: Service, E, A](f: Service => ZSTM[R, E, A])(implicit
tag: Tag[Service]
): ZSTM[R with Service, E, A] =
ZSTM.service[Service].flatMap(f)
}
final class IfSTM[R, E](private val b: ZSTM[R, E, Boolean]) {
def apply[R1 <: R, E1 >: E, A](onTrue: => ZSTM[R1, E1, A], onFalse: => ZSTM[R1, E1, A]): ZSTM[R1, E1, A] =
b.flatMap(b => if (b) onTrue else onFalse)
}
final class UnlessSTM[R, E](private val b: ZSTM[R, E, Boolean]) {
def apply[R1 <: R, E1 >: E, A](stm: => ZSTM[R1, E1, A]): ZSTM[R1, E1, Option[A]] =
b.flatMap(b => if (b) none else stm.asSome)
}
final class UpdateService[-R, +E, +A, M](private val self: ZSTM[R, E, A]) {
def apply[R1 <: R with M](f: M => M)(implicit tag: Tag[M]): ZSTM[R1, E, A] =
self.provideSomeEnvironment(_.update(f))
}
final class UpdateServiceAt[-R, +E, +A, Service](private val self: ZSTM[R, E, A]) extends AnyVal {
def apply[R1 <: R with Map[Key, Service], Key](key: => Key)(
f: Service => Service
)(implicit tag: Tag[Map[Key, Service]]): ZSTM[R1, E, A] =
self.provideSomeEnvironment(_.updateAt(key)(f))
}
final class WhenSTM[R, E](private val b: ZSTM[R, E, Boolean]) {
def apply[R1 <: R, E1 >: E, A](stm: => ZSTM[R1, E1, A]): ZSTM[R1, E1, Option[A]] =
b.flatMap(b => if (b) stm.asSome else none)
}
private[stm] final case class FailException[E](e: E) extends ControlThrowable
private[stm] final case class DieException(t: Throwable) extends ControlThrowable
private[stm] final case class InterruptException(fiberId: FiberId) extends ControlThrowable
private[stm] case object RetryException extends ControlThrowable
private[stm] final case class Effect[R, E, A](f: (Journal, FiberId, ZEnvironment[R]) => A) extends ZSTM[R, E, A] {
def tag: Int = Tags.Effect
}
private[stm] final case class OnFailure[R, E1, E2, A](stm: ZSTM[R, E1, A], k: E1 => ZSTM[R, E2, A])
extends ZSTM[R, E2, A]
with Function[A, ZSTM[R, E2, A]] {
def tag: Int = Tags.OnFailure
def apply(a: A): ZSTM[R, E2, A] = succeedNow(a)
}
private[stm] final case class OnRetry[R, E, A](stm: ZSTM[R, E, A], onRetry: ZSTM[R, E, A])
extends ZSTM[R, E, A]
with Function[A, ZSTM[R, E, A]] {
def tag: Int = Tags.OnRetry
def apply(a: A): ZSTM[R, E, A] = succeedNow(a)
}
private[stm] final case class OnSuccess[R, E, A, B](stm: ZSTM[R, E, A], k: A => ZSTM[R, E, B]) extends ZSTM[R, E, B] {
def tag: Int = Tags.OnSuccess
}
private[stm] final case class Provide[R1, R2, E, A](
effect: ZSTM[R1, E, A],
f: ZEnvironment[R2] => ZEnvironment[R1]
) extends ZSTM[R2, E, A] {
def tag: Int = Tags.Provide
}
private[stm] final case class SucceedNow[A](a: A) extends ZSTM[Any, Nothing, A] {
def tag: Int = Tags.SucceedNow
}
private[stm] final case class Succeed[A](a: () => A) extends ZSTM[Any, Nothing, A] {
def tag: Int = Tags.Succeed
}
private[stm] final case class OnCommit[R](zio: ZIO[R, Nothing, Any], trace: Trace) extends ZSTM[R, Nothing, Unit] {
def tag: Int = Tags.OnCommit
}
private[zio] def succeedNow[A](a: A): USTM[A] = SucceedNow(a)
private[stm] object internal {
// Using 3 because that will size the underlying map to 4 (due to loadFactor = 0.75d)
final val DefaultJournalSize = 3
final val MaxRetries = 10
final val YieldOpCount = 2048
final val LockTimeoutMinMicros = 1L
final val LockTimeoutMaxMicros = 10L
@deprecated("Do not use, scheduled to be removed", "2.1.8")
object Tags {
final val Effect = 0
final val OnSuccess = 1
final val SucceedNow = 2
final val Succeed = 3
final val OnFailure = 4
final val Provide = 5
final val OnRetry = 6
final val OnCommit = 7
}
@deprecated
class Versioned[A](val value: A) extends Serializable
type TxnId = Long
object TxnId {
private[this] val txnCounter = new AtomicLong()
def make(): TxnId = txnCounter.incrementAndGet()
}
final class Journal(
private val map: TreeMap[TRef[?], Entry] = new TreeMap
) {
// -- Map API --
def clear(): Unit = if (map.nonEmpty) map.clear()
def contains(key: TRef[?]): Boolean = map.contains(key)
def getOrElseUpdate(key: TRef[?], entry: => Entry): Entry =
map.getOrElseUpdate(key, entry)
def keys: SortedSet[TRef[?]] = map.keySet
// -- Transactional API --
/**
* Analyzes the journal, determining whether it is valid and whether it is
* read only in a single pass. Note that information on whether the
* journal is read only will only be accurate if the journal is valid, due
* to short-circuiting that occurs on an invalid journal.
*
* In the case that there is only a single entry in the journal, we can
* further shortcut and attempt to commit the transaction in a single pass
* if the `attemptCommit` parameter is set to `true`
*
* '''NOTE''': This method MUST be invoked while we hold the lock on the
* journal
*/
private[internal] def analyze(attemptCommit: Boolean): JournalAnalysis =
if (map.size == 1) analyzeJournal1(map.head._2, attemptCommit)
else analyzeJournalN()
private[this] def analyzeJournal1(value: Entry, attemptCommit: Boolean): JournalAnalysis =
if (attemptCommit) {
if (value.attemptCommit()) JournalAnalysis.Committed
else JournalAnalysis.Invalid
} else if (value.isInvalid) JournalAnalysis.Invalid
else if (value.isChanged) JournalAnalysis.ReadWrite
else JournalAnalysis.ReadOnly
private[this] def analyzeJournalN(): JournalAnalysis = {
var changed = false
val it = map.valuesIterator
while (it.hasNext) {
val value = it.next()
if (value.isInvalid) return JournalAnalysis.Invalid
else if (value.isChanged) changed = true
}
if (changed) JournalAnalysis.ReadWrite else JournalAnalysis.ReadOnly
}
/**
* Commit all changes in the journal.
*
* '''NOTE''': This method MUST be invoked while we hold the lock on the
* journal
*/
private[internal] def commit(): Unit = {
val it = map.valuesIterator
while (it.hasNext) it.next.commit()
}
/**
* Collects all todos and submits them to the executor.
*
* '''NOTE''': This method MUST be invoked while we hold the lock on the
* journal
*/
private[internal] def completeTodos(executor: Executor)(implicit unsafe: Unsafe): Unit = {
val todos = collectTodos()
if (todos.nonEmpty) executor.submitOrThrow(() => execTodos(todos))
}
/**
* Executes the todos in the current thread, sequentially.
*/
private[this] def execTodos(todos: Map[TxnId, Todo]): Unit = {
val it = todos.valuesIterator
while (it.hasNext) it.next.apply()
}
/**
* Flag indicating whether the journal is invalid
*
* '''NOTE''': This method MUST be invoked while we hold the lock on the
* journal
*/
private[ZSTM] def isInvalid: Boolean = !isValid
/**
* Flag indicating whether the journal is valid
*
* '''NOTE''': This method MUST be invoked while we hold the lock on the
* journal
*/
private[ZSTM] def isValid: Boolean = {
val it = map.valuesIterator
while (it.hasNext) if (!it.next().isValid) return false
true
}
/**
* Creates a function that can reset the journal.
*/
private[ZSTM] def resetFn(): () => Unit = {
val currentNewValues = ZSTMUtils.newMutableMap[TRef[_], Any](map.size)
val itCapture = map.iterator
while (itCapture.hasNext) {
val (key, value) = itCapture.next()
currentNewValues.update(key, value.unsafeGet[Any])
}
() => {
val saved = ZSTMUtils.newMutableMap[TRef[_], Entry](map.size)
val it = map.iterator
while (it.hasNext) {
val (key, value) = it.next()
val resetValue = currentNewValues.getOrElse(key, null) match {
case null => value.expected
case v => v
}
saved.update(key, value.copy().reset(resetValue))
}
map.clear()
map ++= saved
()
}
}
/**
* Collect and clear all todos in the journal
*
* '''NOTE''': This method MUST be invoked while we hold the lock on the
* journal
*/
private[this] def collectTodos(): Map[TxnId, Todo] = {
var allTodos = Map.empty[TxnId, Todo]
val it = map.valuesIterator
while (it.hasNext) {
val tref = it.next.tref
val oldTodo = tref.todo
if (oldTodo.nonEmpty) {
tref.todo = Map.empty
allTodos ++= oldTodo
}
}
allTodos
}
/**
* For the given transaction id, adds the specified todo effect to all
* `TRef` values.
*
* '''NOTE''': This method MUST be invoked while we hold the lock on the
* journal
*/
private[internal] def addTodo(txnId: TxnId, todo: Todo): Unit = {
val it = map.keysIterator
while (it.hasNext) {
val tref = it.next()
val oldTodo = tref.todo
if (!oldTodo.contains(txnId)) {
val newTodo = oldTodo.updated(txnId, todo)
tref.todo = newTodo
}
}
}
}
type Todo = () => Any
type JournalAnalysis = Int
object JournalAnalysis {
final val Invalid = 0
final val ReadWrite = 1
final val ReadOnly = 2
final val Committed = 3
}
def tryCommitSync[R, E, A](
fiberId: FiberId,
stm: ZSTM[R, E, A],
state: AtomicReference[State[E, A]],
r: ZEnvironment[R],
executor: Executor
)(implicit unsafe: Unsafe): TryCommit[E, A] = {
val journal = new Journal
var value = null.asInstanceOf[TExit[E, A]]
val stateIsNull = state eq null
// Used to store the previous snapshot of TRefs
var tRefs = immutable.TreeSet.empty[TRef[?]]
// Mutable, changes when journal keys are modified!
// Use `ZSTMUtils.newImmutableTreeSet` to extract the current snapshot
val tRefsUnsafe = journal.keys
var loop = true
var retries = 0
while (loop) {
journal.clear()
if (retries > MaxRetries) {
ZSTMLockSupport.lock(tRefs) {
value = stm.run(journal, fiberId, r)
// Ensure we have the lock on all the tRefs in the current Journal (they might have changed!)
if (tRefsUnsafe.forall(tRefs.contains)) {
if (value.isInstanceOf[TExit.Succeed[?]]) {
val isRunning = stateIsNull || state.compareAndSet(State.Running, State.done(value))
if (isRunning) journal.commit()
loop = false
} else if (journal.isValid) {
loop = false
}
} else {
tRefs = ZSTMUtils.newImmutableTreeSet(tRefsUnsafe)
}
if (!loop && (value ne TExit.Retry)) journal.completeTodos(executor)
}
} else {
value = stm.run(journal, fiberId, r)
ZSTMLockSupport.tryLock(tRefsUnsafe) {
val isSuccess = value.isInstanceOf[TExit.Succeed[_]]
val analysis = journal.analyze(attemptCommit = isSuccess && stateIsNull)
if (analysis != JournalAnalysis.Invalid) {
loop = false
if (
analysis == JournalAnalysis.ReadWrite &&
isSuccess &&
(stateIsNull || state.compareAndSet(State.Running, State.done(value)))
) journal.commit()
if (value ne TExit.Retry) journal.completeTodos(executor)
}
}
if (loop && retries >= MaxRetries) tRefs = ZSTMUtils.newImmutableTreeSet(tRefsUnsafe)
}
retries += 1
}
value match {
case TExit.Succeed(a, onCommit) => TryCommit.Done(Exit.succeed(a), onCommit)
case TExit.Fail(e, onCommit) => TryCommit.Done(Exit.fail(e), onCommit)
case TExit.Die(t, onCommit) => TryCommit.Done(Exit.die(t), onCommit)
case TExit.Interrupt(fiberId, onCommit) => TryCommit.Done(Exit.interrupt(fiberId), onCommit)
case TExit.Retry => TryCommit.Suspend(journal)
}
}
def tryCommitAsync[R, E, A](
journal: Journal,
executor: Executor,
fiberId: FiberId,
stm: ZSTM[R, E, A],
txnId: TxnId,
state: AtomicReference[State[E, A]],
r: ZEnvironment[R]
)(
k: ZIO[R, E, A] => Any
)(implicit trace: Trace, unsafe: Unsafe): Unit = {
def exec(journal: Journal) = {
val keys = journal.keys
ZSTMLockSupport.lock(keys) {
if (journal.isInvalid)
executor.submitOrThrow(() => tryCommitAsync(null, executor, fiberId, stm, txnId, state, r)(k))
else
journal.addTodo(txnId, () => tryCommitAsync(null, executor, fiberId, stm, txnId, state, r)(k))
}
}
state.get match {
case State.Done(exit, _) => k(exit)
case State.Interrupted => k(Exit.interrupt(FiberId.None))
case State.Running if journal ne null => exec(journal)
case State.Running =>
tryCommitSync(fiberId, stm, state, r, executor) match {
case TryCommit.Done(io, _) => k(io)
case TryCommit.Suspend(newJournal) => exec(newJournal)
}
}
}
sealed abstract class TExit[+A, +B] extends Serializable with Product
object TExit {
val unit: TExit[Nothing, Unit] = Succeed((), List.empty)
final case class Fail[+A](value: A, onCommit: List[ZIO[Any, Nothing, Any]]) extends TExit[A, Nothing]
final case class Die(error: Throwable, onCommit: List[ZIO[Any, Nothing, Any]]) extends TExit[Nothing, Nothing]
final case class Interrupt(fiberId: FiberId, onCommit: List[ZIO[Any, Nothing, Any]])
extends TExit[Nothing, Nothing]
final case class Succeed[+B](value: B, onCommit: List[ZIO[Any, Nothing, Any]]) extends TExit[Nothing, B]
case object Retry extends TExit[Nothing, Nothing]
}
abstract class Entry { self =>
type S <: AnyRef
val tref: TRef[S]
private[stm] val expected: S
protected[this] var newValue: S
protected[this] var _isChanged: Boolean
def unsafeSet(value: Any): Unit = {
val value0 = value.asInstanceOf[S]
if (value0 ne newValue) {
if (!_isChanged) _isChanged = true
newValue = value0
}
}
def unsafeUpdate(f: Any => Any): Unit = unsafeSet(f(newValue))
def unsafeGet[B]: B = newValue.asInstanceOf[B]
/**
* Commits the new value to the `TRef`.
*/
def commit(): Unit = tref.versioned.set(newValue)
def attemptCommit(): Boolean = tref.versioned.compareAndSet(expected, newValue)
/**
* Creates a copy of the Entry.
*/
def copy(): Entry = new Entry {
type S = self.S
val tref = self.tref
val expected = self.expected
var newValue = self.newValue
var _isChanged = self.isChanged
}
/**
* Resets the Entry with a given value.
*/
private[stm] def reset(resetValue: Any): Entry = new Entry {
type S = self.S
val tref = self.tref
val expected = self.expected
var newValue = resetValue.asInstanceOf[S]
var _isChanged = false
}
/**
* Determines if the entry is invalid. This is the negated version of
* `isValid`.
*/
def isInvalid: Boolean = !isValid
/**
* Determines if the entry is valid. That is, if the version of the `TRef`
* is equal to the expected version.
*/
def isValid: Boolean = tref.versioned.get.asInstanceOf[AnyRef] eq expected
/**
* Determines if the variable has been set in a transaction.
*/
def isChanged: Boolean = _isChanged
override def toString: String =
s"Entry(expected.value = ${expected}, newValue = $newValue, tref = $tref, isChanged = $isChanged)"
}
object Entry {
/**
* Creates an entry for the journal, given the `TRef` being untracked, the
* new value of the `TRef`, and the expected version of the `TRef`.
*/
private[stm] def apply[A0 <: AnyRef](tref0: TRef[A0]): Entry =
new Entry {
type S = A0
val tref = tref0
val expected = tref.versioned.get
var newValue = expected
var _isChanged = false
}
}
sealed abstract class TryCommit[+E, +A]
object TryCommit {
final case class Done[+E, +A](exit: Exit[E, A], onCommit: List[ZIO[Any, Nothing, Any]]) extends TryCommit[E, A]
case class Suspend(journal: Journal) extends TryCommit[Nothing, Nothing]
}
sealed abstract class State[+E, +A] { self =>
final def isRunning: Boolean =
self match {
case State.Running => true
case _ => false
}
}
object State {
final case class Done[+E, +A](exit: Exit[E, A], onCommit: List[ZIO[Any, Nothing, Any]]) extends State[E, A]
case object Interrupted extends State[Nothing, Nothing]
case object Running extends State[Nothing, Nothing]
def done[E, A](exit: TExit[E, A]): State[E, A] =
exit match {
case TExit.Succeed(a, onCommit) => State.Done(Exit.succeed(a), onCommit)
case TExit.Die(t, onCommit) => State.Done(Exit.die(t), onCommit)
case TExit.Fail(e, onCommit) => State.Done(Exit.fail(e), onCommit)
case TExit.Interrupt(fiberId, onCommit) => State.Done(Exit.interrupt(fiberId), onCommit)
case TExit.Retry => throw new Error("Defect: done being called on TExit.Retry")
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy