 
                        
        
                        
        scala.concurrent.stm.ccstm.AccessHistory.scala Maven / Gradle / Ivy
 The newest version!
        
        /* scala-stm - (c) 2009-2011, Stanford University, PPL */
package scala.concurrent.stm
package ccstm
import annotation.tailrec
private[ccstm] object AccessHistory {
  /** The operations provided by the read set functionality of an
   *  `AccessHistory`.
   */
  trait ReadSet {
    protected def readCount: Int
    protected def readHandle(i: Int): Handle[_]
    protected def readVersion(i: Int): CCSTM.Version
    protected def recordRead(handle: Handle[_], version: CCSTM.Version)
    protected def readLocate(index: Int): AccessHistory.UndoLog
  }
  /** The operations provided by the pessimistic read set functionality of an
   *  `AccessHistory`.
   */
  trait BargeSet {
    protected def bargeCount: Int
    protected def bargeHandle(i: Int): Handle[_]
    protected def recordBarge(handle: Handle[_])
  }
  /** The operations provided by the write buffer functionality of an
   *  `AccessHistory`.
   */
  trait WriteBuffer {
    protected def writeCount: Int
    protected def getWriteHandle(i: Int): Handle[_]
    protected def getWriteSpecValue[T](i: Int): T
    protected def wasWriteFreshOwner(i: Int): Boolean
    protected def findWrite(handle: Handle[_]): Int
    protected def stableGet[T](handle: Handle[T]): T
    protected def put[T](handle: Handle[T], freshOwner: Boolean, value: T)
    protected def writeAppend[T](handle: Handle[T], freshOwner: Boolean, v: T)
    protected def writeUpdate[T](i: Int, v: T)
    protected def swap[T](handle: Handle[T], freshOwner: Boolean, value: T): T
    protected def compareAndSetIdentity[T, R <: T with AnyRef](
        handle: Handle[T], freshOwner: Boolean, before: R, after: T): Boolean
    protected def getAndTransform[T](handle: Handle[T], freshOwner: Boolean, func: T => T): T
    protected def transformAndGet[T](handle: Handle[T], freshOwner: Boolean, func: T => T): T
    protected def getAndAdd(handle: Handle[Int], freshOwner: Boolean, delta: Int): Int
  }
  /** Holds the write buffer undo log for a particular nesting level.  This is
   *  exposed as an abstract class so that the different parts of the STM that
   *  have per-nesting level objects can share a single instance.
   */
  abstract class UndoLog {
    def parUndo: UndoLog
    var minRetryTimeoutNanos = Long.MaxValue
    var consumedRetryDelta = 0L
    var prevReadCount = 0
    var prevBargeCount = 0
    var prevWriteThreshold = 0
    def addRetryTimeoutNanos(timeoutNanos: Long) {
      minRetryTimeoutNanos = math.min(minRetryTimeoutNanos, timeoutNanos)
    }
    /** Returns the sum of the timeouts of the retries that have timed out.
     *  Included levels are this one, parents of this one, levels that have
     *  been merged into an included level, and levels that were ended with a
     *  permanent rollback and whose parent was included.
     */
    @tailrec final def consumedRetryTotal(accum: Long = 0L): Long = {
      val z = accum + consumedRetryDelta
      if (parUndo == null) z else parUndo.consumedRetryTotal(z)
    }
    @tailrec final def readLocate(index: Int): UndoLog = {
      if (index >= prevReadCount) this else parUndo.readLocate(index)
    }
    private var _logSize = 0
    private var _indices: Array[Int] = null
    private var _prevValues: Array[AnyRef] = null
    def logWrite(i: Int, v: AnyRef) {
      if (_indices == null || _logSize == _indices.length)
        grow()
      _indices(_logSize) = i
      _prevValues(_logSize) = v
      _logSize += 1
    }
    private def grow() {
      if (_logSize == 0) {
        _indices = new Array[Int](16)
        _prevValues = new Array[AnyRef](16)
      } else {
        _indices = copyTo(_indices, new Array[Int](_indices.length * 2))
        _prevValues = copyTo(_prevValues, new Array[AnyRef](_prevValues.length * 2))
      }
    }
    private def copyTo[A](src: Array[A], dst: Array[A]): Array[A] = {
      System.arraycopy(src, 0, dst, 0, src.length)
      dst
    }
    def undoWrites(hist: AccessHistory) {
      // it is important to apply in reverse order
      var i = _logSize - 1
      while (i >= 0) {
        hist.setSpecValue(_indices(i), _prevValues(i))
        i -= 1
      }
    }
  }
}
/** `AccessHistory` includes the read set and the write buffer for all
 *  transaction levels that have not been rolled back.  The read set is a
 *  linear log that contains duplicates, rollback consists of truncating the
 *  read log.  The write buffer is a hash table in which the entries are
 *  addressed by index, rather than by Java reference.  Indices are allocated
 *  sequentially, so a high-water mark (_wUndoThreshold) can differentiate
 *  between entries that can be discarded on a partial rollback and those that
 *  need to be reverted to a previous value.  An undo log is maintained for
 *  writes to entries from enclosing nesting levels, but it is expected that
 *  common usage will be handled mostly using the high-water mark.
 *
 *  It is intended that this class can be extended by the actual `InTxn`
 *  implementation to reduce levels of indirection during barriers.  This is a
 *  bit clumsy, and results in verbose method names to differentiate between
 *  overlapping operations.  Please look away as the sausage is made.  To help
 *  tame the mess the read set and write buffer interfaces are separated into
 *  traits housed in the companion object.  This doesn't actually increase
 *  modularity, but serves as compile-time-checked documentation.
 *
 *  @author Nathan Bronson
 */
private[ccstm] abstract class AccessHistory extends AccessHistory.ReadSet with AccessHistory.BargeSet with AccessHistory.WriteBuffer {
  protected def undoLog: AccessHistory.UndoLog
  protected def checkpointAccessHistory(reusedReadThreshold: Int) {
    checkpointReadSet(reusedReadThreshold)
    checkpointBargeSet()
    checkpointWriteBuffer()
  }
  protected def mergeAccessHistory() {
    // nested commit
    if (Stats.nested != null)
      recordMerge()
    mergeRetryTimeout()
    mergeWriteBuffer()
  }
  private def recordMerge() {
    Stats.nested.commits += 1
  }
  /** Releases locks for discarded handles */
  protected def rollbackAccessHistory(slot: CCSTM.Slot, cause: Txn.RollbackCause) {
    // nested or top-level rollback
    if (Stats.top != null)
      recordRollback(cause)
    if (!cause.isInstanceOf[Txn.ExplicitRetryCause]) {
      rollbackReadSet()
      rollbackBargeSet(slot)
    }
    rollbackRetryTimeout(cause)
    rollbackWriteBuffer(slot)
  }
  private def recordRollback(cause: Txn.RollbackCause) {
    val stat = if (undoLog.parUndo == null) Stats.top else Stats.nested
    stat.rollbackReadSet += (readCount - undoLog.prevReadCount)
    stat.rollbackBargeSet += (bargeCount - undoLog.prevBargeCount)
    stat.rollbackWriteSet += (writeCount - undoLog.prevWriteThreshold)
    cause match {
      case Txn.ExplicitRetryCause(_) => stat.explicitRetries += 1
      case Txn.OptimisticFailureCause(tag, _) => stat.optimisticRetries += tag
      case Txn.UncaughtExceptionCause(x) => stat.failures += x.getClass
    }
  }
  /** Does not release locks */
  protected def resetAccessHistory() {
    if (Stats.top != null)
      recordTopLevelCommit()
    resetReadSet()
    resetBargeSet()
    resetWriteBuffer()
  }
  private def recordTopLevelCommit() {
    // top-level commit
    val top = Stats.top
    top.commitReadSet += readCount
    top.commitBargeSet += bargeCount
    top.commitWriteSet += writeCount
    top.commits += 1
  }
  /** Clears the read set and barge set, returning a `RetrySet` that holds the
   *  values that were removed.  Releases any ownership held by the barge set.
   */
  protected def takeRetrySet(slot: CCSTM.Slot): RetrySet = {
    // barge entries were copied to the read set by addLatestWritesAsReads
    var i = 0
    while (i < _bCount) {
      rollbackHandle(_bHandles(i), slot)
      i += 1
    }
    resetBargeSet()
    val accum = new RetrySetBuilder
    i = 0
    while (i < _rCount) {
      accum += (_rHandles(i), _rVersions(i))
      i += 1
    }
    resetReadSet()
    accum.result()
  }
  //////////// retry timeout
  private def mergeRetryTimeout() {
    // nested commit
    val u = undoLog
    val p = u.parUndo
    p.addRetryTimeoutNanos(u.minRetryTimeoutNanos)
    p.consumedRetryDelta += u.consumedRetryDelta
  }
  private def rollbackRetryTimeout(cause: Txn.RollbackCause) {
    cause match {
      case Txn.ExplicitRetryCause(timeoutNanos) => {
        if (!timeoutNanos.isEmpty)
          undoLog.addRetryTimeoutNanos(timeoutNanos.get)
        if (undoLog.parUndo != null)
          undoLog.parUndo.addRetryTimeoutNanos(undoLog.minRetryTimeoutNanos)
      }
      case _: Txn.PermanentRollbackCause => {
        if (undoLog.parUndo != null)
          undoLog.parUndo.consumedRetryDelta += undoLog.consumedRetryDelta
      }
      case _ =>
    }
  }
  //////////// read set
  private final val InitialReadCapacity = 1024
  private final val MaxRetainedReadCapacity = 8 * InitialReadCapacity
  private var _rCount = 0
  private var _rHandles: Array[Handle[_]] = null
  private var _rVersions: Array[CCSTM.Version] = null
  allocateReadSet()
  protected def readCount = _rCount
  protected def readHandle(i: Int): Handle[_] = _rHandles(i)
  protected def readVersion(i: Int): CCSTM.Version = _rVersions(i)
  protected def recordRead(handle: Handle[_], version: CCSTM.Version) {
    val i = _rCount
    if (i == _rHandles.length)
      growReadSet()
    _rHandles(i) = handle
    _rVersions(i) = version
    _rCount = i + 1
  }
  private def growReadSet() {
    _rHandles = copyTo(_rHandles, new Array[Handle[_]](_rHandles.length * 2))
    _rVersions = copyTo(_rVersions, new Array[CCSTM.Version](_rVersions.length * 2))
  }
  private def copyTo[A](src: Array[A], dst: Array[A]): Array[A] = {
    System.arraycopy(src, 0, dst, 0, src.length)
    dst
  }
  private def checkpointReadSet(reusedReadThreshold: Int) {
    undoLog.prevReadCount = if (reusedReadThreshold >= 0) reusedReadThreshold else _rCount
  }
  private def rollbackReadSet() {
    val n = undoLog.prevReadCount
    var i = n
    while (i < _rCount) {
      _rHandles(i) = null
      i += 1
    }
    _rCount = n
  }
  private def resetReadSet() {
    if (_rHandles.length > MaxRetainedReadCapacity) {
      // avoid staying very large
      allocateReadSet()
    } else {
      // allow GC of old handles
      var i = 0
      while (i < _rCount) {
        _rHandles(i) = null
        i += 1
      }
    }
    _rCount = 0
  }
  private def allocateReadSet() {
    _rHandles = new Array[Handle[_]](InitialReadCapacity)
    _rVersions = new Array[CCSTM.Version](InitialReadCapacity)
  }
  protected def readLocate(index: Int): AccessHistory.UndoLog = undoLog.readLocate(index)
  //////////// pessimistic read buffer
  private final val InitialBargeCapacity = 1024
  private final val MaxRetainedBargeCapacity = 8 * InitialBargeCapacity
  private var _bCount = 0
  private var _bHandles: Array[Handle[_]] = null
  allocateBargeSet()
  protected def bargeCount = _bCount
  protected def bargeHandle(i: Int): Handle[_] = _bHandles(i)
  protected def recordBarge(handle: Handle[_]) {
    val i = _bCount
    if (i == _bHandles.length)
      growBargeSet()
    _bHandles(i) = handle
    _bCount = i + 1
  }
  private def growBargeSet() {
    _bHandles = copyTo(_bHandles, new Array[Handle[_]](_bHandles.length * 2))
  }
  private def checkpointBargeSet() {
    undoLog.prevBargeCount = _bCount
  }
  private def rollbackBargeSet(slot: CCSTM.Slot) {
    val n = undoLog.prevBargeCount
    var i = n
    while (i < _bCount) {
      rollbackHandle(_bHandles(i), slot)
      _bHandles(i) = null
      i += 1
    }
    _bCount = n
  }
  private def resetBargeSet() {
    if (_bCount > 0)
      resetBargeSetNonEmpty()
  }
  private def resetBargeSetNonEmpty() {
    if (_bHandles.length > MaxRetainedBargeCapacity) {
      // avoid staying very large
      allocateBargeSet()
    } else {
      // allow GC of old handles
      var i = 0
      while (i < _bCount) {
        _bHandles(i) = null
        i += 1
      }
    }
    _bCount = 0
  }
  private def allocateBargeSet() {
    _bHandles = new Array[Handle[_]](InitialBargeCapacity)
  }
  //////////// write buffer
  private final val InitialWriteCapacity = 8
  private final val MinAllocatedWriteCapacity = 512
  private final val MaxRetainedWriteCapacity = 8 * MinAllocatedWriteCapacity
  // This write buffer implementation uses chaining, but instead of storing the
  // buckets in objects, they are packed into the arrays bucketAnys and
  // bucketInts.  Pointers to a bucket are represented as a 0-based int, with
  // -1 representing nil.  The dispatch array holds the entry index for the
  // beginning of a bucket chain.
  //
  // When a nested context is created with push(), the current number of
  // allocated buckets is recorded in undoThreshold.  Any changes to the
  // speculative value for a bucket with an index less than this threshold are
  // logged to allow partial rollback.  Buckets with an index greater than the
  // undoThreshold can be discarded during rollback.  This means that despite
  // nesting, each © 2015 - 2025 Weber Informatics LLC | Privacy Policy