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

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

package com.twitter.util

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

/**
 * 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: String =
    "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 failed computations: It handles only true
 * exceptions that may require cleanup.
 *
 * @see [[AbstractMonitor]] for an API friendly to creating instances from Java.
 */
trait Monitor { self =>

  /**
   * Attempt to handle the exception `exc`.
   *
   * @return whether the exception was handled by this Monitor
   */
  def handle(exc: Throwable): Boolean

  private[this] val someSelf: Some[Monitor] = Some(self)

  /**
   * 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): Unit = {
    // Note: this inlines `Monitor.using` to avoid closure allocations.
    val saved = Monitor.getOption
    try {
      Monitor.setOption(someSelf)
      try f
      catch {
        case exc: Throwable =>
          if (!handle(exc)) throw exc
      }
    } finally Monitor.setOption(saved)
  }

  /**
   * A new monitor which, if `this` fails to handle the exception,
   * attempts to let `next` handle it.
   */
  def orElse(next: Monitor): Monitor = new Monitor {
    def handle(exc: Throwable): Boolean = {
      val tried = self.tryHandle(exc)
      if (tried.isReturn) true
      else next.tryHandle(tried.throwable).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): Monitor = new Monitor {
    def handle(exc: Throwable): Boolean =
      self.tryHandle(exc) match {
        case Return(_) =>
          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 {
      if (self.handle(exc)) Return.Unit
      else Throw(exc)
    } catch {
      case monitorExc: Throwable => Throw(MonitorException(exc, monitorExc))
    }
}

/**
 * An API for creating [[Monitor]] instances in Java.
 */
abstract class AbstractMonitor extends Monitor

/**
 * Defines the (Future)-`Local` monitor as well as some monitor
 * utilities.
 *
 * Java users should use the `Monitors` class.
 */
object Monitor extends Monitor {
  private[this] val local = new Local[Monitor]

  /**
   * Get the current [[Local]] monitor or a [[NullMonitor]]
   * if none has been [[set]].
   *
   * @see [[getOption]]
   */
  def get: Monitor = local() match {
    case Some(m) => m
    case None => NullMonitor
  }

  /**
   * Get the current [[Local]] monitor or `None`
   * if none has been `set`.
   *
   * @see [[get]]
   */
  def getOption: Option[Monitor] = local()

  private[this] def validateSetMonitor(monitor: Monitor): Unit = {
    if (monitor eq this)
      throw new IllegalArgumentException("Cannot set the monitor to the global Monitor")
  }

  /**
   * Set the [[Local]] monitor.
   *
   * Note that the monitor may not be the `Monitor` companion object.
   *
   * @see [[getOption]]
   */
  def set(m: Monitor): Unit = {
    validateSetMonitor(m)
    local() = m
  }

  /**
   * Set the [[Local]] monitor.
   *
   * Note that the monitor may not be the `Monitor` companion object.
   *
   * If `None` is given, then the local is `clear`-ed.
   *
   * @see [[setOption]]
   */
  def setOption(monitor: Option[Monitor]): Unit =
    monitor match {
      case Some(m) =>
        validateSetMonitor(m)
        local.set(monitor)
      case None =>
        local.clear()
    }

  /** 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): 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)

  private[this] val AlwaysFalse = scala.Function.const(false) _

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

  /**
   * 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): Boolean = false
  override def orElse(next: Monitor): Monitor = next
  override def andThen(next: Monitor): Monitor = next

  def getInstance: Monitor = this

  override def toString: String = "NullMonitor"
}

object RootMonitor extends Monitor {
  private[this] val log = Logger.getLogger("monitor")
  private[this] val root = Monitor.mk {
    case control.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
  }

  @volatile private[this] var monitor: Monitor = root

  def handle(exc: Throwable): Boolean = monitor.handle(exc)

  /**
   * Set a custom monitor `m` as the root monitor. If `m` doesn't handle an exception,
   * it'll fallback to the original root monitor.
   */
  def set(m: Monitor): Unit = monitor = m.andThen(root)

  /**
   * Java-friendly API to get this instance
   */
  def getInstance: Monitor = this
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy