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

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

The newest version!
package com.twitter.finagle

import com.twitter.finagle.context.RemoteInfo
import com.twitter.logging.{HasLogLevel, Level}
import com.twitter.util.Duration
import java.net.SocketAddress


/**
 * A trait for exceptions that contain remote information:
 * the downstream address/client id, upstream address/client id (if applicable), and trace id
 * of the request. [[RemoteInfo.NotAvailable]] is used if no remote information
 * has been set.
 */
trait HasRemoteInfo extends Exception {
  private[this] var _remoteInfo: RemoteInfo = RemoteInfo.NotAvailable

  def remoteInfo(): RemoteInfo = _remoteInfo

  private[finagle] def setRemoteInfo(remoteInfo: RemoteInfo): Unit =
    _remoteInfo = remoteInfo

  def exceptionMessage(): String = super.getMessage()

  override def getMessage(): String =
    if (exceptionMessage == null) null
    else s"$exceptionMessage. Remote Info: $remoteInfo"
}

/**
 * A trait for exceptions that have a source. The name of the source is
 * specified as a `serviceName`. The "unspecified" value is used if no
 * `serviceName` is provided by the implementation.
 */
trait SourcedException extends Exception with HasRemoteInfo {
  var serviceName: String = SourcedException.UnspecifiedServiceName
}

object SourcedException {
  val UnspecifiedServiceName = "unspecified"

  def unapply(t: Throwable): Option[String] = t match {
    case sourced: SourcedException
      if sourced.serviceName != SourcedException.UnspecifiedServiceName =>
      Some(sourced.serviceName)
    case sourced: Failure =>
      sourced.getSource(Failure.Source.Service).map(_.toString)
    case _ =>
      None
  }
}

/**
 * A trait for common exceptions that either
 *
 * a) don't benefit from full stacktrace information (e.g. stacktraces don't add
 *    useful information, as in the case of connection closure), or
 * b) are thrown frequently enough that stacktrace-creation becomes unacceptably
 *    expensive.
 *
 * This trait represents a tradeoff between debugging ease and efficiency.
 * Implementers beware.
 */
trait NoStacktrace extends Exception {
  override def fillInStackTrace = this
  // specs expects non-empty stacktrace array
  this.setStackTrace(NoStacktrace.NoStacktraceArray)
}

object NoStacktrace {
  val NoStacktraceArray = Array(new StackTraceElement("com.twitter.finagle", "NoStacktrace", null, -1))
}

/**
 * A base class for request failures. Indicates that some failure occurred
 * before a request could be successfully serviced.
 */
class RequestException(message: String, cause: Throwable)
  extends Exception(message, cause)
  with NoStacktrace
  with SourcedException
{
  def this() = this(null, null)
  def this(cause: Throwable) = this(null, cause)
  override def getStackTrace = if (cause != null) cause.getStackTrace else super.getStackTrace
}

/**
 * Indicates that an operation exceeded some timeout duration before completing.
 * Differs from [[com.twitter.util.TimeoutException]] in that this trait doesn't
 * extend [[java.util.concurrent.TimeoutException]], provides more context in
 * its error message (e.g. the source and timeout value), and is only used
 * within the confines of Finagle.
 */
trait TimeoutException extends SourcedException { self: Exception =>
  protected val timeout: Duration
  protected def explanation: String

  override def exceptionMessage = s"exceeded $timeout to $serviceName while $explanation"
}

/**
 * Indicates that a request timed out. See
 * [[com.twitter.finagle.IndividualRequestTimeoutException]] and
 * [[com.twitter.finagle.GlobalRequestTimeoutException]] for details on the
 * different request granularities that this exception class can pertain to.
 */
class RequestTimeoutException(
  protected val timeout: Duration,
  protected val explanation: String
) extends RequestException with TimeoutException

/**
 * Indicates that a single Finagle-level request timed out. In contrast to
 * [[com.twitter.finagle.RequestTimeoutException]], an "individual request"
 * could be a single request-retry performed as a constituent of an
 * application-level RPC.
 */
class IndividualRequestTimeoutException(timeout: Duration)
  extends RequestTimeoutException(
    timeout,
    "waiting for a response for an individual request, excluding retries")

/**
 * Indicates that a request timed out, where "request" comprises a full RPC
 * from the perspective of the application. For instance, multiple retried
 * Finagle-level requests could constitute the single request that this
 * exception pertains to.
 */
class GlobalRequestTimeoutException(timeout: Duration)
  extends RequestTimeoutException(
    timeout,
    "waiting for a response for the request, including retries (if applicable)")

/**
 * Indicates that a request failed because no servers were available. The
 * Finagle client's internal load balancer was empty. This typically occurs
 * under one of the following conditions:
 *
 * - The cluster is actually down. No servers are available.
 * - A service discovery failure. This can be due to a number of causes, such as
 *   the client being constructed with an invalid cluster destination name [1]
 *   or a failure in the service discovery system (e.g. DNS, ZooKeeper).
 *
 * A good way to diagnose NoBrokersAvailableExceptions is to reach out to the
 * owners of the service to which the client is attempting to connect and verify
 * that the service is operational. If so, then investigate the service
 * discovery mechanism that the client is using (e.g. the
 * [[com.twitter.finagle.Resolver]] that is it configured to use and the system
 * backing it).
 *
 * [1] http://twitter.github.io/finagle/guide/Names.html
 */
class NoBrokersAvailableException(
  val name: String,
  val baseDtab: Dtab,
  val localDtab: Dtab
) extends RequestException {
  def this(name: String = "unknown") = this(name, Dtab.empty, Dtab.empty)

  override def exceptionMessage =
    s"No hosts are available for $name, Dtab.base=[${baseDtab.show}], Dtab.local=[${localDtab.show}]"
}

/**
 * Indicates that a request was cancelled. Cancellation is propagated between a
 * Finagle server and a client intra-process when the server is interrupted by
 * an upstream service. In such cases, the pending Future is interrupted with
 * this exception. The client will cancel its pending request which will by
 * default propagate an interrupt to its downstream, and so on. This is done to
 * conserve resources.
 *
 * @see The [[http://twitter.github.io/finagle/guide/FAQ.html#what-are-cancelledrequestexception-and-cancelledconnectionexception user guide]]
 *      for additional details.
 */
class CancelledRequestException(cause: Throwable) extends RequestException(cause) {
  def this() = this(null)
  override def exceptionMessage = {
    if (cause == null)
      "request cancelled"
    else
      "request cancelled due to " + cause
  }
}

/**
 * Used by [[com.twitter.finagle.pool.WatermarkPool]] to indicate that a request
 * failed because too many requests are already waiting for a connection to
 * become available from a client's connection pool.
 */
class TooManyWaitersException extends RequestException

/**
 * A Future is satisfied with this exception when the process of establishing
 * a session is interrupted. Sessions are not preemptively established in Finagle,
 * rather requests are taxed with session establishment when necessary.
 * For example, this exception can occur if a request is interrupted while waiting for
 * an available session or if an interrupt is propagated from a Finagle server
 * during session establishment.
 *
 * @see com.twitter.finagle.CancelledRequestException
 *
 * @see The [[http://twitter.github.io/finagle/guide/FAQ.html#what-are-cancelledrequestexception-and-cancelledconnectionexception user guide]]
 *      for additional details.
 */
class CancelledConnectionException(cause: Throwable) extends RequestException(cause) {
  def this() = this(null)
}

/**
 * Used by [[com.twitter.finagle.service.FailFastFactory]] to indicate that a
 * request failed because all hosts in the cluster to which the client is
 * connected have been marked as failed. See [[com.twitter.finagle.service.FailFastFactory]]
 * for details on this behavior.
 *
 * @see The [[http://twitter.github.io/finagle/guide/FAQ.html#why-do-clients-see-com-twitter-finagle-failedfastexception-s user guide]]
 *      for additional details.
 */
class FailedFastException(message: String)
  extends RequestException(message, cause = null)
  with WriteException
{
  def this() = this(null)
}

/**
 * Indicates that the request was not servable, according to some policy. See
 * [[com.twitter.finagle.service.OptionallyServableFilter]] as an example.
 */
class NotServableException extends RequestException

/**
 * Indicates that the client failed to distribute a given request according to
 * some sharding strategy. See [[com.twitter.finagle.service.ShardingService]]
 * for details on this behavior.
 */
class NotShardableException extends NotServableException

/**
 * Indicates that the shard to which a request was assigned was not available.
 * See [[com.twitter.finagle.service.ShardingService]] for details on this
 * behavior.
 */
class ShardNotAvailableException extends NotServableException

object ChannelException {
  def apply(cause: Throwable, remoteAddress: SocketAddress) = {
    cause match {
      case exc: ChannelException => exc
      case _: java.net.ConnectException                    => new ConnectionFailedException(cause, remoteAddress)
      case _: java.nio.channels.UnresolvedAddressException => new ConnectionFailedException(cause, remoteAddress)
      case _: java.nio.channels.ClosedChannelException     => new ChannelClosedException(cause, remoteAddress)
      case e: java.io.IOException
        if "Connection reset by peer" == e.getMessage      => new ChannelClosedException(cause, remoteAddress)
      case e: java.io.IOException
        if "Broken pipe" == e.getMessage                   => new ChannelClosedException(cause, remoteAddress)
      case e: java.io.IOException
        if "Connection timed out" == e.getMessage          => new ConnectionFailedException(cause, remoteAddress)
      case e                                               => new UnknownChannelException(cause, remoteAddress)
    }
  }
}

/**
 * An exception encountered within the context of a given socket channel.
 */
class ChannelException(underlying: Throwable, val remoteAddress: SocketAddress)
  extends Exception(underlying)
  with SourcedException
  with HasLogLevel
{
  def this(underlying: Throwable) = this(underlying, null)
  def this() = this(null, null)
  override def exceptionMessage = {
    val message = (underlying, remoteAddress) match {
      case (_, null) => super.exceptionMessage
      case (null, _) => s"ChannelException at remote address: ${remoteAddress.toString}"
      case (_, _) => s"${underlying.getMessage} at remote address: ${remoteAddress.toString}"
    }

    if (serviceName == SourcedException.UnspecifiedServiceName) message
    else s"$message from service: $serviceName"
  }
  def logLevel: Level = Level.DEBUG
}

/**
 * Indicates that the client failed to establish a connection. Typically this
 * class will be extended to provide additional information relevant to a
 * particular category of connection failure.
 */
class ConnectionFailedException(underlying: Throwable, remoteAddress: SocketAddress)
  extends ChannelException(underlying, remoteAddress) with NoStacktrace {
  def this() = this(null, null)
}

/**
 * Indicates that a given channel was closed, for instance if the connection
 * was reset by a peer or a proxy.
 */
class ChannelClosedException(underlying: Throwable, remoteAddress: SocketAddress)
  extends ChannelException(underlying, remoteAddress) with NoStacktrace {
  def this(remoteAddress: SocketAddress) = this(null, remoteAddress)
  def this() = this(null, null)
}

/**
 * Indicates that a write to a given `remoteAddress` timed out.
 */
class WriteTimedOutException(
    remoteAddress: SocketAddress) extends ChannelException(null, remoteAddress) {
  def this() = this(null)
}

/**
 * Indicates that a read from a given `remoteAddress` timed out.
 */
class ReadTimedOutException(
    remoteAddress: SocketAddress) extends ChannelException(null, remoteAddress) {
  def this() = this(null)
}

/**
 * Indicates that some client state was inconsistent with the observed state of
 * some server. For example, the client could receive a channel-connection event
 * from a proxy when there is no outstanding connect request.
 */
class InconsistentStateException(remoteAddress: SocketAddress) extends ChannelException(null, remoteAddress) {
  def this() = this(null)
}

/**
 * A catch-all exception class for uncategorized
 * [[com.twitter.finagle.ChannelException ChannelExceptions]].
 */
case class UnknownChannelException(underlying: Throwable, override val remoteAddress: SocketAddress)
  extends ChannelException(underlying, remoteAddress) {
  def this() = this(null, null)
}

object WriteException {
  def apply(underlying: Throwable): WriteException =
    ChannelWriteException(underlying)

  def unapply(t: Throwable): Option[Throwable] = t match {
    case we: WriteException => Some(we.getCause)
    case _ => None
  }
}

/**
 * Marker trait to indicate there was an exception before writing any of the
 * request.
 * These exceptions should generally be retryable.
 *
 * @see [[com.twitter.finagle.service.RetryPolicy.RetryableWriteException]]
 * @see [[com.twitter.finagle.service.RetryPolicy.WriteExceptionsOnly]]
 */
trait WriteException extends Exception with SourcedException

/**
 * Default implementation for [[WriteException]] that wraps an underlying exception.
 */
case class ChannelWriteException(underlying: Throwable)
  extends ChannelException(underlying)
  with WriteException
  with NoStacktrace
{
  override def fillInStackTrace: NoStacktrace = this
  override def getStackTrace: Array[StackTraceElement] = underlying.getStackTrace
}

/**
 * Indicates that an error occurred while an SSL handshake was being performed
 * with a server at a given `remoteAddress`.
 */
case class SslHandshakeException(underlying: Throwable, override val remoteAddress: SocketAddress)
  extends ChannelException(underlying, remoteAddress) {
  def this() = this(null, null)
}

/**
 * Indicates that the certificate for a given session was invalidated.
 */
case class SslHostVerificationException(principal: String) extends ChannelException {
  def this() = this(null)
}

/**
 * Indicates that connecting to a given `remoteAddress` was refused.
 */
case class ConnectionRefusedException(override val remoteAddress: SocketAddress)
  extends ChannelException(null, remoteAddress) {
  def this() = this(null)
}

/**
 * Indicates that requests were failed by a rate-limiter. See
 * [[com.twitter.finagle.service.RateLimitingFilter]] for details.
 */
case class RefusedByRateLimiter() extends ChannelException

/**
 * A base class for exceptions encountered in the context of a
 * [[com.twitter.finagle.transport.Transport]].
 */
class TransportException extends Exception with SourcedException

/**
 * Indicates that a request failed because a
 * [[com.twitter.finagle.transport.Transport]] write associated with the request
 * was cancelled.
 */
class CancelledWriteException extends TransportException

/**
 * Indicates that a [[com.twitter.finagle.transport.Transport]] write associated
 * with the request was dropped by the transport (usually to respect backpressure).
 */
class DroppedWriteException extends TransportException

/**
 * A trait for exceptions related to a [[com.twitter.finagle.Service]].
 */
trait ServiceException extends Exception with SourcedException

/**
 * Indicates that a request was applied to a [[com.twitter.finagle.Service]]
 * that is closed (i.e. the connection is closed).
 */
class ServiceClosedException extends ServiceException

/**
 * Indicates that a request was applied to a [[com.twitter.finagle.Service]]
 * that is unavailable. This constitutes a fail-stop condition.
 */
class ServiceNotAvailableException extends ServiceException

/**
 * Indicates that the connection was not established within the timeouts.
 * This type of exception should generally be safe to retry.
 */
class ServiceTimeoutException(override protected val timeout: Duration)
  extends WriteException
  with ServiceException
  with TimeoutException
{
  override protected def explanation =
    "creating a service/connection or reserving a service/connection from the service/connection pool " + serviceName
}

/**
 * A base class for exceptions encountered on account of incorrect API usage.
 */
class ApiException extends Exception

/**
 * Indicates that the client has issued more concurrent requests than are
 * allowable, where "allowable" is typically determined based on some
 * configurable maximum.
 */
class TooManyConcurrentRequestsException extends ApiException

/**
 * Indicates that an error occurred on account of incorrect usage of a
 * [[org.jboss.netty.buffer.ChannelBuffer]].
 *
 * TODO: Probably remove this exception class once we migrate away from Netty
 * usage in public APIs.
 */
class ChannelBufferUsageException(description: String) extends Exception(description)

/**
 * An exception that is raised on requests that are discarded because
 * their corresponding backup requests succeeded first. See
 * [[com.twitter.finagle.exp.BackupRequestFilter]] for details.
 */
object BackupRequestLost extends Exception with NoStacktrace with HasLogLevel {
  def logLevel: Level = Level.TRACE
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy