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

com.twitter.finagle.context.Context.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle.context

import com.twitter.finagle.netty3.ChannelBufferBuf
import com.twitter.io.Buf
import com.twitter.util.{Local, Try, Return, Throw}
import scala.collection.mutable

/**
 * A context contains a number of let-delimited bindings. Bindings
 * are indexed by type Key[A] in a typesafe manner. Later bindings
 * shadow earlier ones.
 *
 * Note that the implementation of context maintains all bindings
 * in a linked list; context lookup requires a linear search.
 */
trait Context {
  type Key[A]

  sealed trait Env {
    /**
     * Retrieve the current definition of a key.
     *
     * @throws NoSuchElementException when the key is undefined
     * in this environment.
     */
    @throws[NoSuchElementException]("If the key does not exist")
    def apply[A](key: Key[A]): A

    /**
     * Retrieve the current definition of a key, but only
     * if it is defined.
     */
    def get[A](key: Key[A]): Option[A]

    /**
     * Retrieve the current definition of a key if it is defined.
     * If it is not defined, `orElse` is evaluated and returned.
     */
    def getOrElse[A](key: Key[A], orElse: () => A): A

    /**
     * Tells whether `key` is defined in this environment.
     */
    def contains[A](key: Key[A]): Boolean

    /**
     * Create a derived environment where `key` is bound to
     * `value`; previous bindings of `key` are shadowed.
     */
    final def bound[A](key: Key[A], value: A): Env = Bound(this, key, value)

    /**
     * Clear the binding for the given key. Lookups for `key` will be
     * negative in the returned environment.
     */
    final def cleared(key: Key[_]): Env = Cleared(this, key)
  }

  /**
   * An empty environment. No keys are present.
   */
  object Empty extends Env {
    def apply[A](key: Key[A]) = throw new NoSuchElementException
    def get[A](key: Key[A]) = None
    def getOrElse[A](key: Key[A], orElse: () => A): A = orElse()
    def contains[A](key: Key[A]) = false
    override def toString = ""
  }

  /**
   * An environment with `key` bound to `value`; lookups for other keys
   * are forwarded to `next`.
   */
  case class Bound[A](next: Env, key: Key[A], value: A) extends Env {
    def apply[B](key: Key[B]): B =
      if (key == this.key) value.asInstanceOf[B]
      else next(key)

    def get[B](key: Key[B]): Option[B] =
      if (key == this.key) Some(value.asInstanceOf[B])
      else next.get(key)

    def getOrElse[B](key: Key[B], orElse: () => B): B =
      if (key == this.key) value.asInstanceOf[B]
      else next.getOrElse(key, orElse)

    def contains[B](key: Key[B]): Boolean =
      key == this.key || next.contains(key)

    override def toString = s"Bound($key, $value) :: $next"
  }

  /**
   * An environment without `key`. Lookups for other keys
   * are forwarded to `next`.
   */
  case class Cleared[A](next: Env, key: Key[A]) extends Env {
    def apply[B](key: Key[B]) =
      if (key == this.key) throw new NoSuchElementException
      else next(key)

    def get[B](key: Key[B])  =
      if (key == this.key) None
      else next.get(key)

    def getOrElse[B](key: Key[B], orElse: () => B): B =
      if (key == this.key) orElse()
      else next.getOrElse(key, orElse)

    def contains[B](key: Key[B]) =
      key != this.key && next.contains(key)

    override def toString = s"Clear($key) :: $next"
  }

  /**
   * Concatenate two environments with left-hand side precedence.
   */
  case class OrElse(left: Env, right: Env) extends Env {
    def apply[A](key: Key[A]) =
      if (left.contains(key)) left.apply(key)
      else right.apply(key)

    def get[A](key: Key[A])  =
      if (left.contains(key)) left.get(key)
      else right.get(key)

    def getOrElse[A](key: Key[A], orElse: () => A): A =
      left.getOrElse(key, () => right.getOrElse(key, orElse))

    def contains[A](key: Key[A]) =
      left.contains(key) || right.contains(key)

    override def toString = s"OrElse($left, $right)"
  }

  private[this] val local = new Local[Env]

  private[finagle] def env: Env = local() match {
    case Some(env) => env
    case None => Empty
  }

  /**
   * Retrieve the current definition of a key.
   *
   * @throws NoSuchElementException when the key is undefined
   * in the current request-local context.
   */
  @throws[NoSuchElementException]("If the key does not exist")
  def apply[A](key: Key[A]): A = env(key)

  /**
   * Retrieve the current definition of a key, but only
   * if it is defined in the current request-local context.
   */
  def get[A](key: Key[A]): Option[A] = env.get(key)

  /**
   * Retrieve the current definition of a key if it is defined.
   * If it is not defined, `orElse` is evaluated and returned.
   */
  def getOrElse[A](key: Key[A], orElse: () => A): A =
    env.getOrElse(key, orElse)

  /**
   * Tells whether `key` is defined in the current request-local
   * context.
   */
  def contains[A](key: Key[A]): Boolean = env.contains(key)

  /**
   * Bind `value` to `key` in the scope of `fn`.
   */
  def let[A, R](key: Key[A], value: A)(fn: => R): R =
    local.let(env.bound(key, value))(fn)

  /**
   * Bind two keys and values in the scope of `fn`.
   */
  def let[A, B, R](key1: Key[A], value1: A, key2: Key[B], value2: B)(fn: => R): R =
    local.let(env.bound(key1, value1).bound(key2, value2))(fn)

  /**
   * Bind the given environment.
   */
  private[finagle] def let[R](env1: Env)(fn: => R): R =
    local.let(OrElse(env1, env))(fn)

  /**
   * Unbind the passed-in keys, in the scope of `fn`.
   */
  def letClear[R](keys: Key[_]*)(fn: => R): R = {
    val newEnv = keys.foldLeft(env) {
      case (e, k) => e.cleared(k)
    }
    local.let(newEnv)(fn)
  }

  /**
   * Clears all bindings in the scope of `fn`.
   *
   * For example:
   * {{{
   *   context.let(Key1, "value1") {
   *     context.let(Key2, "something else") {
   *       context.letClear() {
   *         // context.contains(Key1) == false
   *         // context.contains(Key2) == false
   *       }
   *       // context(Key1) == "value1"
   *       // context(Key2) == "something else"
   *     }
   *   }
   * }}}
   */
  def letClear[R]()(fn: => R): R =
    local.let(Empty) {
      fn
    }

}

/**
 * A marshalled context contains bindings that may be
 * marshalled and sent across process boundaries. A set
 * of marshalled bindings may be restored in the local
 * environment. Thus we can use marshalled contexts to
 * propagate a set of bindings across a whole request
 * tree.
 */
final class MarshalledContext extends Context {
  /**
   * Keys in MarshalledContext must provide a marshaller
   * and unmarshaller.
   */

  abstract class Key[A](val id: String) {
    /**
     * A unique identifier defining this marshaller. This is
     * transmitted together with marshalled values in order to
     * pick the the appropriate unmarshaller for a given value.
     */
    final val marshalId: Buf = Buf.ByteBuffer.coerce(Buf.Utf8(id))

    /**
     * Marshal an A-typed value into a Buf.
     */
    def marshal(value: A): Buf

    /**
     * Attempt to unmarshal an A-typed context value.
     */
    def tryUnmarshal(buf: Buf): Try[A]
  }

  /**
   * A translucent environment is capable of storing key/value pairs
   * to be (possibly) unmarshalled later.
   */
  case class Translucent(next: Env, marshalId: Buf, marshalled: Buf) extends Env {
    @volatile private var cachedEnv: Env = null

    private def env[A](key: Key[A]): Env = {
      if (cachedEnv != null) cachedEnv
      else if (key.marshalId != marshalId) next
      else (key.tryUnmarshal(marshalled): Try[A]) match {
        case Return(value) =>
          cachedEnv = Bound(next, key, value)
          cachedEnv
        case Throw(_) =>
          // Should we omit the context altogether when this happens?
          // Should we log some warnings?
          next
      }
    }

    def apply[A](key: Key[A]): A = env(key).apply(key)
    def get[A](key: Key[A]): Option[A] = env(key).get(key)
    def getOrElse[A](key: Key[A], orElse: () => A): A = env(key).getOrElse(key, orElse)
    def contains[A](key: Key[A]): Boolean = env(key).contains(key)

    override def toString =
      if (cachedEnv != null) cachedEnv.toString else {
        val Buf.Utf8(id8) = marshalId
        s"Translucent(${id8}(${marshalled.length})) :: $next"
      }
  }

  private def marshalMap(env: Env, map: mutable.Map[Buf, Buf]): Unit =
    env match {
      case Bound(next, key, value) =>
        marshalMap(next, map)
        map.put(key.marshalId, key.marshal(value))
      case Translucent(next, id, marshalled) =>
        marshalMap(next, map)
        map.put(id, marshalled)
      case OrElse(left, right) =>
        marshalMap(right, map)
        marshalMap(left, map)
      case Cleared(next, key) =>
        marshalMap(next, map)
        map.remove(key.marshalId)
      case Empty =>
        ()
  }

  /**
   * Store into the current environment a set of marshalled
   * bindings and run `fn`. Bindings are unmarshalled on demand.
   */
  def letUnmarshal[R](contexts: Iterable[(Buf, Buf)])(fn: => R): R = {
    val u = new Unmarshaller(env)
    for ((id, marshalled) <- contexts)
      u.put(id, marshalled)
    let(u.build)(fn)
  }

  /**
   * Marshal the `env` into a set of (id, value) pairs.
   */
  def marshal(env: Env): Iterable[(Buf, Buf)] = {
    val map = mutable.Map[Buf, Buf]()
    marshalMap(env, map)
    map
  }

  /**
   * Marshal the current environment into a set of (id, value) pairs.
   */
  def marshal(): Iterable[(Buf, Buf)] =
    marshal(env)

  /**
   * Produce an environment consisting of the given marshalled
   * (id, value) pairs. They are unmarshalled on demand.
   */
  def unmarshal(contexts: Iterable[(Buf, Buf)]): Env = {
    val builder = new Unmarshaller
    for ((id, marshalled) <- contexts)
      builder.put(id, marshalled)
    builder.build
  }

  /**
   * An Unmarshaller gradually builds up an environment from
   * a set of (id, value) pairs.
   */
  class Unmarshaller(init: Env) {
    def this() = this(Empty)

    private[this] var env = init

    def put(id: Buf, marshalled: Buf) {
      // Copy the Bufs to avoid indirectly keeping a reference to Netty internal buffer (big)
      env = Translucent(env, copy(id), copy(marshalled))
    }

    def build: Env = env

    private[this] def copy(buf: Buf): Buf = buf match {
      case ChannelBufferBuf(cb) => Buf.ByteBuffer.Shared(cb.toByteBuffer)
      case _ => buf
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy