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

com.twitter.finagle.Failure.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle

/**
 * Base exception for all Finagle originated failures. These are
 * Exceptions, but with additional `sources` and `flags`.
 * Sources describe the origins of the failure to aid in debugging
 * and flags mark attributes of the Failure (e.g. Retryable).
 * Failures are constructed and extracted using the Failure
 * companion object.
 *
 * {{{
 * val failure = Failure.InterruptedBy(cause).withRetryable(true)
 * val shouldRetry = failure match {
 *  case Failure.Retryable(_) => true
 *  case _ => false
 * }
 * }}}
 */
final class Failure private[finagle](
  private[finagle] val why: String,
  val cause: Throwable = null,
  val flags: Long = Failure.Flag.None,
  sources: Map[String, Object] = Map(),
  val stacktrace: Array[StackTraceElement] = Failure.NoStacktrace
) extends Exception(why, cause) with NoStacktrace {
  import Failure._

  /**
   * Returns a source for a given key, if it exists.
   */
  def getSource(key: String): Option[Object] = sources.get(key)

  /**
   * Creates a new Failure with the given key value pair prepended to sources.
   */
  def withSource(key: String, value: Object): Failure =
    copy(sources = sources + (key -> value))

  /**
   * Creates a new Failure with the current threads stacktrace.
   */
  def withStackTrace(): Failure =
    copy(stacktrace = Thread.currentThread.getStackTrace())

  /**
   * Creates a new Failure that toggles retryable based on the given boolean.
   */
  def withRetryable(on: Boolean): Failure =
    if (on == isSet(flags, Flag.Retryable)) this else {
      copy(flags = toggle(on, flags, Flag.Retryable))
    }

  override def toString: String =
    "Failure(%s, flags=0x%02x)\n\twith %s".format(why, flags,
      if (sources.isEmpty) "NoSources" else sources.mkString("\n\twith "))

  override def getStackTrace(): Array[StackTraceElement] = stacktrace
  override def printStackTrace(p: java.io.PrintWriter) {
    p.println(this)
    for (te <- stacktrace)
      p.println("\tat %s".format(te))
  }

  override def equals(a: Any) = {
    a match {
      case Failure(cause, flags) => this.cause == cause && this.flags == flags
      case _ => false
    }
  }

  override def hashCode = cause.hashCode ^ flags.hashCode

  def copy(
    why: String = why,
    cause: Throwable = cause,
    flags: Long = flags,
    sources: Map[String, Object] = sources,
    stacktrace: Array[StackTraceElement] = stacktrace
  ): Failure = new Failure(why, cause, flags, sources, stacktrace)
}

/**
 * Defines convenient methods for contructing and extracting failures.
 */
object Failure {
  private val NoStacktrace =
    Array(new StackTraceElement("com.twitter.finagle", "NoStacktrace", null, -1))

  object Sources {
    val ServiceName = "service name"
  }

  def unapply(exc: Failure): Option[(Throwable, Long)] = Some((exc.cause, exc.flags))

  /**
   * Failure attributes are distinguished by flags.
   * Flags should be a power of 2.
   */
  private object Flag {
    val None          = 0L
    val Retryable     = 1L << 0
    val Interrupted   = 1L << 1
    // Bits 32 to 63 are reserved for
    // flags private to finagle.
    val Requeueable    = 1L << 32
  }

  private[this] def validate(flag: Long) =
    require((flag & flag-1) == 0, "flag should be a power of 2")

  private def isSet(flags: Long, flag: Long) = {
    validate(flag)
    (flags & flag) != 0
  }

  private def toggle(on: Boolean, flags: Long, flag: Long) = {
    validate(flag)
    if (on) flags | flag else flags & (~flag)
  }

  /**
   * Defines methods to create a Failure type with the given `flag` bit.
   */
  trait Injections {
    val flag: Long
    def apply(why: String, cause: Throwable = null): Failure = new Failure(why, cause, flag)
    def apply(cause: Throwable): Failure =
      if (cause == null) apply("unknown cause")
      else if (cause.getMessage == null) apply(cause.getClass.getName, cause)
      else apply(cause.getMessage, cause)
  }

  /**
   * Defines methods to extract a Failure based on the `flag` bit. Note that if
   * an underlying cause is set, the underlying exception is extracted.
   */
  trait Extractions {
    val flag: Long
    def unapply(exc: Throwable): Option[Throwable] = exc match {
      case f@Failure(null, fs) if isSet(fs, flag) => Some(f)
      case Failure(cause, fs) if isSet(fs, flag) => Some(cause)
      case _ => None
    }
  }

  /**
   * A Failure with a throwable cause.
   */
  object Cause extends Injections {
    val flag = Flag.None
    def unapply(exc: Throwable): Option[Throwable] = exc match {
      case Failure(cause, _) if cause != null => Some(cause)
      case _ => None
    }
  }

  /**
   * A Failure indicating that the corresponding request future has been interrupted.
   * Note, this does does not guarantee that the request was not dispatched.
   */
  object InterruptedBy extends Injections with Extractions {
    val flag = Flag.Interrupted
  }

  /**
   * A retryable failure indicates that thw corresponding dispatch has failed and
   * is eligible for a retry. These types of failures should count against a retry
   * budget.
   */
  object Retryable extends Injections with Extractions {
    val flag = Flag.Retryable
  }

  /**
   * A requeueable failure indicates that a corresponding dispatch has failed
   * and finagle can safely attempt to redispatch. These failures should be
   * handled by finagle and should not count against any user defined retry
   * budget.
   */
  private[finagle] object Requeueable extends Injections with Extractions {
    val flag = Flag.Requeueable
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy