All Downloads are FREE. Search and download functionalities are using the official Maven repository.

caustic.runtime.Runtime.scala Maven / Gradle / Ivy

The newest version!
package caustic.runtime

import beaker.client._
import caustic.runtime.Runtime._

import scala.annotation.tailrec
import scala.collection.mutable
import scala.util.{Failure, Success, Try}

/**
 * A transactional virtual machine. Thread-safe.
 *
 * @param database Underlying database.
 */
class Runtime(database: Volume) extends Serializable {

  /**
   * Executes the program and returns the result. Programs are repeatedly partially evaluated until
   * they are reduced to a single literal value. Automatically batches reads and buffers writes.
   *
   * @param program Program to execute.
   * @throws Rollbacked If the program was rolled back.
   * @throws Aborted If the program could not be executed.
   * @throws Fault If the program is illegally constructed.
   * @return Literal result or exception on failure.
   */
  def execute(program: Program): Try[Literal] = {
    val versions = mutable.Map.empty[Key, Version].withDefaultValue(0L)
    val snapshot = mutable.Map.empty[Key, Literal].withDefaultValue(Null)
    val depends  = mutable.Map.empty[Key, Version]
    val buffer   = mutable.Map.empty[Key, Literal]
    val locals   = mutable.Map.empty[Key, Literal]

    @tailrec
    def evaluate(iteration: Program): Try[Literal] = {
      // Fetch all keys that are read (for their value) and written (for their version), that have
      // not been read before (to avoid changes in value and version) to ensure that the evaluation
      // of an expression is correct and consistent.
      @tailrec
      def rwset(stack: List[Program], aggregator: Set[Key]): Set[Key] = stack match {
        case Nil => aggregator
        case (_: Literal) :: rest => rwset(rest, aggregator)
        case Expression(Read, Text(key) :: _) :: rest => rwset(rest, aggregator + key)
        case Expression(Write, Text(key) :: _) :: rest => rwset(rest, aggregator + key)
        case (o: Expression) :: rest => rwset(o.operands ::: rest, aggregator)
      }

      // Fetch the keys, update the local snapshot, and reduce the program. If the result is a
      // literal then return, otherwise recurse on the partially evaluated program.
      this.database.get(rwset(List(iteration), Set.empty) -- versions.keys) match {
        case Success(r) =>
          versions ++= r.mapValues(r => r.version)
          snapshot ++= r.mapValues(r => Literal(r.value))

          reduce(List(iteration), List.empty) match {
            case l: Literal => Success(l)
            case o: Expression => evaluate(o)
          }
        case Failure(e) => Failure(e)
      }
    }

    @tailrec
    def reduce(stack: List[Any], results: List[Program]): Program = (stack, results) match {
      // Return Results.
      case (Nil, _) =>
        if (results.size != 1)
          throw Fault(s"Transaction evaluates to $results.")
        else
          results.head

      // Replace Literals.
      case ((l: Literal) :: rest, rem) => reduce(rest, l :: rem)

      // Expand Expressions.
      case (Expression(Read, Text(k) :: Nil) :: rest, rem) =>
        depends += k -> versions(k)
        reduce(rest, buffer.getOrElse(k, snapshot(k)) :: rem)
      case (Expression(Write, Text(k) :: (v: Literal) :: Nil) :: rest, rem) =>
        depends += k -> versions(k)
        buffer += k -> v
        reduce(rest, Null :: rem)
      case (Expression(Branch, cmp :: pass :: fail :: Nil) :: rest, rem) =>
        reduce(cmp :: Branch :: rest, pass :: fail :: rem)
      case (Expression(Eval, first :: second :: Nil) :: rest, rem) =>
        reduce(second :: first :: Eval :: rest, rem)
      case (Expression(Cons, first :: second :: Nil) :: rest, rem) =>
        reduce(first :: Cons :: rest, second :: rem)
      case (Expression(Repeat, c :: b :: Nil) :: rest, rem) =>
        reduce(branch(c, cons(b, repeat(c, b)), Null) :: rest, rem)
      case ((e: Expression) :: rest, rem) =>
        reduce(e.operands.reverse ::: e.operator :: rest, rem)

      // Simplify Core Expressions.
      case (Read :: rest, k :: rem) => reduce(rest, read(k) :: rem)
      case (Write :: rest, k :: v :: rem) => reduce(rest, write(k, v) :: rem)
      case (Load :: rest, Text(k) :: rem) => reduce(rest, locals.getOrElse(k, Null) :: rem)
      case (Load :: rest, k :: rem) => reduce(rest, load(k) :: rem)
      case (Store :: rest, Text(k) :: (v: Literal) :: rem) => locals += k -> v; reduce(rest, v :: rem)
      case (Store :: rest, k :: v :: rem) => reduce(rest, store(k, v) :: rem)
      case (Rollback :: _, (l: Literal) :: _) => throw Rollbacked(l)
      case (Rollback :: rest, x :: rem) => reduce(rest, rollback(x) :: rem)
      case (Repeat :: rest, False :: _ :: rem) => reduce(rest, rem)
      case (Repeat :: rest, c :: b :: rem) => reduce(rest, repeat(c, b) :: rem)
      case (Prefetch :: rest, k :: s :: r :: rem) => reduce(rest, prefetch(k, s, r) :: rem)
      case (Eval :: rest, (_: Literal) :: (s: Literal) :: rem) => reduce(s :: rest, rem)
      case (Eval :: rest, f :: s :: rem) => reduce(rest, eval(f, s) :: rem)
      case (Cons :: rest, (_: Literal) :: s :: rem) => reduce(s :: rest, rem)
      case (Cons :: rest, f :: s :: rem) => reduce(rest, cons(f, s) :: rem)
      case (Branch :: rest, True :: pass :: _ :: rem) => reduce(pass :: rest, rem)
      case (Branch :: rest, False :: _ :: fail :: rem) => reduce(fail :: rest, rem)
      case (Branch :: rest, Null :: _ :: fail :: rem) => reduce(fail :: rest, rem)
      case (Branch :: rest, (_: Literal) :: pass :: _ :: rem) => reduce(pass :: rest, rem)
      case (Branch :: rest, c :: p :: f :: rem) => reduce(rest, branch(c, p, f) :: rem)

      // Simplify String Expressions.
      case (Length :: rest, x :: rem) => reduce(rest, length(x) :: rem)
      case (Matches :: rest, x :: y :: rem) => reduce(rest, matches(x, y) :: rem)
      case (Contains :: rest, x :: y :: rem) => reduce(rest, contains(x, y) :: rem)
      case (Slice :: rest, x :: l :: h :: rem) => reduce(rest, slice(x, l, h) :: rem)
      case (IndexOf :: rest, x :: y :: rem) => reduce(rest, indexOf(x, y) :: rem)

      // Simplify Math Expressions.
      case (Add :: rest, x :: y :: rem) => reduce(rest, add(x, y) :: rem)
      case (Sub :: rest, x :: y :: rem) => reduce(rest, sub(x, y) :: rem)
      case (Mul :: rest, x :: y :: rem) => reduce(rest, mul(x, y) :: rem)
      case (Div :: rest, x :: y :: rem) => reduce(rest, div(x, y) :: rem)
      case (Mod :: rest, x :: y :: rem) => reduce(rest, mod(x, y) :: rem)
      case (Pow :: rest, x :: y :: rem) => reduce(rest, pow(x, y) :: rem)
      case (Log :: rest, x :: rem) => reduce(rest, log(x) :: rem)
      case (Sin :: rest, x :: rem) => reduce(rest, sin(x) :: rem)
      case (Cos :: rest, x :: rem) => reduce(rest, cos(x) :: rem)
      case (Floor :: rest, x :: rem) => reduce(rest, floor(x) :: rem)

      // Simplify Logical Expressions.
      case (Both :: rest, x :: y :: rem) => reduce(rest, both(x, y) :: rem)
      case (Either :: rest, x :: y :: rem) => reduce(rest, either(x, y) :: rem)
      case (Negate :: rest, x :: rem) => reduce(rest, negate(x) :: rem)

      // Simplify Comparison Expressions.
      case (Equal :: rest, x :: y :: rem) => reduce(rest, equal(x, y) :: rem)
      case (Less :: rest, x :: y :: rem) => reduce(rest, less(x, y) :: rem)

      // Default Error.
      case _ => throw Fault(s"$stack cannot be applied to $results.")
    }

    // Recursively reduce the program, and then conditionally persist all changes made by the
    // transaction to the underlying database if and only if the versions of its various
    // dependencies have not changed. Filter out empty first changes to allow local variables.
    evaluate(program) recoverWith { case e: Rollbacked =>
      this.database.cas(depends.toMap, Map.empty) match {
        case Success(_) => Failure(e)
        case _ => Failure(Aborted)
      }
    } flatMap { r =>
      this.database.cas(depends.toMap, buffer.mapValues(_.asBinary).toMap) match {
        case Success(_) => Success(r)
        case _ => Failure(Aborted)
      }
    }
  }

}

object Runtime {

  /**
   * A non-retryable failure that terminates execution and discards all writes.
   *
   * @param message Literal return value.
   */
  case class Rollbacked(message: Literal) extends Exception

  /**
   * A retryable failure that indicates a program could not be transactionally executed.
   */
  case object Aborted extends Exception

  /**
   * An non-retryable failure that is thrown when a program is illegally constructed.
   *
   * @param message Error message.
   */
  case class Fault(message: String) extends Exception

  /**
   * Constructs a runtime connected to the specified database.
   *
   * @param database Underlying database.
   * @return Initialized runtime.
   */
  def apply(database: Volume): Runtime = new Runtime(database)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy