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

scala.concurrent.stm.ccstm.TxnLevelImpl.scala Maven / Gradle / Ivy

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

package scala.concurrent.stm
package ccstm

import scala.annotation.tailrec
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
import skel.{AbstractNestingLevel, RollbackError}

private[ccstm] object TxnLevelImpl {

  private val stateUpdater = new TxnLevelImpl(null, null, null, false).newStateUpdater

  /** Maps blocked `InTxnImpl` that are members of a commit barrier to the
   *  `TxnLevelImpl` instance on which they are waiting.  All of the values
   *  in this map must be notified when a new link in a (potential) deadlock
   *  chain is formed.
   */
  @volatile private var _blockedBarrierMembers = Map.empty[InTxnImpl, TxnLevelImpl]

  def notifyBlockedBarrierMembers() {
    val m = _blockedBarrierMembers
    if (!m.isEmpty) {
      for (v <- m.values)
        v.synchronized { v.notifyAll() }
    }
  }

  def addBlockedBarrierMember(waiter: InTxnImpl, monitor: TxnLevelImpl) {
    synchronized {
      _blockedBarrierMembers += (waiter -> monitor)
    }
  }

  def removeBlockedBarrierMember(waiter: InTxnImpl) {
    synchronized {
      _blockedBarrierMembers -= waiter
    }
  }
}

/** `TxnLevelImpl` bundles the data and behaviors from `AccessHistory.UndoLog`
 *  and `AbstractNestingLevel`, and adds handling of the nesting level status.
 *
 *  @author Nathan Bronson
 */
private[ccstm] class TxnLevelImpl(val txn: InTxnImpl,
                                  val executor: TxnExecutor,
                                  val parUndo: TxnLevelImpl,
                                  val phantom: Boolean)
        extends AccessHistory.UndoLog with AbstractNestingLevel {
  import TxnLevelImpl._

  // this is the first non-hidden parent
  val parLevel: AbstractNestingLevel = if (parUndo == null || !parUndo.phantom) parUndo else parUndo.parLevel

  val root: AbstractNestingLevel = if (parLevel == null) this else parLevel.root

  /** The transaction attempt on which this transaction is blocked, if any.
   *  Only set for roots.
   */
  @volatile private var _blockedBy: TxnLevelImpl = null

  /** To make `requireActive` more efficient we store the raw state of
   *  `Txn.Active` as null.  If the raw state is a `TxnLevelImpl`, then that
   *  indicates that this nesting level is an ancestor of `txn.currentLevel`;
   *  this is reported as a status of `Txn.Active`.  The raw state can also be
   *  the interned string value "merged" to indicate that the status is now in
   *  lock-step with the parent.
   */
  @volatile private var _state: AnyRef = null

  private def newStateUpdater: AtomicReferenceFieldUpdater[TxnLevelImpl, AnyRef] = {
    AtomicReferenceFieldUpdater.newUpdater(classOf[TxnLevelImpl], classOf[AnyRef], "_state")
  }

  /** True if anybody is waiting for `status.completed`. */
  @volatile private var _waiters = false

  @tailrec final def minEnclosingRetryTimeout(accum: Long = Long.MaxValue): Long = {
    val z = math.min(accum, executor.retryTimeoutNanos.getOrElse(Long.MaxValue))
    if (parUndo == null) z else parUndo.minEnclosingRetryTimeout(z)
  }

  @tailrec final def status: Txn.Status = {
    val raw = _state
    if (raw == null)
      Txn.Active // we encode active as null to make requireActive checks smaller
    else if (raw eq "merged")
      parUndo.status
    else if (raw.isInstanceOf[TxnLevelImpl])
      Txn.Active // child is active
    else
      raw.asInstanceOf[Txn.Status]
  }

  def setCommitting() {
    _state = Txn.Committing
  }

  def setCommitted() {
    _state = Txn.Committed
    notifyCompleted()
  }

  def tryActiveToCommitted(): Boolean = {
    val f = stateUpdater.compareAndSet(this, null, Txn.Committed)
    if (f)
      notifyCompleted()
    f
  }

  def tryActiveToPreparing(): Boolean = {
    val f = stateUpdater.compareAndSet(this, null, Txn.Preparing)
    if (f && txn.commitBarrier != null)
      notifyBlockedBarrierMembers()
    f
  }

  def tryPreparingToPrepared(): Boolean = stateUpdater.compareAndSet(this, Txn.Preparing, Txn.Prepared)

  def tryPreparingToCommitting(): Boolean = stateUpdater.compareAndSet(this, Txn.Preparing, Txn.Committing)

  /** Equivalent to `status` if this level is the current level, otherwise
   *  the result is undefined.
   */
  def statusAsCurrent: Txn.Status = {
    val raw = _state
    if (raw == null)
      Txn.Active
    else
      raw.asInstanceOf[Txn.Status]
  }

  private def notifyCompleted() {
    if (_waiters)
      synchronized { notifyAll() }
  }

  // We are looking for a chain of blockedBy txns that ends in a Prepared txn,
  // and the beginning and the end of the chain have the same non-null commit
  // barrier.  This is made a bit tricky because the deadlock chain might not
  // form from either end, some of the links might not be commit barrier
  // members, and one of the steps to building the chain might not be a call to
  // blockedBy.
  //
  // Our design places responsibility for checking on the beginning (Active) end
  // of the chain, and places responsibility for waking up threads that might
  // need to check on any thread that might complete a chain.

  @tailrec private def hasMemberCycle(cb: CommitBarrierImpl, src: TxnLevelImpl): Boolean = {
    if (src._state.isInstanceOf[Txn.RolledBack])
      false
    else {
      val next = src._blockedBy
      if (next == null)
        src.txn.commitBarrier == cb
      else
        hasMemberCycle(cb, next)
    }
  }

  /** Blocks until `status.completed`, possibly returning early if `waiter`
   *  has been rolled back.
   */
  @throws(classOf[InterruptedException])
  def awaitCompleted(waiter: TxnLevelImpl, debugInfo: Any) {
    assert(parUndo == null)

    _waiters = true

    if (Stats.top != null)
      Stats.top.blockingAcquires += 1

    if (waiter != null) {
      val cb = waiter.txn.commitBarrier
      try {
        waiter._blockedBy = this
        notifyBlockedBarrierMembers()
        if (cb != null)
          addBlockedBarrierMember(waiter.txn, this)

        synchronized {
          // We would not have to check that the waiter has been stolen from
          // except that when there is a commit barrier the thief might also be
          // the txn on which we are blocked
          while (!status.completed && !waiter._state.isInstanceOf[Txn.RolledBack]) {
            if (cb != null && hasMemberCycle(cb, waiter))
              cb.cancelAll(CommitBarrier.MemberCycle(debugInfo))
            wait()
          }
        }

      } finally {
        if (cb != null)
          removeBlockedBarrierMember(waiter.txn)
        waiter._blockedBy = null
      }
    } else {
      synchronized {
        while (!status.completed)
          wait()
      }
    }
  }


  def requireActive() {
    if (_state != null)
      slowRequireActive()
  }

  private def slowRequireActive() {
    status match {
      case Txn.RolledBack(_) => throw RollbackError
      case s => throw new IllegalStateException(s.toString)
    }
  }

  def pushIfActive(child: TxnLevelImpl): Boolean = {
    stateUpdater.compareAndSet(this, null, child)
  }

  def attemptMerge(): Boolean = {
    // First we need to set the current state to forwarding.  Regardless of
    // whether or not this fails we still need to unlink the parent.
    val f = (_state == null) && stateUpdater.compareAndSet(this, null, "merged")

    // We must use CAS to unlink ourselves from our parent, because we race
    // with remote cancels.
    if (parUndo._state eq this)
      stateUpdater.compareAndSet(parUndo, this, null)

    f
  }

  /** Must be called from the transaction's thread. */
  def forceRollback(cause: Txn.RollbackCause) {
    val s = rollbackImpl(Txn.RolledBack(cause))
    assert(s.isInstanceOf[Txn.RolledBack])
  }

  def requestRollback(cause: Txn.RollbackCause): Txn.Status = {
    if (cause.isInstanceOf[Txn.ExplicitRetryCause])
      throw new IllegalArgumentException("explicit retry is not available via requestRollback")
    rollbackImpl(Txn.RolledBack(cause))
  }

  // this is tailrec for retries, but not when we forward to child
  private def rollbackImpl(rb: Txn.RolledBack): Txn.Status = {
    val raw = _state
    if (raw == null || canAttemptLocalRollback(raw)) {
      // normal case
      if (stateUpdater.compareAndSet(this, raw, rb)) {
        notifyCompleted()
        rb
      } else
        rollbackImpl(rb) // retry
    } else if (raw eq "merged") {
      // we are now taking our status from our parent
      parUndo.rollbackImpl(rb)
    } else if (raw.isInstanceOf[TxnLevelImpl]) {
      // roll back the child first, then retry
      raw.asInstanceOf[TxnLevelImpl].rollbackImpl(rb)
      rollbackImpl(rb)
    } else {
      // request denied
      raw.asInstanceOf[Txn.Status]
    }
  }

  private def canAttemptLocalRollback(raw: AnyRef): Boolean = raw match {
    case Txn.Prepared => InTxnImpl.get eq txn // remote cancel is not allowed while preparing
    case s: Txn.Status => !s.decided
    case ch: TxnLevelImpl => ch.rolledBackOrMerged
    case _ => false
  }

  private def rolledBackOrMerged = _state match {
    case "merged" => true
    case Txn.RolledBack(_) => true
    case _ => false
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy