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

com.twitter.finagle.thrift.ClientDeserializeCtx.scala Maven / Gradle / Ivy

There is a newer version: 24.2.0
Show newest version
package com.twitter.finagle.thrift

import com.twitter.finagle.context.Contexts
import com.twitter.finagle.service.ReqRep
import com.twitter.util.Try

/**
 * Used by Thrift and ThriftMux Client to facilitate giving
 * the Finagle stack access to various data that are computed
 * outside of Finagle's stack.
 *
 * This includes:
 *  - the deserialized forms of Thrift requests and responses.
 *  - the name of the rpc.
 *  - the elapsed time to serialize the request.
 *  - the elapsed time to deserialize the response.
 *
 * While this is thread-safe, it should only be used for the life
 * of a single request/response pair.
 *
 * When using [[https://twitter.github.io/scrooge/ Scrooge]] for code
 * generation, a proper `ClientDeserializeCtx` will be available
 * to code via `Contexts.local(ClientDeserializeCtx.Key)`.
 *
 * @note this class has evolved and it's name is now a bit too specific
 *       for its more expanded role.
 */
class ClientDeserializeCtx[Rep](val request: Any, replyDeserializer: Array[Byte] => Try[Rep])
    extends (Array[Byte] => ReqRep) {

  // Thread safety provided via synchronization on `this`.
  //   Note that coarse grained synchronization suffices here as
  //   there should be no contention between threads, though reads
  //   and writes are likely to happen on different threads.
  private[this] var deserialized: Try[Rep] = null
  private[this] var _rpcName: Option[String] = None
  // `Durations` are not used for these, to avoid object allocations.
  private[this] var serializationNanos: Long = Long.MinValue
  private[this] var deserializationNanos: Long = Long.MinValue

  /**
   * Deserialize the given bytes.
   *
   * Ensures that deserialization will only happen once for non fan-out responses
   * regardless of future inputs. If different bytes are seen on future calls,
   * this will still return the first deserialized result.
   */
  def deserialize(responseBytes: Array[Byte]): Try[Rep] = synchronized {
    if (deserialized == null) {
      val start = System.nanoTime
      deserialized = replyDeserializer(responseBytes)
      deserializationNanos = System.nanoTime - start
    }
    deserialized
  }

  def apply(responseBytes: Array[Byte]): ReqRep = {
    ReqRep(request, deserialize(responseBytes))
  }

  /**
   * Sets the name of the the rpc method.
   */
  def rpcName(name: String): Unit = {
    val asOption = Option(name)
    synchronized {
      _rpcName = asOption
    }
  }

  /**
   * Gets the rpc method's name, if set.
   */
  def rpcName: Option[String] = synchronized {
    _rpcName
  }

  /**
   * Applies deserializer to one response from bathed responses
   * @note: This won't update the `deserialized` content
   */
  private[finagle] def deserializeFromBatched(responseBytes: Array[Byte]): Try[Rep] = synchronized {
    replyDeserializer(responseBytes)
  }

  /**
   * Sets the merged deserialized response
   */
  private[finagle] def mergedDeserializedResponse(response: Try[Any]): Unit = synchronized {
    deserialized = response.asInstanceOf[Try[Rep]]
  }

  /**
   * Sets how long, in nanoseconds, it took for the client to
   * serialize the request into Thrift format.
   */
  def serializationTime(nanos: Long): Unit = synchronized {
    serializationNanos = nanos
  }

  /**
   * Gets how long, in nanoseconds, it took for the client to
   * serialize the request into Thrift format.
   *
   * Negative values indicate that this was not recorded and
   * as such, should be ignored.
   */
  def serializationTime: Long = synchronized {
    serializationNanos
  }

  /**
   * Gets how long, in nanoseconds, it took for the client to
   * [[deserialize]] the response from Thrift format.
   *
   * Negative values indicate that this was not recorded and
   * as such, should be ignored.
   */
  def deserializationTime: Long = synchronized {
    deserializationNanos
  }

}

object ClientDeserializeCtx {

  val Key: Contexts.local.Key[ClientDeserializeCtx[_]] =
    new Contexts.local.Key[ClientDeserializeCtx[_]]

  private val NullDeserializeFn: () => ClientDeserializeCtx[Nothing] = () => nullDeserializeCtx

  def get: ClientDeserializeCtx[_] = Contexts.local.getOrElse(Key, NullDeserializeFn)

  val nullDeserializeCtx: ClientDeserializeCtx[Nothing] =
    new ClientDeserializeCtx[Nothing](null, null) {
      override def rpcName(name: String): Unit = ()
      override def serializationTime(nanos: Long): Unit = ()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy