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

com.twitter.util.Monitor.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.util

import java.util.logging.{Level, Logger}

/**
 * Wraps an exception that happens when handling another exception in
 * a monitor.
 */
case class MonitorException(
  handlingExc: Throwable,
  monitorExc: Throwable
) extends Exception(monitorExc) {
  override def getMessage =
    "threw exception \""+monitorExc+"\" while handling "+
    "another exception \""+handlingExc+"\""
}

/**
 * A Monitor is a composable exception handler.  It is independent of
 * position, divorced from the notion of a call stack.  Monitors do
 * not recover values from a failed computations: It handles only true
 * exceptions that may require cleanup.
 */
trait Monitor { self =>
  /**
   * Attempt to handle the exception {{exc}}.
   *
   * @return whether the exception was handled by this Monitor
   */
  def handle(exc: Throwable): Boolean

  /**
   * Run {{f}} inside of the monitor context. If {{f}} throws
   * an exception - directly or not - it is handled by this
   * monitor.
   */
  def apply(f: => Unit) = Monitor.using(this) {
    try f catch { case exc: Throwable => if (!handle(exc)) throw exc }
  }

  /**
   * A new monitor which, if {{this}} fails to handle the exception,
   * attempts to let {{next}} handle it.
   */
  def orElse(next: Monitor) = new Monitor {
    def handle(exc: Throwable) = {
      self.tryHandle(exc) rescue { case exc1 =>
        next.tryHandle(exc1)
      } isReturn
    }
  }

  /**
   * A new monitor which first handles the exception with {{this}},
   * then passes it onto {{next}} unconditionally. The new monitor
   * handles the exception if either {{this}} or {{next}} does.
   */
  def andThen(next: Monitor) = new Monitor {
    def handle(exc: Throwable) =
      self.tryHandle(exc) match {
        case Return.Unit =>
          next.tryHandle(exc)
          true
        case Throw(exc1) =>
          next.tryHandle(exc1).isReturn
      }
  }

  /**
   * An implementation widget: attempts to handle {{exc}} returning a
   * {{com.twitter.util.Try[Unit]}}.  If the exception is unhandled,
   * we return {{Throw(exc)}}, if the handler throws an exception, we
   * wrap it in a {{MonitorException}}.
   */
  protected def tryHandle(exc: Throwable): Try[Unit] =
    Try { self.handle(exc) } rescue {
      case monitorExc => Throw(MonitorException(exc, monitorExc))
    } flatMap { ok =>
      if (ok) Return.Unit
      else Throw(exc): Try[Unit]
    }
}

/**
 * Defines the (Future)-{{Local}} monitor as well as some monitor
 * utilities.
 */
object Monitor extends Monitor {
  private[this] val local = new Local[Monitor]

  /** Get the current {{Local}} monitor */
  def get = local() getOrElse NullMonitor
  /** Set the {{Locall}} monitor */
  def set(m: Monitor) {
    require(m ne this, "Cannot set the monitor to the global Monitor")
    local() = m
  }

  /** Compute {{f}} with the {{Local}} monitor set to {{m}} */
  @inline
  def using[T](m: Monitor)(f: => T): T = restoring {
    set(m)
    f
  }

  /** Restore the {{Local}} monitor after running computation {{f}} */
  @inline
  def restoring[T](f: => T): T = {
    val saved = local()
    try f finally local.set(saved)
  }

  /**
   * An exception catcher that attempts to handle exceptions with
   * the current monitor.
   */
  val catcher: PartialFunction[Throwable, Unit] = {
    case exc =>
      if (!handle(exc))
        throw exc
  }

  /**
   * Run the computation {{f}} in the context of the current {{Local}}
   * monitor.
   */
  override def apply(f: => Unit) =
    try f catch catcher

  /**
   * Handle {{exc}} with the current {{Local}} monitor.  If the
   * {{Local}} monitor fails to handle the exception, it is handled by
   * the {{RootMonitor}}.
   */
  def handle(exc: Throwable): Boolean =
    (get orElse RootMonitor).handle(exc)

  /**
   * Create a new monitor from a partial function.
   */
  def mk(f: PartialFunction[Throwable, Boolean]) = new Monitor {
    def handle(exc: Throwable) =
      if (f.isDefinedAt(exc)) f(exc)
      else false
  }

  /**
   * Checks whether or not monitoring is activated, meaning that the
   * currently-set Monitor is non-null.
   *
   * @return true if currently-set Monitor is the NullMonitor. False otherwise.
   */
  def isActive: Boolean = get != NullMonitor
}

/**
 * A monitor that always fails to handle an exception. Combining this
 * with any other Monitor will simply return the other Monitor effectively
 * removing NullMonitor from the chain.
 */
object NullMonitor extends Monitor {
  def handle(exc: Throwable) = false
  override def orElse(next: Monitor) = next
  override def andThen(next: Monitor) = next
}

object RootMonitor extends Monitor {
  private[this] val log = Logger.getLogger("monitor")

  def handle(exc: Throwable) = exc match {
    case NonFatal(e) =>
      log.log(Level.SEVERE, "Exception propagated to the root monitor!", e)
      true /* Never propagate non fatal exception */

    case e: VirtualMachineError =>
      log.log(Level.SEVERE, "VM error", e)
      System.err.println("VM error: %s".format(e.getMessage))
      e.printStackTrace(System.err)
      System.exit(1)
      true  /*NOTREACHED*/

    case e: Throwable =>
      log.log(Level.SEVERE, "Fatal exception propagated to the root monitor!", e)
      false
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy