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

com.twitter.finagle.util.ConcurrentRingBuffer.scala Maven / Gradle / Ivy

package com.twitter.finagle.util

import java.util.concurrent.atomic.AtomicLong
import scala.annotation.tailrec
import scala.reflect.ClassTag

/**
 * A simple, lock-free, non-blocking ring buffer.
 *
 * This differs from [[com.twitter.util.RingBuffer]] in that it is
 * simpler (fewer features), and is fully concurrent (and hence also
 * threadsafe). This should probably be  moved into util at some
 * point.
 *
 * '''Note:''' For very high-rate usages, sizing buffers by powers of
 * two may be advantageous.
 *
 * '''Caveats:''' References are kept to old entries until they are
 * overwritten again. You can get around this by storing nullable
 * references (though this would require another allocation for each
 * item).
 */
class ConcurrentRingBuffer[T : ClassTag](capacity: Int) {
  assert(capacity > 0)

  private[this] val nextRead, nextWrite = new AtomicLong(0)
  private[this] val publishedWrite = new AtomicLong(-1)
  private[this] val ring = new Array[T](capacity)

  private[this] def publish(which: Long) {
    while (publishedWrite.get != which - 1) {}
    val ok = publishedWrite.compareAndSet(which - 1, which)
    assert(ok)
  }

  /**
   * Try to get an item out of the ConcurrentRingBuffer. Returns no item only
   * when the buffer is empty.
   */
  @tailrec
  final def tryGet(): Option[T] = {
    val w = publishedWrite.get
    val r = nextRead.get

    // Note that the race here is intentional: even if another thread
    // adds an item between getting the r/w cursors and testing them,
    // we have still maintain visibility.
    if (w < r)
      return None

    val el = ring((r%capacity).toInt)
    if (nextRead.compareAndSet(r, r+1))
      Some(el)
    else
      tryGet()
  }

  /**
   * Returns the next element without changing the read position.
   *
   * @return the next element or None if buffer is empty
   */
  final def tryPeek: Option[T] = {
    val w = publishedWrite.get
    val r = nextRead.get

    if (w < r) None
    else Some(ring((r%capacity).toInt))
  }

  /**
   * Attempt to put an item into the buffer. If it is full, the
   * operation fails.
   */
  @tailrec
  final def tryPut(el: T): Boolean = {
    val w = nextWrite.get
    val r = nextRead.get

    if (w - r >= capacity)
      return false

    if (!nextWrite.compareAndSet(w, w+1)) tryPut(el) else {
      ring((w%capacity).toInt) = el
      publish(w)
      true
    }
  }

  /**
   * Current size of the buffer.
   *
   * The returned value is only quiescently consistent; treat it
   * as a fast approximation.
   */
  final def size: Int = (nextWrite.get - nextRead.get).toInt
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy