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

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

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

import com.google.common.base.Charsets
import com.twitter.finagle.stats.{DefaultStatsReceiver, StatsReceiver}
import com.twitter.logging.Logger
import com.twitter.util.NonFatal
import java.nio.{ByteBuffer, CharBuffer}
import java.nio.charset.{CoderResult, CharsetEncoder}
import java.security.{PrivilegedExceptionAction, AccessController}
import org.apache.thrift.protocol.{TProtocol, TProtocolFactory, TBinaryProtocol}
import org.apache.thrift.transport.TTransport

object Protocols {

  // based on guava's UnsignedBytes.getUnsafe()
  private[this] def getUnsafe: sun.misc.Unsafe = {
    try {
      sun.misc.Unsafe.getUnsafe()
    } catch {
      case NonFatal(_) => // try reflection instead
        try {
          AccessController.doPrivileged(new PrivilegedExceptionAction[sun.misc.Unsafe]() {
            def run(): sun.misc.Unsafe = {
              val k = classOf[sun.misc.Unsafe]
              for (f <- k.getDeclaredFields) {
                f.setAccessible(true)
                val x = f.get(null)
                if (k.isInstance(x)) {
                  return k.cast(x)
                }
              }
              throw new NoSuchFieldException("the Unsafe") // fall through to the catch block below
            }
          })
        } catch {
          case NonFatal(t) =>
            Logger.get().info("%s unable to initialize sun.misc.Unsafe", getClass.getName)
            null
        }
    }
  }

  private val unsafe: Option[sun.misc.Unsafe] = Option(getUnsafe)

  private[this] def optimizedBinarySupported: Boolean = unsafe.isDefined

  /**
   * Returns a `TProtocolFactory` that creates `TProtocol`s that
   * are wire-compatible with `TBinaryProtocol`.
   */
  def binaryFactory(
    strictRead: Boolean = false,
    strictWrite: Boolean = true,
    readLength: Int = 0,
    statsReceiver: StatsReceiver = DefaultStatsReceiver
  ): TProtocolFactory = {
    if (!optimizedBinarySupported) {
      new TBinaryProtocol.Factory(strictRead, strictWrite, readLength)
    } else {
      new TProtocolFactory {
        override def getProtocol(trans: TTransport): TProtocol = {
          val proto = new TFinagleBinaryProtocol(
            trans, strictRead, strictWrite, statsReceiver.scope("TFinagleBinaryProtocol"))
          if (readLength != 0) {
            proto.setReadLength(readLength)
          }
          proto
        }
      }
    }
  }

  def factory(statsReceiver: StatsReceiver = DefaultStatsReceiver): TProtocolFactory = {
    binaryFactory(statsReceiver = statsReceiver)
  }

  // Visible for testing purposes.
  private[thrift] object TFinagleBinaryProtocol {
    // zero-length strings are written to the wire as an i32 of its length, which is 0
    private val EmptyStringInBytes = Array[Byte](0, 0, 0, 0)

    // assume that most of our strings are mostly single byte utf8
    private val MultiByteMultiplierEstimate = 1.3f

    /** Only valid if unsafe is defined */
    private val StringValueOffset: Long = unsafe.map {
      _.objectFieldOffset(classOf[String].getDeclaredField("value"))
    }.getOrElse(Long.MinValue)

    /**
     * Note, some versions of the JDK's define `String.offset`,
     * while others do not and always use 0.
     */
    private val OffsetValueOffset: Long = unsafe.map { u =>
      try {
        u.objectFieldOffset(classOf[String].getDeclaredField("offset"))
      } catch {
        case NonFatal(_) => Long.MinValue
      }
    }.getOrElse(Long.MinValue)

    /**
     * Note, some versions of the JDK's define `String.count`,
     * while others do not and always use `value.length`.
     */
    private val CountValueOffset: Long = unsafe.map { u =>
      try {
        u.objectFieldOffset(classOf[String].getDeclaredField("count"))
      } catch {
        case NonFatal(_) => Long.MinValue
      }
    }.getOrElse(Long.MinValue)

    private val charsetEncoder = new ThreadLocal[CharsetEncoder] {
      override def initialValue() = Charsets.UTF_8.newEncoder()
    }

    // Visible for testing purposes
    private[thrift] val OutBufferSize = 4096

    private val outByteBuffer = new ThreadLocal[ByteBuffer] {
      override def initialValue() = ByteBuffer.allocate(OutBufferSize)
    }
  }

  /**
   * An implementation of TBinaryProtocol that optimizes `writeString`
   * to minimize object allocations.
   *
   * This specific speedup depends on sun.misc.Unsafe and will fall
   * back to standard TBinaryProtocol in the case when it is unavailable.
   *
   * Visible for testing purposes.
   */
  private[thrift] class TFinagleBinaryProtocol(
      trans: TTransport,
      strictRead: Boolean = false,
      strictWrite: Boolean = true,
      statsReceiver: StatsReceiver = DefaultStatsReceiver)
    extends TBinaryProtocol(
      trans,
      strictRead,
      strictWrite)
  {
    import TFinagleBinaryProtocol._

    private[this] val fastEncodeFailed = statsReceiver.counter("fast_encode_failed")
    private[this] val largerThanTlOutBuffer = statsReceiver.counter("larger_than_threadlocal_out_buffer")

    override def writeString(str: String) {
      if (str.length == 0) {
        trans.write(EmptyStringInBytes)
        return
      }
      // this is based on the CharsetEncoder code at:
      // http://psy-lob-saw.blogspot.co.nz/2013/04/writing-java-micro-benchmarks-with-jmh.html
      // we could probably do better than this via:
      // https://github.com/nitsanw/jmh-samples/blob/master/src/main/java/psy/lob/saw/utf8/CustomUtf8Encoder.java
      val u = unsafe.get
      val chars = u.getObject(str, StringValueOffset).asInstanceOf[Array[Char]]
      val offset = if (OffsetValueOffset == Long.MinValue) 0 else {
        u.getInt(str, OffsetValueOffset)
      }
      val count = if (CountValueOffset == Long.MinValue) chars.length else {
        u.getInt(str, CountValueOffset)
      }
      val charBuffer = CharBuffer.wrap(chars, offset, count)

      val out = if (count * MultiByteMultiplierEstimate <= OutBufferSize) {
        val o = outByteBuffer.get()
        o.clear()
        o
      } else {
        largerThanTlOutBuffer.incr()
        ByteBuffer.allocate((count * MultiByteMultiplierEstimate).toInt)
      }

      val csEncoder = charsetEncoder.get()
      csEncoder.reset()

      val result = csEncoder.encode(charBuffer, out, true)
      if (result != CoderResult.UNDERFLOW) {
        fastEncodeFailed.incr()
        super.writeString(str)
      } else {
        writeI32(out.position())
        trans.write(out.array(), 0, out.position())
      }
    }

    // Note: libthrift 0.5.0 has a bug when operating on ByteBuffer's with a non-zero arrayOffset.
    // We instead use the version from head that fixes this issue.
    override def writeBinary(bin: ByteBuffer) {
      val length = bin.limit() - bin.position()
      writeI32(length)
      trans.write(bin.array(), bin.position() + bin.arrayOffset(), length)
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy