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

com.twitter.concurrent.SpoolSource.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.concurrent

import java.util.concurrent.atomic.AtomicReference

import scala.annotation.tailrec

import com.twitter.util.{Future, Promise, Return}

object SpoolSource {
  private object DefaultInterruptHandler extends PartialFunction[Any, Nothing] {
    def isDefinedAt(x: Any) = false
    def apply(x: Any) = throw new MatchError(x)
  }
}

/**
 * A SpoolSource is a simple object for creating and populating a Spool-chain.  apply()
 * returns a Future[Spool] that is populated by calls to offer().  This class is thread-safe.
 * @param interruptHandler attached to every Promise in the produced Spool.
 */
class SpoolSource[A](interruptHandler: PartialFunction[Throwable, Unit]) {
  def this() = this(SpoolSource.DefaultInterruptHandler)

  private val closedp = new Promise[Unit]

  // a reference to the current outstanding promise for the next Future[Spool[A]] result
  private val promiseRef = new AtomicReference[Promise[Spool[A]]]

  // when the SpoolSource is closed, promiseRef will be permanently set to emptyPromise,
  // which always returns an empty spool.
  private val emptyPromise = new Promise(Return(Spool.empty[A]))

  // set the first promise to be fulfilled by the first call to offer()
  promiseRef.set({
    val p = new Promise[Spool[A]]
    p.setInterruptHandler(interruptHandler)
    p
  })

  /**
   * Gets the current outstanding Future for the next Spool value.  The returned Spool
   * will see all future values passed to offer(), up until close() is called.
   * Previous values passed to offer() will not be seen in the Spool.
   */
  def apply(): Future[Spool[A]] = promiseRef.get

  /**
   * Puts a value into the spool.  Unless this SpoolSource has been closed, the current
   * Future[Spool[A]] value will be fulfilled with a Spool that contains the
   * provided value.  If the SpoolSource has been closed, then this value is ignored.
   * If multiple threads call `offer` simultaneously, the operation is thread-safe but
   * the resulting order of values in the spool is non-deterministic.
   */
  final def offer(value: A) {
    val nextPromise = new Promise[Spool[A]]
    nextPromise.setInterruptHandler(interruptHandler)
    updatingTailCall(nextPromise) { currentPromise =>
      currentPromise.setValue(Spool.cons(value, nextPromise))
    }
  }

  /**
   * Puts a value into the spool and closes this SpoolSource.  Unless
   * this SpoolSource has been closed, the current Future[Spool[A]]
   * value will be fulfilled with Spool.cons(value, Spool.empty[A]).
   * If the SpoolSource has been closed, then this value is ignored.
   * If multiple threads call offer simultaneously, the operation is
   * thread-safe but the resulting order of values in the spool is
   * non-deterministic.
   */
  final def offerAndClose(value: A) {
    updatingTailCall(emptyPromise) { currentPromise =>
      currentPromise.setValue(Spool.cons(value, Spool.empty[A]))
      closedp.setDone()
    }
  }

  /**
   * Closes this SpoolSource, which also terminates the generated Spool.  This method
   * is idempotent.
   */
  final def close() {
    updatingTailCall(emptyPromise) { currentPromise =>
      currentPromise.setValue(Spool.empty[A])
      closedp.setDone()
    }
  }

  /**
   * Fulfilled when this SpoolSource has been closed or an exception
   * is raised.
   */
  val closed: Future[Unit] = closedp

  /**
   * Raises exception on this SpoolSource, which also terminates the generated Spool.  This method
   * is idempotent.
   */
  final def raise(e: Throwable) {
    updatingTailCall(emptyPromise) { currentPromise =>
      currentPromise.setException(e)
      closedp.setException(e)
    }
  }

  @tailrec
  private[this] def updatingTailCall(newPromise: Promise[Spool[A]])(f: Promise[Spool[A]] => Unit) {
    val currentPromise = promiseRef.get
    // if the current promise is emptyPromise, then this source has already been closed
    if (currentPromise ne emptyPromise) {
      if (promiseRef.compareAndSet(currentPromise, newPromise)) {
        f(currentPromise)
      } else {
        // try again
        updatingTailCall(newPromise)(f)
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy