Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
swaydb.IO.scala Maven / Gradle / Ivy
/*
* Copyright (c) 2020 Simer JS Plaha ([email protected] - @simerplaha)
*
* This file is a part of SwayDB.
*
* SwayDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* SwayDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with SwayDB. If not, see .
*
* Additional permission under the GNU Affero GPL version 3 section 7:
* If you modify this Program or any covered work, only by linking or
* combining it with separate works, the licensors of this Program grant
* you additional permission to convey the resulting work.
*/
package swaydb
import com.typesafe.scalalogging.LazyLogging
import swaydb.data.Reserve
import swaydb.data.slice.Slice
import scala.annotation.tailrec
import scala.annotation.unchecked.uncheckedVariance
import scala.collection.mutable.ListBuffer
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
import scala.util.Try
/**
* [[IO.Right]] and [[IO.Left]] are similar to types in [[scala.util.Try]].
*
* [[IO.Defer]] is for performing synchronous and asynchronous IO.
*/
sealed trait IO[+L, +R] {
def isLeft: Boolean
def isRight: Boolean
def exceptionHandler: IO.ExceptionHandler[_]
@throws[Throwable]
def get: R
def getOrElse[B >: R](default: => B): B
def orElse[L2 >: L : IO.ExceptionHandler, B >: R](default: => IO[L2, B]): IO[L2, B]
def foreach[B](f: R => B): Unit
def map[B](f: R => B): IO[L, B]
/**
* Difference between [[map]] and [[transform]] is that [[transform]] does not
* recovery from exception if the function F throws an Exception.
*/
def transform[B](f: R => B): IO[L, B]
def flatMap[L2 >: L : IO.ExceptionHandler, B](f: R => IO[L2, B]): IO[L2, B]
def and[L2 >: L : IO.ExceptionHandler, B](io: => IO[L2, B]): IO[L2, B]
def andThen[B](io: => B): IO[L, B]
def exists(f: R => Boolean): Boolean
def filter(p: R => Boolean): IO[L, R]
def valueOrElse[B](f: R => B, orElse: => B): B
@inline final def withFilter(p: R => Boolean): WithFilter = new WithFilter(p)
final class WithFilter(p: R => Boolean) {
def map[B](f: R => B): IO[L, B] =
IO.this filter p map f
def flatMap[L2 >: L : IO.ExceptionHandler, B](f: R => IO[L2, B]): IO[L2, B] =
IO.this filter p flatMap f
def foreach[B](f: R => B): Unit =
IO.this filter p foreach f
def withFilter(q: R => Boolean): WithFilter =
new WithFilter(x => p(x) && q(x))
}
def left: IO[Throwable, L]
def right: IO[Throwable, R]
def recoverWith[L2 >: L : IO.ExceptionHandler, B >: R](f: PartialFunction[L, IO[L2, B]]): IO[L2, B]
def recover[B >: R](f: PartialFunction[L, B]): IO[L, B]
def flatten[L2, R2](implicit ev: R <:< IO[L2, R2]): IO[L2, R2]
def onLeftSideEffect(f: IO.Left[L, R] => Unit): IO[L, R]
def onRightSideEffect(f: R => Unit): IO[L, R]
def onCompleteSideEffect(f: IO[L, R] => Unit): IO[L, R]
def toOption: Option[R]
def toOptionValue: IO[L, Option[R]]
def toEither: Either[L, R]
def toFuture: Future[R]
def toTry: scala.util.Try[R]
def toDefer[L2 >: L : IO.ExceptionHandler]: IO.Defer[L2, R] =
IO.Defer.io[L2, R](io = this)
def toBag[BAG[_]](implicit bag: Bag[BAG]): BAG[R]@uncheckedVariance =
this match {
case IO.Right(value) =>
bag.success(value)
case IO.Left(value) =>
val exception = exceptionHandler.asInstanceOf[IO.ExceptionHandler[L]].toException(value)
bag.failure(exception)
}
}
object IO {
/**
* [[IO]] type with Throwable error type. Here all errors are returned as exception.
*/
type ThrowableIO[+T] = IO[Throwable, T]
/**
* [[IO]] type with Nothing error type. Nothing indicates this IO type can never result in an error.
*/
type NothingIO[+T] = IO[Nothing, T]
/**
* [[IO]] type with Unit error type. Unit indicates this IO type can never result in an error.
*/
type UnitIO[+T] = IO[Unit, T]
/**
* [[IO]] type used to access database APIs.
*/
type ApiIO[+T] = IO[Error.API, T]
/**
* [[IO]] type for database boot up.
*/
type BootIO[+T] = IO[Error.Boot, T]
val unit: IO.Right[Nothing, Unit] = IO.Right(())(IO.ExceptionHandler.Nothing)
val unitUnit: IO.Right[Nothing, IO.Right[Nothing, Unit]] = IO.Right(IO.Right(())(IO.ExceptionHandler.Nothing))(IO.ExceptionHandler.Nothing)
val none: IO.Right[Nothing, Option[Nothing]] = IO.Right(None)(IO.ExceptionHandler.Nothing)
val `false`: IO.Right[Nothing, Boolean] = IO.Right(false)(IO.ExceptionHandler.Nothing)
val `true`: IO.Right[Nothing, Boolean] = IO.Right(true)(IO.ExceptionHandler.Nothing)
val someTrue: IO[Nothing, Some[Boolean]] = IO.Right(Some(true))(IO.ExceptionHandler.Nothing)
val someFalse: IO[Nothing, Some[Boolean]] = IO.Right(Some(false))(IO.ExceptionHandler.Nothing)
val zero: IO.Right[Nothing, Int] = IO.Right(0)(IO.ExceptionHandler.Nothing)
val zeroZero: IO[Nothing, IO.Right[Nothing, Int]] = IO.Right(IO.Right(0)(IO.ExceptionHandler.Nothing))(IO.ExceptionHandler.Nothing)
val emptyBytes: IO.Right[Nothing, Slice[Byte]] = IO.Right(Slice.emptyBytes)(IO.ExceptionHandler.Nothing)
val emptySeqBytes: IO.Right[Nothing, Seq[Slice[Byte]]] = IO.Right(Seq.empty[Slice[Byte]])(IO.ExceptionHandler.Nothing)
val okIO: IO.Right[Nothing, OK] = IO.Right(OK.instance)(IO.ExceptionHandler.Nothing)
implicit class IterableIOImplicit[E: IO.ExceptionHandler, A: ClassTag](iterable: Iterable[A]) {
def foreachIO[R](f: A => IO[E, R], failFast: Boolean = true): Option[IO.Left[E, R]] = {
val it = iterable.iterator
var failure: Option[IO.Left[E, R]] = None
while (it.hasNext && (failure.isEmpty || !failFast)) {
f(it.next()) onLeftSideEffect {
case failed @ IO.Left(_) =>
failure = Some(failed)
}
}
failure
}
//returns the first IO.Right(Some(_)).
def untilSome[R](f: A => IO[E, Option[R]]): IO[E, Option[(R, A)]] = {
iterable.iterator foreach {
item =>
f(item) match {
case IO.Right(Some(value)) =>
//Not a good idea to break out with return. Needs improvement.
return IO.Right[E, Option[(R, A)]](Some((value, item)))
case IO.Right(None) =>
//continue reading
case IO.Left(error) =>
//Not a good idea to break out with return. Needs improvement.
return IO.Left(error)
}
}
IO.none
}
def untilSomeValue[R](f: A => IO[E, Option[R]]): IO[E, Option[R]] = {
iterable.iterator foreach {
item =>
f(item) match {
case IO.Right(Some(value)) =>
//Not a good idea to break out with return. Needs improvement.
return IO.Right[E, Option[R]](Some(value))
case IO.Right(None) =>
//continue reading
case IO.Left(error) =>
//Not a good idea to break out with return. Needs improvement.
return IO.Left(error)
}
}
IO.none
}
def mapRecoverIO[R: ClassTag](block: A => IO[E, R],
recover: (Slice[R], IO.Left[E, Slice[R]]) => Unit = (_: Slice[R], _: IO.Left[E, Slice[R]]) => (),
failFast: Boolean = true): IO[E, Slice[R]] = {
val it = iterable.iterator
var failure: Option[IO.Left[E, Slice[R]]] = None
val results = Slice.of[R](iterable.size)
while ((!failFast || failure.isEmpty) && it.hasNext) {
block(it.next()) match {
case IO.Right(value) =>
results add value
case failed @ IO.Left(_) =>
failure = Some(IO.Left[E, Slice[R]](failed.value))
}
}
failure match {
case Some(value) =>
recover(results, value)
value
case None =>
IO.Right[E, Slice[R]](results)
}
}
def mapRecover[R: ClassTag](block: A => R,
recover: (Slice[R], Throwable) => Unit = (_: Slice[R], _: Throwable) => (),
failFast: Boolean = true): Slice[R] = {
val it = iterable.iterator
var failure: Throwable = null
val successes = Slice.of[R](iterable.size)
while ((!failFast || failure == null) && it.hasNext) {
try
successes add block(it.next())
catch {
case exception: Throwable =>
failure = exception
}
}
if (failure == null) {
successes
} else {
recover(successes, failure)
throw failure
}
}
def flatMapRecoverIO[R](ioBlock: A => IO[E, Iterable[R]],
recover: (Iterable[R], IO.Left[E, Slice[R]]) => Unit = (_: Iterable[R], _: IO.Left[E, Iterable[R]]) => (),
failFast: Boolean = true): IO[E, Iterable[R]] = {
val it = iterable.iterator
var failure: Option[IO.Left[E, Slice[R]]] = None
val results = ListBuffer.empty[R]
while ((!failFast || failure.isEmpty) && it.hasNext) {
ioBlock(it.next()) match {
case IO.Right(value) =>
value foreach (results += _)
case failed @ IO.Left(_) =>
failure = Some(IO.Left[E, Slice[R]](failed.value))
}
}
failure match {
case Some(value) =>
recover(results, value)
value
case None =>
IO.Right[E, Iterable[R]](results)
}
}
def flatMapRecoverThrowable[R](ioBlock: A => Iterable[R],
recover: (Iterable[R], Throwable) => Unit = (_: Iterable[R], _: Throwable) => (),
failFast: Boolean = true): Iterable[R] = {
val it = iterable.iterator
var failure: Option[Throwable] = None
val results = ListBuffer.empty[R]
while ((!failFast || failure.isEmpty) && it.hasNext)
try
ioBlock(it.next()) foreach (results += _)
catch {
case throwable: Throwable =>
failure = Some(throwable)
}
failure match {
case Some(throwable) =>
recover(results, throwable)
throw throwable
case None =>
results
}
}
def foldLeftRecoverIO[R](r: R,
failFast: Boolean = true,
recover: (R, IO.Left[E, R]) => Unit = (_: R, _: IO.Left[E, R]) => ())(f: (R, A) => IO[E, R]): IO[E, R] = {
val it = iterable.iterator
var failure: Option[IO.Left[E, R]] = None
var result: R = r
while ((!failFast || failure.isEmpty) && it.hasNext) {
f(result, it.next()) match {
case IO.Right(value) =>
if (failure.isEmpty)
result = value
case failed @ IO.Left(_) =>
failure = Some(IO.Left[E, R](failed.value))
}
}
failure match {
case Some(failure) =>
recover(result, failure)
failure
case None =>
IO.Right[E, R](result)
}
}
def foldLeftRecover[R](r: R,
failFast: Boolean = true,
recover: (R, Throwable) => Unit = (_: R, _: Throwable) => ())(f: (R, A) => R): R = {
val it = iterable.iterator
var failure: Option[Throwable] = None
var result: R = r
while ((!failFast || failure.isEmpty) && it.hasNext)
try
result = f(result, it.next())
catch {
case throwable: Throwable =>
failure = Some(throwable)
}
failure match {
case Some(failure) =>
recover(result, failure)
throw failure
case None =>
result
}
}
}
@inline final def tryOrNone[A](block: => A): Option[A] =
try
Option(block)
catch {
case _: Throwable =>
None
}
@inline final def when[E: IO.ExceptionHandler, T, F](condition: => Boolean, onTrue: => IO[E, T], onFalse: => T)(f: T => IO[E, F]): IO[E, F] =
if (condition)
onTrue flatMap f
else
f(onFalse)
@inline final def when[E: IO.ExceptionHandler, T](condition: => Boolean, onTrue: => IO[E, T], onFalse: => IO[E, T]): IO[E, T] =
if (condition)
onTrue
else
onFalse
@inline final def apply[E: IO.ExceptionHandler, A](f: => A): IO[E, A] =
try IO.Right[E, A](f) catch {
case ex: Throwable =>
IO.Left(value = IO.ExceptionHandler.toError[E](ex))
}
object Catch {
@inline final def apply[E: IO.ExceptionHandler, A](f: => IO[E, A]): IO[E, A] =
try
f
catch {
case ex: Throwable =>
IO.Left(value = IO.ExceptionHandler.toError[E](ex))
}
}
def fromTry[E: IO.ExceptionHandler, A](tryBlock: Try[A]): IO[E, A] =
tryBlock match {
case scala.util.Success(value) =>
IO.Right[E, A](value)
case scala.util.Failure(exception) =>
IO.Left[E, A](value = IO.ExceptionHandler.toError[E](exception))
}
def javaExceptionHandler(): ExceptionHandler.type =
ExceptionHandler
trait ExceptionHandler[E] {
def toException(f: E): Throwable
def toError(e: Throwable): E
protected[swaydb] def recover(f: E): Option[Reserve[Unit]] = None
}
trait RecoverableExceptionHandler[E] extends IO.ExceptionHandler[E] {
def recoverFrom(f: E): Option[Reserve[Unit]]
override protected[swaydb] def recover(f: E): Option[Reserve[Unit]] =
recoverFrom(f)
}
object ExceptionHandler extends LazyLogging {
/**
* Creates an [[ExceptionHandler]] that throws Exceptions instead of converting it to a Type.
*
* This is current used only in Java.
*/
def neverException[T] =
new ExceptionHandler[T] {
override def toException(f: T): Throwable = new Exception("neverException: Cannot convert to Exception.")
override def toError(e: Throwable): T = throw new Exception("neverException: Cannot convert Exception to Error.", e)
}
def toException[E](error: E)(implicit errorHandler: IO.ExceptionHandler[E]): Throwable =
errorHandler.toException(error)
def toError[E](exception: Throwable)(implicit errorHandler: IO.ExceptionHandler[E]): E =
errorHandler.toError(exception)
def recover[E](e: E)(implicit errorHandler: IO.ExceptionHandler[E]): Option[Reserve[Unit]] =
try
errorHandler.recover(e)
catch {
case exception: Throwable =>
logger.error("Failed to fetch Reserve. Stopping recovery.", exception)
scala.None
}
object Nothing extends IO.ExceptionHandler[Nothing] {
override def toError(e: Throwable): Nothing =
throw new scala.Exception("Nothing cannot be created from Exception.", e)
override def toException(f: Nothing): Throwable =
new Exception("Nothing value.")
}
object Unit extends IO.ExceptionHandler[Unit] {
override def toError(e: Throwable): Unit =
throw new scala.Exception("Unit cannot be created from Exception.", e)
override def toException(f: Unit): Throwable =
new Exception("Unit value.")
}
object None extends IO.ExceptionHandler[scala.None.type] {
override def toError(e: Throwable): scala.None.type =
throw new scala.Exception("None cannot be created from Exception.", e)
override def toException(f: scala.None.type): Throwable =
new Exception("None value.")
}
def javaThrowable(): IO.ExceptionHandler[Throwable] = Throwable
implicit object Throwable extends IO.ExceptionHandler[Throwable] {
override def toError(e: Throwable): Throwable =
e
override def toException(f: Throwable): Throwable =
f
}
trait Promise[T] extends IO.ExceptionHandler[scala.concurrent.Promise[T]] {
override def toException(promise: scala.concurrent.Promise[T]): Throwable =
promise.future.value match {
case Some(scala.util.Failure(failure)) =>
failure
case Some(scala.util.Success(_)) =>
new Exception("Exception cannot be created from successful Promise.")
case scala.None =>
new Exception("Exception cannot be created from an incomplete Promise.")
}
override def toError(e: Throwable): scala.concurrent.Promise[T] =
scala.concurrent.Promise.failed(e)
}
object PromiseUnit extends IO.ExceptionHandler.Promise[Unit]
}
/** *********************
* **********************
* **********************
* ******** RIGHT *******
* **********************
* **********************
* **********************
*/
final case class Right[+L, +R](value: R)(implicit exceptionHandler: IO.ExceptionHandler[L]) extends IO[L, R] {
override def get: R =
value
override def isLeft: Boolean =
false
override def isRight: Boolean =
true
override def left: IO.Left[Throwable, L] =
IO.Left[Throwable, L](new UnsupportedOperationException("Value is IO.Right"))(IO.ExceptionHandler.Throwable)
override def right: IO.Right[Throwable, R] =
IO.Right[Throwable, R](get)(IO.ExceptionHandler.Throwable)
override def exists(f: R => Boolean): Boolean =
f(value)
override def getOrElse[B >: R](default: => B): B =
get
override def orElse[F >: L : IO.ExceptionHandler, B >: R](default: => IO[F, B]): IO.Right[F, B] =
this
override def valueOrElse[B](f: R => B, orElse: => B): B =
f(value)
override def foreach[B](f: R => B): Unit =
f(get)
override def map[B](f: R => B): IO[L, B] =
IO[L, B](f(get))
/**
* Difference between [[map]] and [[transform]] is that [[transform]] does not
* recovery from exception if the function F throws an Exception.
*/
def transform[B](f: R => B): IO[L, B] =
IO.Right[L, B](f(get))
override def flatMap[F >: L : IO.ExceptionHandler, B](f: R => IO[F, B]): IO[F, B] =
IO.Catch(f(get))
override def and[L2 >: L : ExceptionHandler, B](io: => IO[L2, B]): IO[L2, B] =
io
override def andThen[B](io: => B): IO[L, B] =
IO[L, B](io)
override def flatten[F, B](implicit ev: R <:< IO[F, B]): IO[F, B] =
get
override def recover[B >: R](f: PartialFunction[L, B]): IO[L, B] =
this
override def recoverWith[F >: L : IO.ExceptionHandler, B >: R](f: PartialFunction[L, IO[F, B]]): IO[F, B] =
this
override def toOption: Option[R] =
Some(get)
def toOptionValue: IO[L, Option[R]] =
IO.Right(Some(value))
override def toEither: Either[L, R] =
scala.util.Right(get)
override def filter(p: R => Boolean): IO[L, R] =
IO.Catch(if (p(get)) this else IO.failed[L, R](new NoSuchElementException("Predicate does not hold for " + get)))
override def toFuture: Future[R] =
Future.successful(get)
override def toTry: scala.util.Try[R] =
scala.util.Success(get)
override def onLeftSideEffect(f: IO.Left[L, R] => Unit): IO.Right[L, R] =
this
override def onRightSideEffect(f: R => Unit): IO.Right[L, R] = {
try f(get) finally {}
this
}
override def onCompleteSideEffect(f: IO[L, R] => Unit): IO[L, R] = {
try f(this) finally {}
this
}
override def exceptionHandler: ExceptionHandler[_] =
this.exceptionHandler
}
@inline final def throwable(message: String): Throwable =
new Exception(message)
@inline final def throwable(message: String, inner: Throwable): Throwable =
new Exception(message, inner)
@inline final def throws[T](message: String): T =
throw throwable(message)
@inline final def failed[E: IO.ExceptionHandler, A](exception: Throwable): IO.Left[E, A] =
new IO.Left[E, A](IO.ExceptionHandler.toError[E](exception))
@inline final def failed[E: IO.ExceptionHandler, A](exceptionMessage: String): IO.Left[E, A] =
new IO.Left[E, A](IO.ExceptionHandler.toError[E](new scala.Exception(exceptionMessage)))
/** *********************
* **********************
* **********************
* ******** LEFT ********
* **********************
* **********************
* **********************
*/
final case class Left[+L, +R](value: L)(implicit exceptionHandler: IO.ExceptionHandler[L]) extends IO[L, R] with LazyLogging {
def exception: Throwable =
IO.ExceptionHandler.toException(value)
@throws[Throwable]
override def get: R =
throw exception
override def isLeft: Boolean =
true
override def isRight: Boolean =
false
override def left: IO.Right[Throwable, L] =
IO.Right[Throwable, L](value)(IO.ExceptionHandler.Throwable)
override def right: IO.Left[Throwable, R] =
IO.Left[Throwable, R](new UnsupportedOperationException("Value is IO.Left", IO.ExceptionHandler.toException(value)))(IO.ExceptionHandler.Throwable)
def isRecoverable =
IO.ExceptionHandler.recover(value).isDefined
override def exists(f: R => Boolean): Boolean =
false
override def getOrElse[B >: R](default: => B): B =
default
def valueOrElse[B](f: R => B, orElse: => B): B =
orElse
override def orElse[L2 >: L : IO.ExceptionHandler, B >: R](default: => IO[L2, B]): IO[L2, B] =
IO.Catch(default)
override def foreach[B](f: R => B): Unit =
()
override def map[B](f: R => B): IO.Left[L, B] =
this.asInstanceOf[IO.Left[L, B]]
def transform[B](f: R => B): IO[L, B] =
this.asInstanceOf[IO.Left[L, B]]
override def flatMap[F >: L : IO.ExceptionHandler, B](f: R => IO[F, B]): IO.Left[F, B] =
this.asInstanceOf[IO.Left[F, B]]
override def and[F >: L : ExceptionHandler, B](io: => IO[F, B]): IO[F, B] =
this.asInstanceOf[IO.Left[F, B]]
override def andThen[B](io: => B): IO[L, B] =
this.asInstanceOf[IO.Left[L, B]]
override def flatten[F, B](implicit ev: R <:< IO[F, B]): IO.Left[F, B] =
this.asInstanceOf[IO.Left[F, B]]
override def recover[B >: R](f: PartialFunction[L, B]): IO[L, B] =
IO.Catch {
if (f isDefinedAt value)
IO.Right[L, B](f(value))
else
this
}
override def recoverWith[F >: L : IO.ExceptionHandler, B >: R](f: PartialFunction[L, IO[F, B]]): IO[F, B] =
IO.Catch {
if (f isDefinedAt value)
f(value)
else
this
}
def recoverTo[F >: L : IO.ExceptionHandler, B](io: => IO.Defer[F, B]): IO.Defer[F, B] =
if (this.isRecoverable)
IO.Defer[F, B](io.toIO.get, this.value)
else
IO.Defer[F, B](throw IO.ExceptionHandler.toException(this.value))
override def toOption: Option[R] =
None
def toOptionValue: IO[L, Option[R]] =
IO.Left(value)
override def toEither: Either[L, R] =
scala.util.Left(value)
override def filter(p: R => Boolean): IO.Left[L, R] =
this
override def toFuture: Future[R] =
Future.failed(exception)
override def toTry: scala.util.Try[R] =
scala.util.Failure(exception)
override def onLeftSideEffect(f: IO.Left[L, R] => Unit): IO.Left[L, R] = {
try
f(this)
catch {
case throwable: Throwable =>
logger.error("Failed to apply error side effect", throwable)
}
this
}
override def onCompleteSideEffect(f: IO[L, R] => Unit): IO[L, R] =
onLeftSideEffect(f)
override def onRightSideEffect(f: R => Unit): IO.Left[L, R] =
this
override def exceptionHandler: ExceptionHandler[_] =
this.exceptionHandler
}
/**
* There is a bug here. The Future should not be executed from within [[IO.Defer]].
*
* The following will lead to an infinite loop because future will get invoked each time
* the [[Reserve]] returns free but the future is incomplete which will result in the Defer
* being created again which will result in a new future.
*
* But using this as root defer is ok.
*
* {{{
* def future = Future(println("Running future")
*
* IO
* .Defer
* .unit
* .and(IO.fromFuture(future, "My test"))
* }}}
*
*
* Use val instead where new future is not created every time [[IO.Defer]] is retried on failure/busy.
*
* {{{
* val future = Future(println("Running future")
*
* IO
* .Defer
* .and(IO.fromFuture(future, "My test"))
* .unit
* }}}
*/
private[swaydb] def fromFuture[R](executedFuture: Future[R], reserveName: String = "Future reserve")(implicit ec: ExecutionContext): IO.Defer[swaydb.Error.Level, R] = {
val reserve = Reserve.busy((), reserveName)
executedFuture onComplete {
_ =>
Reserve.setFree(reserve)
}
/**
* [[deferredValue]] will only be invoked the reserve is set free which occurs only when the future is complete.
* But functions like [[IO.Defer.getUnsafe]] will try to access this before the Future is complete will result in failure.
*
* This is necessary to avoid blocking Futures.
*/
def deferredValue(): R =
executedFuture.value match {
case Some(result) =>
result match {
case scala.util.Success(value) =>
//success
value
case scala.util.Failure(exception) =>
//throw Future's failure so deferred can react to this.
//wrap it in another Exception in-case the inner exception is recoverable because the future is complete
//and cannot be recovered.
throw new Exception(exception)
}
case None =>
//Accessing Future when its incomplete.
throw swaydb.Exception.GetOnIncompleteDeferredFutureIO(reserve)
}
import swaydb.Error.Level.ExceptionHandler
//Deferred that returns the result of the above deferred when completed.
IO.Defer[swaydb.Error.Level, R](deferredValue(), swaydb.Error.ReservedResource(reserve))
}
/** **********************************
* **********************************
* **********************************
* ************ DEFERRED ************
* **********************************
* **********************************
* ********************************* */
object Defer extends LazyLogging {
val maxRecoveriesBeforeWarn = 5
val none: IO.Defer[Nothing, None.type] = new IO.Defer[Nothing, None.type](() => None, None)(swaydb.IO.ExceptionHandler.Nothing)
val ok: IO.Defer[Nothing, OK] = new IO.Defer[Nothing, OK](() => OK.instance, None)(swaydb.IO.ExceptionHandler.Nothing)
val unit: IO.Defer[Nothing, Unit] = new IO.Defer[Nothing, Unit](() => (), None)(swaydb.IO.ExceptionHandler.Nothing)
val zero: IO.Defer[Nothing, Int] = new IO.Defer[Nothing, Int](() => 0, None)(swaydb.IO.ExceptionHandler.Nothing)
@inline final def apply[E: IO.ExceptionHandler, A](value: => A): IO.Defer[E, A] =
new Defer(() => value, None)
@inline final def apply[E: IO.ExceptionHandler, A](value: => A, error: E): IO.Defer[E, A] =
new Defer(() => value, Some(error))
@inline final def io[E: IO.ExceptionHandler, A](io: => IO[E, A]): IO.Defer[E, A] =
new IO.Defer(() => io.get, None)
@inline private final def runAndRecover[E: IO.ExceptionHandler, A](f: IO.Defer[E, A]): Either[IO[E, A], IO.Defer[E, A]] =
try
scala.util.Left(IO.Right[E, A](f.getUnsafe))
catch {
case ex: Throwable =>
val error = IO.ExceptionHandler.toError[E](ex)
if (IO.ExceptionHandler.recover(error).isDefined)
scala.util.Right(f.copy(error = Some(error)))
else
scala.util.Left(IO.Left(error))
}
}
final case class Defer[+E: IO.ExceptionHandler, +A] private(private val operation: () => A,
error: Option[E],
private val recovery: Option[_ => IO.Defer[E, A]] = None) extends LazyLogging {
@volatile private var _value: Option[Any] = None
private def getValue = _value.map(_.asInstanceOf[A])
//a deferred IO is completed if it's not reserved.
def isReady: Boolean =
error forall (IO.ExceptionHandler.recover[E](_).forall(_.isFree))
def isBusy =
!isReady
def isComplete: Boolean =
getValue.isDefined
def isPending: Boolean =
!isComplete
def isSuccess: Boolean =
isComplete || toIO.isRight
def isFailure: Boolean =
isPending && toIO.isLeft
private[IO] def getUnsafe: A = {
//Runs composed functions does not perform any recovery.
def forceGet: A =
getValue getOrElse {
val got = operation()
_value = Some(got)
got
}
if (_value.isDefined || !isBusy)
forceGet
else
error match {
case Some(error) =>
throw IO.ExceptionHandler.toException[E](error)
case None =>
forceGet
}
}
def toIO: IO[E, A] =
IO(getUnsafe)
def run[B >: A, BAG[_]](tried: Int)(implicit bag: Bag[BAG]): BAG[B] =
bag match {
case sync: Bag.Sync[BAG] =>
runSync[B, BAG](tried)(sync)
//if a bag is provided that implements isComplete then execute it directly
case async: Bag.Async.Retryable[BAG] =>
runAsync[B, BAG](tried)(async)
case async: Bag.Async[BAG] =>
//else run the Bag as Future and return it's result.
implicit val ec = async.executionContext
async.fromFuture(runAsync[B, Future](tried))
}
/**
* Opens all [[IO.Defer]] types to read the final value in a blocking manner.
*/
def runIO: IO[E, A] = {
def blockIfNeeded(deferred: IO.Defer[E, A]): Unit =
deferred.error foreach {
error =>
ExceptionHandler.recover(error) foreach {
reserve =>
logger.trace(s"Blocking. ${reserve.name}")
Reserve.blockUntilFree(reserve)
logger.trace(s"Freed. ${reserve.name}")
}
}
@tailrec
def doRun(deferred: IO.Defer[E, A], tried: Int): IO[E, A] = {
if (tried > 0) blockIfNeeded(deferred)
IO.Defer.runAndRecover(deferred) match {
case scala.util.Left(io) =>
logger.trace(s"Run! isCached: ${getValue.isDefined}. ${io.getClass.getSimpleName}")
io match {
case success @ IO.Right(_) =>
success
case IO.Left(error) =>
logger.trace(s"Run! isCached: ${getValue.isDefined}. ${io.getClass.getSimpleName}")
if (recovery.isDefined) //pattern matching is not allowing @tailrec. So .get is required here.
doRun(recovery.get.asInstanceOf[E => IO.Defer[E, A]](error), 0)
else
io
}
case scala.util.Right(deferred) =>
logger.trace(s"Retry! isCached: ${getValue.isDefined}. ${deferred.error}")
if (tried > 0 && tried % IO.Defer.maxRecoveriesBeforeWarn == 0)
logger.warn(s"${Thread.currentThread().getName}: Competing reserved resource accessed via IO. Times accessed: $tried. Reserve: ${deferred.error.flatMap(error => IO.ExceptionHandler.recover(error).map(_.name))}")
doRun(deferred, tried + 1)
}
}
doRun(this, 0)
}
/**
* TODO - Similar to [[runIO]]. [[runIO]] should be calling this function
* to build it's execution process.
*/
private def runSync[B >: A, BAG[_]](tried: Int)(implicit bag: Bag.Sync[BAG]): BAG[B] = {
def blockIfNeeded(deferred: IO.Defer[E, B]): Unit =
deferred.error foreach {
error =>
ExceptionHandler.recover(error) foreach {
reserve =>
logger.trace(s"Blocking. ${reserve.name}")
Reserve.blockUntilFree(reserve)
logger.trace(s"Freed. ${reserve.name}")
}
}
@tailrec
def doRun(deferred: IO.Defer[E, B], tried: Int): BAG[B] = {
if (tried > 0) blockIfNeeded(deferred)
IO.Defer.runAndRecover(deferred) match {
case scala.util.Left(io) =>
logger.trace(s"Run! isCached: ${getValue.isDefined}. ${io.getClass.getSimpleName}")
io match {
case success @ IO.Right(_) =>
bag.fromIO(success)
case IO.Left(error) =>
logger.trace(s"Run! isCached: ${getValue.isDefined}. ${io.getClass.getSimpleName}")
if (recovery.isDefined) //pattern matching is not allowing @tailrec. So .get is required here.
doRun(recovery.get.asInstanceOf[E => IO.Defer[E, B]](error), 0)
else
bag.fromIO(io)
}
case scala.util.Right(deferred) =>
logger.trace(s"Retry! isCached: ${getValue.isDefined}. ${deferred.error}")
if (tried > 0 && tried % IO.Defer.maxRecoveriesBeforeWarn == 0)
logger.warn(s"${Thread.currentThread().getName}: Competing reserved resource accessed via runSync. Times accessed: $tried. Reserve: ${deferred.error.flatMap(error => IO.ExceptionHandler.recover(error).map(_.name))}")
doRun(deferred, tried + 1)
}
}
doRun(this, tried)
}
private def runAsync[B >: A, BAG[_]](tried: Int)(implicit bag: Bag.Async.Retryable[BAG]): BAG[B] = {
/**
* If the value is already fetched [[isPending]] run in current thread
* else return a T that listens for the value to be complete.
*/
def delayedRun(deferred: IO.Defer[E, B]): Option[BAG[Unit]] =
deferred.error flatMap {
error =>
ExceptionHandler.recover(error) flatMap {
reserve =>
val promise = Reserve.promise(reserve)
if (promise.isCompleted)
None
else
Some(bag.fromPromise(promise))
}
}
def runDelayed(deferred: IO.Defer[E, B],
tried: Int,
async: BAG[Unit]): BAG[B] =
bag.flatMap(async) {
_ =>
runNow(deferred, tried)
}
//TO-DO moved Options.scala to data package.
def when[X](condition: Boolean)(success: => Option[X]): Option[X] =
if (condition)
success
else
None
@tailrec
def runNow(deferred: IO.Defer[E, B], tried: Int): BAG[B] =
when(tried > 0)(delayedRun(deferred)) match {
case Some(async) if bag.isIncomplete(async) =>
logger.trace(s"Run delayed! isCached: ${getValue.isDefined}.")
runDelayed(deferred, tried, async)
case Some(_) | None =>
logger.trace(s"Run no delay! isCached: ${getValue.isDefined}")
//no delay required run in stack safe manner.
IO.Defer.runAndRecover(deferred) match {
case scala.util.Left(io) =>
io match {
case success @ IO.Right(_) =>
bag.fromIO(success)
case IO.Left(error) =>
if (recovery.isDefined) //pattern matching is not allowing @tailrec. So .get is required here.
runNow(recovery.get.asInstanceOf[E => IO.Defer[E, B]](error), 0)
else
bag.fromIO(io)
}
case scala.util.Right(deferred) =>
if (tried > 0 && tried % IO.Defer.maxRecoveriesBeforeWarn == 0)
logger.warn(s"${Thread.currentThread().getName}: Competing reserved resource accessed via Async. Times accessed: $tried. Reserve: ${deferred.error.flatMap(error => IO.ExceptionHandler.recover(error).map(_.name))}")
runNow(deferred, tried + 1)
}
}
runNow(this, tried)
}
//blocking
def getOrElse[B >: A](default: => B): B =
runIO getOrElse default
def orElse[F >: E : IO.ExceptionHandler, B >: A](default: => IO.Defer[F, B]): IO.Defer[F, B] =
IO.Defer[F, B]((runIO orElse default.toIO).get)
def exists(f: A => Boolean): IO.Defer[E, Boolean] =
map(f)
def foreach(f: A => Unit): IO.Defer[E, Unit] =
IO.Defer[E, Unit](
operation = () => f(getUnsafe),
error = error
)
def orElseIO[F >: E : IO.ExceptionHandler, B >: A](default: => IO[F, B]): IO.Defer[F, B] =
IO.Defer[F, B]((runIO orElse default).get)
def map[B](f: A => B): IO.Defer[E, B] =
IO.Defer[E, B](
operation = () => f(getUnsafe),
error = error
)
def flatMap[F >: E : IO.ExceptionHandler, B](f: A => IO.Defer[F, B]): IO.Defer[F, B] =
IO.Defer[F, B](
operation = () => f(getUnsafe).getUnsafe,
error = error
)
def and[F >: E : IO.ExceptionHandler, B](f: => IO.Defer[F, B]): IO.Defer[F, B] =
IO.Defer[F, B](
operation =
() => {
getUnsafe
f.getUnsafe
},
error = error
)
def flatMapIO[F >: E : IO.ExceptionHandler, B](f: A => IO[F, B]): IO.Defer[F, B] =
IO.Defer[F, B](
operation = () => f(getUnsafe).get,
error = error
)
def andIO[F >: E : IO.ExceptionHandler, B](f: => IO[F, B]): IO.Defer[F, B] =
IO.Defer[F, B](
operation =
() => {
getUnsafe
f.get
},
error = error
)
def recover[B >: A](f: PartialFunction[E, B]): IO.Defer[E, B] =
copy(
recovery =
Some(
(error: E) =>
IO.Defer(f(error))
)
)
def recoverWith[F >: E : IO.ExceptionHandler, B >: A](f: PartialFunction[E, IO.Defer[F, B]]): IO.Defer[F, B] =
copy(
recovery =
Some(
(error: E) =>
f(error)
)
)
//flattens using blocking IO.
def flatten[F, B](implicit ev: A <:< IO.Defer[F, B]): IO.Defer[F, B] =
runIO.get
}
}