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

scala.concurrent.stm.Txn.scala Maven / Gradle / Ivy

The newest version!
/* scala-stm - (c) 2009-2011, Stanford University, PPL */

package scala.concurrent.stm

import java.util.concurrent.TimeUnit

/** The `Txn` object provides methods that operate on the current transaction
 *  context.  These methods are only valid within an atomic block or a
 *  transaction life-cycle handler, which is checked at compile time by
 *  requiring that an implicit `InTxn` or `InTxnEnd` be available.
 *
 *  @author Nathan Bronson
 */
object Txn {
  import impl.STMImpl

  //////////// dynamic InTxn binding

  /** Returns `Some(t)` if called from inside the static or dynamic scope of
   *  the transaction context `t`, `None` otherwise.  If an implicit `InTxn` is
   *  available it may be used.
   */
  def findCurrent(implicit mt: MaybeTxn): Option[InTxn] = STMImpl.instance.findCurrent


  //////////// status

  /** The current state of an attempt to execute an atomic block. */
  sealed abstract class Status {

    /** True for `Committing`, `Committed` and `RolledBack`. */
    def decided: Boolean

    /** True for `Committed` and `RolledBack`. */
    def completed: Boolean
  }

  /** The `Status` for a transaction nesting level that may perform `Ref` reads
   *  and writes, that is waiting for a child nesting level to complete, or
   *  that has been merged into an `Active` parent nesting level.
   */
  case object Active extends Status {
    def decided = false
    def completed = false
  }

  /** The `Status` for the nesting levels of a transaction that are attempting
   *  to commit, but for which the outcome is uncertain.
   */
  case object Preparing extends Status {
    def decided = false
    def completed = false
  }

  /** The `Status` for the nesting levels of a transaction that has
   *  successfully acquired all write permissions necessary to succeed, and
   *  that has delegated the final commit decision to an external decider.
   */
  case object Prepared extends Status {
    def decided = false
    def completed = false
  }

  /** The `Status` for the nesting levels of a transaction that has decided to 
   *  commit, but whose `Ref` writes are not yet visible to other threads.
   */
  case object Committing extends Status {
    def decided = true
    def completed = false
  }

  /** The `Status` for the nesting levels of a transaction that has been
   *  committed.  After-commit callbacks may still be running.
   */
  case object Committed extends Status {
    def decided = true
    def completed = true
  }

  /** The `Status` for an atomic block execution attempt that is being or that
   *  has been cancelled.  None of the `Ref` writes made during this nesting
   *  level or in any child nesting level will ever be visible to other
   *  threads.  The atomic block will be automatically retried if `cause` is a
   *  `TransientRollbackCause`, unless STM-specific retry thresholds are
   *  exceeded.
   */
  case class RolledBack(cause: RollbackCause) extends Status {
    def decided = true
    def completed = true
  }


  /** A record of the reason that a atomic block execution attempt was rolled
   *  back.
   */
  sealed abstract class RollbackCause

  /** `RollbackCause`s for which the failure is transient and another attempt
   *  should be made to execute the underlying atomic block.
   */
  sealed abstract class TransientRollbackCause extends RollbackCause

  /** `RollbackCause`s for which the failure is permanent and no attempt should
   *  be made to retry the underlying atomic block.
   */
  sealed abstract class PermanentRollbackCause extends RollbackCause

  /** The `RollbackCause` for a `NestingLevel` whose optimistic execution was
   *  invalid, and that should be retried.  The specific situations in which an
   *  optimistic failure can occur are specific to the STM algorithm, but may
   *  include:
   *  - the STM detected that the value returned by a previous read in this
   *    nesting level is no longer valid;
   *  - a cyclic dependency has occurred and this nesting level must be rolled
   *    back to avoid deadlock;
   *  - a transaction with a higher priority wanted to write to a `Ref` written
   *    by this transaction;
   *  - the STM decided to switch execution strategies for this atomic block;
   *    or
   *  - no apparent reason (*).
   *
   *  (*) - Some STMs perform validation, conflict detection and deadlock cycle
   *  breaking using algorithms that are conservative approximations.  This
   *  means that any particular attempt to execute an atomic block might fail
   *  spuriously.
   *
   *  @param category an STM-specific label for the reason behind this
   *                  optimistic failure. The set of possible categories is
   *                  bounded.
   *  @param trigger  the specific object that led to the optimistic failure,
   *                  if it is available, otherwise `None`.
   */
  case class OptimisticFailureCause(category: Symbol, trigger: Option[Any]) extends TransientRollbackCause

  /** The `RollbackCause` for an atomic block execution attempt that ended with
   *  a call to `retry` or `retryFor`.  The atomic block will be retried after
   *  some memory location read in the previous attempt has changed.
   */
  case class ExplicitRetryCause(timeoutNanos: Option[Long]) extends TransientRollbackCause

  /** The `RollbackCause` for an atomic block that should not be restarted
   *  because it threw an exception.  The exception might have been thrown from
   *  the body of the atomic block or from a handler invoked before the commit
   *  decision.  Exceptions used for control flow are not included (see
   *  `TxnExecutor.isControlFlow`).
   *
   *  Scala's STM discards `Ref` writes performed by atomic blocks that throw
   *  an exception.  This is referred to as "failure atomicity".  In a system
   *  that uses exceptions for error cleanup this design tends to preserve the
   *  invariants of shared data structures, which is a good thing.  If a system
   *  uses exceptions for control flow, however, this design may lead to
   *  unexpected behavior.  The `TxnExecutor` object's `isControlFlow` method
   *  is used to distinguish exceptions representing control flow transfers
   *  from those used to represent error conditions.  See
   *  `TxnExecutor.transformDefault` to change the default rules.
   */
  case class UncaughtExceptionCause(x: Throwable) extends PermanentRollbackCause

  /** Returns the status of the current nesting level of the current
   *  transaction, equivalent to `NestingLevel.current.status`.
   */
  def status(implicit txn: InTxnEnd): Status = txn.status

  //////////// explicit retry and rollback

  // These are methods of the Txn object because it is generally only correct
  // to call them inside the static context of an atomic block.  If they were
  // methods on the InTxn instance, then users might expect to be able to call
  // them from any thread.  Methods to add life-cycle callbacks are also object
  // methods for the same reason.

  /** Rolls back the current nesting level for modular blocking.  It will be
   *  retried, but only after some memory location observed by this transaction
   *  has been changed.  If any alternatives to this atomic block were provided
   *  via `orAtomic` or `atomic.oneOf`, then the alternative will be tried
   *  before blocking.
   *  @throws IllegalStateException if the transaction is not active.
   */
  def retry(implicit txn: InTxn): Nothing = txn.retry()

  /** Causes the transaction to roll back and retry using modular blocking with
   *  a timeout, or returns immediately if the timeout has already expired.
   *  The STM keeps track of the total amount of blocking that has occurred
   *  during modular blocking; this time is apportioned among the calls to
   *  `View.tryAwait` and `retryFor` that are part of the current attempt.
   *  `retryFor(0)` is a no-op.
   *
   *  Returns only if the timeout has expired.
   *  @param timeout the maximum amount of time that this `retryFor` should
   *      block, in units of `unit`.
   *  @param unit the units in which to measure `timeout`, by default
   *      milliseconds.
   */
  def retryFor(timeout: Long, unit: TimeUnit = TimeUnit.MILLISECONDS)(implicit txn: InTxn) {
    txn.retryFor(unit.toNanos(timeout))
  }

  /** Causes the current nesting level to be rolled back due to the specified
   *  `cause`.  This method may only be called by the thread executing the
   *  transaction; obtain a `NestingLevel` instance `n` and call
   *  `n.requestRollback(cause)` if you wish to doom a transaction from another
   *  thread.
   *  @throws IllegalStateException if the current transaction has already
   *      decided to commit.
   */
  def rollback(cause: RollbackCause)(implicit txn: InTxnEnd): Nothing = txn.rollback(cause)


  //////////// life-cycle callbacks

  /** Arranges for `handler` to be executed as late as possible while the root
   *  nesting level of the current transaction is still `Active`, unless the
   *  current nesting level is rolled back.  Reads, writes and additional
   *  nested transactions may be performed inside the handler.  Details:
   *  - it is possible that after `handler` is run the transaction might still
   *    be rolled back;
   *  - it is okay to call `beforeCommit` from inside `handler`, the
   *    reentrantly added handler will be included in this before-commit phase;
   *    and
   *  - before-commit handlers will be executed in their registration order.
   */
  def beforeCommit(handler: InTxn => Unit)(implicit txn: InTxn) { txn.beforeCommit(handler) }

  /** (rare) Arranges for `handler` to be called after the `Ref` reads and
   *  writes have been checked for serializability, but before the decision has
   *  been made to commit or roll back.  While-preparing handlers can lead to
   *  scalability problems, because while this transaction is in the
   *  `Preparing` state it might obstruct other transactions.  Details:
   *  - the handler must not access any `Ref`s, even using `Ref.single`;
   *  - handlers will be executed in their registration order; and
   *  - handlers may be registered while the transaction is active, or from a
   *    while-preparing callback during the `Preparing` phase.
   */
  def whilePreparing(handler: InTxnEnd => Unit)(implicit txn: InTxnEnd) { txn.whilePreparing(handler) }

  /** (rare) Arranges for `handler` to be called after (if) it has been decided
   *  that the current transaction will commit, but before the writes made by
   *  the transaction have become available to other threads.  While-committing
   *  handlers can lead to scalability problems, because while this transaction
   *  is in the `Committing` state it might obstruct other transactions.
   *  Details:
   *  - the handler must not access any `Ref`s, even using `Ref.single`;
   *  - handlers will be executed in their registration order; and
   *  - handlers may be registered so long as the current transaction status is
   *    not `RolledBack` or `Committed`.
   */
  def whileCommitting(handler: InTxnEnd => Unit)(implicit txn: InTxnEnd) { txn.whileCommitting(handler) }

  /** Arranges for `handler` to be executed as soon as possible after the
   *  current transaction is committed, if this nesting level is part of the
   *  overall transaction commit.  Details:
   *  - no transaction will be active while the handler is run, but it may
   *    access `Ref`s using a new top-level atomic block or `.single`;
   *  - the handler runs after all internal locks have been released, so any
   *    values read or written in the transaction might already have been
   *    changed by another thread before the handler is executed;
   *  - handlers will be executed in their registration order; and
   *  - handlers may be registered so long as the current transaction status is
   *    not `RolledBack` or `Committed`.
   */
  def afterCommit(handler: Status => Unit)(implicit txn: InTxnEnd) { txn.afterCommit(handler) }

  /** Arranges for `handler` to be executed as soon as possible after the
   *  current nesting level is rolled back, or runs the handler immediately if
   *  the current nesting level's status is already `RolledBack`.  Details:
   *  - the handler will be executed during any partial rollback that includes
   *    the current nesting level;
   *  - the handler will be run before any additional attempts to execute the
   *    atomic block;
   *  - handlers will be run in the reverse of their registration order; and
   *  - handlers may be registered so long as the current transaction status is
   *    not `Committed`.
   */
  def afterRollback(handler: Status => Unit)(implicit txn: InTxnEnd) { txn.afterRollback(handler) }

  /** Arranges for `handler` to be called as both an after-commit and
   *  after-rollback handler.
   *
   *  Equivalent to: {{{
   *     afterRollback(handler)
   *     afterCommit(handler)
   *  }}}
   */
  def afterCompletion(handler: Status => Unit)(implicit txn: InTxnEnd) { txn.afterCompletion(handler) }


  /** An `ExternalDecider` is given the final control over the decision of
   *  whether or not to commit a transaction, which allows two-phase commit to
   *  be integrated with a single non-transactional resource.  `shouldCommit`
   *  will only be called if a `InTxn` has successfully called all of its
   *  before-commit handlers, acquired all necessary write locks, validated all
   *  of its reads and called all of its while-preparing handlers.  The decider
   *  may then attempt a non-transactional operation whose outcome is
   *  uncertain, and based on the outcome may directly cause the transaction to
   *  commit or roll back.
   */
  trait ExternalDecider {
    /** Should return true if the end-of-life transaction `txn` should commit,
     *  false if it should roll back.  `Txn.rollback` may also be used to
     *  initiate a rollback if that is more convenient.  Called while the
     *  status is `Prepared`.  This method may not access any `Ref`s, even via
     *  `Ref.single`.
     */
    def shouldCommit(implicit txn: InTxnEnd): Boolean
  }

  /** (rare) Delegates final decision of the outcome of the transaction to
   *  `decider` if the current nesting level participates in the top-level
   *  commit.  This method can succeed with at most one value per top-level
   *  transaction.
   *  @throws IllegalStateException if the current transaction's status is not
   *      `Active` or `Preparing`
   *  @throws IllegalArgumentException if `setExternalDecider(d)` was
   *      previously called in this transaction, `d != decider`, and the
   *      nesting level from which `setExternalDecider(d)` was called has not
   *      rolled back.
   */
  def setExternalDecider(decider: ExternalDecider)(implicit txn: InTxnEnd) { txn.setExternalDecider(decider) }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy