com.twitter.finagle.builder.ClientBuilder.scala Maven / Gradle / Ivy
The newest version!
package com.twitter.finagle.builder
import com.twitter.finagle._
import com.twitter.finagle.client.{DefaultPool, StackClient, Transporter}
import com.twitter.finagle.factory.TimeoutFactory
import com.twitter.finagle.filter.ExceptionSourceFilter
import com.twitter.finagle.loadbalancer.{LoadBalancerFactory, WeightedLoadBalancerFactory}
import com.twitter.finagle.netty3.Netty3Transporter
import com.twitter.finagle.service._
import com.twitter.finagle.ssl.{Engine, Ssl}
import com.twitter.finagle.stack.nilStack
import com.twitter.finagle.stats.{
NullStatsReceiver, ClientStatsReceiver, StatsReceiver, RollupStatsReceiver}
import com.twitter.finagle.tracing.NullTracer
import com.twitter.finagle.transport.Transport
import com.twitter.finagle.util._
import com.twitter.util.TimeConversions._
import com.twitter.util.{Duration, Future, NullMonitor, Time, Var, Try}
import java.net.SocketAddress
import java.util.concurrent.atomic.AtomicBoolean
import java.util.logging.Level
import javax.net.ssl.SSLContext
import org.jboss.netty.channel.{Channel, ChannelFactory}
import scala.annotation.implicitNotFound
/**
* Factory for [[com.twitter.finagle.builder.ClientBuilder]] instances
*/
object ClientBuilder {
type Complete[Req, Rep] =
ClientBuilder[Req, Rep, ClientConfig.Yes, ClientConfig.Yes, ClientConfig.Yes]
type NoCluster[Req, Rep] =
ClientBuilder[Req, Rep, Nothing, ClientConfig.Yes, ClientConfig.Yes]
type NoCodec =
ClientBuilder[_, _, ClientConfig.Yes, Nothing, ClientConfig.Yes]
def apply() = new ClientBuilder()
/**
* Used for Java access.
*/
def get() = apply()
/**
* Provides a typesafe `build` for Java.
*/
def safeBuild[Req, Rep](builder: Complete[Req, Rep]): Service[Req, Rep] =
builder.build()(ClientConfigEvidence.FullyConfigured)
/**
* Provides a typesafe `buildFactory` for Java.
*/
def safeBuildFactory[Req, Rep](builder: Complete[Req, Rep]): ServiceFactory[Req, Rep] =
builder.buildFactory()(ClientConfigEvidence.FullyConfigured)
}
object ClientConfig {
sealed abstract trait Yes
type FullySpecified[Req, Rep] = ClientConfig[Req, Rep, Yes, Yes, Yes]
val DefaultName = "client"
def nilClient[Req, Rep] = new Client[Req, Rep] {
def newClient(dest: Name, label: String): ServiceFactory[Req, Rep] =
ServiceFactory(() => Future.value(Service.mk[Req, Rep](_ => Future.exception(
new Exception("unimplemented")))))
}
// params specific to ClientBuilder
case class DestName(name: Name)
implicit object DestName extends Stack.Param[DestName] {
val default = DestName(Name.empty)
}
case class GlobalTimeout(timeout: Duration)
implicit object GlobalTimeout extends Stack.Param[GlobalTimeout] {
val default = GlobalTimeout(Duration.Top)
}
case class Retries(policy: RetryPolicy[Try[Nothing]])
implicit object Retries extends Stack.Param[Retries] {
private[this] val none = new RetryPolicy[Try[Nothing]] {
def apply(t: Try[Nothing]) = None
}
val default = Retries(none)
}
case class Daemonize(onOrOff: Boolean)
implicit object Daemonize extends Stack.Param[Daemonize] {
val default = Daemonize(true)
}
case class FailureAccrualFac(fac: com.twitter.util.Timer => ServiceFactoryWrapper)
implicit object FailureAccrualFac extends Stack.Param[FailureAccrualFac] {
val default = FailureAccrualFac(Function.const(ServiceFactoryWrapper.identity)_)
}
case class FailFast(onOrOff: Boolean)
implicit object FailFast extends Stack.Param[FailFast] {
val default = FailFast(true)
}
case class MonitorFactory(mFactory: String => com.twitter.util.Monitor)
implicit object MonitorFactory extends Stack.Param[MonitorFactory] {
val default = MonitorFactory(_ => NullMonitor)
}
// historical defaults for ClientBuilder
val DefaultParams = Stack.Params.empty +
param.Stats(NullStatsReceiver) +
param.Label(DefaultName) +
DefaultPool.Param(low = 1, high = Int.MaxValue,
bufferSize = 0, idleTime = 5.seconds, maxWaiters = Int.MaxValue) +
param.Tracer(NullTracer) +
param.Monitor(NullMonitor) +
param.Reporter(NullReporterFactory) +
Daemonize(false)
}
@implicitNotFound("Builder is not fully configured: Cluster: ${HasCluster}, Codec: ${HasCodec}, HostConnectionLimit: ${HasHostConnectionLimit}")
private[builder] trait ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]
private[builder] object ClientConfigEvidence {
implicit object FullyConfigured extends ClientConfigEvidence[ClientConfig.Yes, ClientConfig.Yes, ClientConfig.Yes]
}
/**
* TODO: do we really need to specify HasCodec? -- it's implied in a
* way by the proper Req, Rep.
*
* Note: these are documented in ClientBuilder, as that is where they
* are accessed by the end-user.
*/
private[builder] final class ClientConfig[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit]
/**
* Provides a class for building clients. The main class to use is
* [[com.twitter.finagle.builder.ClientBuilder]], as so
*
*/
/**
* A builder of Finagle [[com.twitter.finagle.Client Clients]].
*
* {{{
* val client = ClientBuilder()
* .codec(Http)
* .hosts("localhost:10000,localhost:10001,localhost:10003")
* .hostConnectionLimit(1)
* .tcpConnectTimeout(1.second) // max time to spend establishing a TCP connection.
* .retries(2) // (1) per-request retries
* .reportTo(new OstrichStatsReceiver) // export host-level load data to ostrich
* .logger(Logger.getLogger("http"))
* .build()
* }}}
*
* The `ClientBuilder` requires the definition of `cluster`, `codec`,
* and `hostConnectionLimit`. In Scala, these are statically type
* checked, and in Java the lack of any of the above causes a runtime
* error.
*
* The `build` method uses an implicit argument to statically
* typecheck the builder (to ensure completeness, see above). The Java
* compiler cannot provide such implicit, so we provide a separate
* function in Java to accomplish this. Thus, the Java code for the
* above is
*
* {{{
* Service service =
* ClientBuilder.safeBuild(
* ClientBuilder.get()
* .codec(new Http())
* .hosts("localhost:10000,localhost:10001,localhost:10003")
* .hostConnectionLimit(1)
* .tcpConnectTimeout(1.second)
* .retries(2)
* .reportTo(new OstrichStatsReceiver())
* .logger(Logger.getLogger("http")))
* }}}
*
* Alternatively, using the `unsafeBuild` method on `ClientBuilder`
* verifies the builder dynamically, resulting in a runtime error
* instead of a compiler error.
*
* =Defaults=
*
* The following defaults are applied to clients constructed via ClientBuilder,
* unless overridden with the corresponding method. These defaults were chosen
* carefully so as to work well for most use cases.
*
* Commonly-configured options:
*
* - `connectTimeout`: [[com.twitter.util.Duration]].Top
* - `tcpConnectTimeout`: 1 second
* - `requestTimeout`: [[com.twitter.util.Duration]].Top
* - `timeout`: [[com.twitter.util.Duration]].Top
* - `hostConnectionLimit`: Int.MaxValue
* - `hostConnectionCoresize`: 0
* - `hostConnectionIdleTime`: [[com.twitter.util.Duration]].Top
* - `hostConnectionMaxWaiters`: Int.MaxValue
* - `failFast`: true
* - `failureAccrualParams`, `failureAccrual`, `failureAccrualFactory`:
* `numFailures` = 5, `markDeadFor` = 5 seconds
*
* Advanced options:
*
* *Before changing any of these, make sure that you know exactly how they will
* affect your application -- these options are typically only changed by expert
* users.*
*
* - `keepAlive`: Unspecified, in which case the Java default of `false` is used
* (http://docs.oracle.com/javase/7/docs/api/java/net/StandardSocketOptions.html?is-external=true#SO_KEEPALIVE)
* - `readerIdleTimeout`: [[com.twitter.util.Duration]].Top
* - `writerIdleTimeout`: [[com.twitter.util.Duration]].Top
* - `hostConnectionMaxIdleTime`: [[com.twitter.util.Duration]].Top
* - `hostConnectionMaxLifeTime`: [[com.twitter.util.Duration]].Top
* - `sendBufferSize`, `recvBufferSize`: OS-defined default value
*
* Please see the Finagle user guide for information on a newer set of
* client-construction APIs introduced in Finagle v6:
* http://twitter.github.io/finagle/guide/FAQ.html#configuring-finagle6
*/
class ClientBuilder[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit] private[finagle](
params: Stack.Params,
mk: Stack.Params => Client[Req, Rep]
) {
import ClientConfig._
import com.twitter.finagle.param._
// Convenient aliases.
type FullySpecifiedConfig = FullySpecified[Req, Rep]
type ThisConfig = ClientConfig[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit]
type This = ClientBuilder[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit]
private[builder] def this() = this(ClientConfig.DefaultParams, Function.const(ClientConfig.nilClient)_)
override def toString() = "ClientBuilder(%s)".format(params)
protected def copy[Req1, Rep1, HasCluster1, HasCodec1, HasHostConnectionLimit1](
ps: Stack.Params,
newClient: Stack.Params => Client[Req1, Rep1]
): ClientBuilder[Req1, Rep1, HasCluster1, HasCodec1, HasHostConnectionLimit1] =
new ClientBuilder(ps, newClient)
protected def configured[P: Stack.Param, HasCluster1, HasCodec1, HasHostConnectionLimit1](
param: P
): ClientBuilder[Req, Rep, HasCluster1, HasCodec1, HasHostConnectionLimit1] =
copy(params + param, mk)
/**
* Specify the set of hosts to connect this client to. Requests
* will be load balanced across these. This is a shorthand form for
* specifying a cluster.
*
* One of the {{hosts}} variations or direct specification of the
* cluster (via {{cluster}}) is required.
*
* @param hostNamePortcombinations comma-separated "host:port"
* string.
*/
def hosts(
hostnamePortCombinations: String
): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] = {
val addresses = InetSocketAddressUtil.parseHosts(hostnamePortCombinations)
hosts(addresses)
}
/**
* A variant of {{hosts}} that takes a sequence of
* [[java.net.SocketAddress]] instead.
*/
def hosts(
addrs: Seq[SocketAddress]
): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
dest(Name.bound(addrs:_*))
/**
* A convenience method for specifying a one-host
* [[java.net.SocketAddress]] client.
*/
def hosts(
address: SocketAddress
): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
hosts(Seq(address))
/**
* The logical destination of requests dispatched through this
* client, as evaluated by a resolver. If the name evaluates a
* label, this replaces the builder's current name.
*/
def dest(
addr: String
): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] = {
Resolver.evalLabeled(addr) match {
case (n, "") => dest(n)
case (n, l) =>
val Label(label) = params[Label]
val cb =
if (label.isEmpty || l != addr)
this.name(l)
else
this
cb.dest(n)
}
}
/**
* The logical destination of requests dispatched through this
* client.
*/
def dest(
name: Name
): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
configured(DestName(name))
/**
* Specify a cluster directly. A
* [[com.twitter.finagle.builder.Cluster]] defines a dynamic
* mechanism for specifying a set of endpoints to which this client
* remains connected.
*/
def cluster(
cluster: Cluster[SocketAddress]
): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
group(Group.fromCluster(cluster))
def group(
group: Group[SocketAddress]
): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
dest(Name.fromGroup(group))
def loadBalancer(lbf: LoadBalancerFactory): This =
loadBalancer(lbf.toWeighted)
/**
* Specify a load balancer. The load balancer implements
* a strategy for choosing one from a set of hosts to service a request
*/
def loadBalancer(loadBalancer: WeightedLoadBalancerFactory): This =
configured(LoadBalancerFactory.Param(loadBalancer))
/**
* Specify the codec. The codec implements the network protocol
* used by the client, and consequently determines the {{Req}} and {{Rep}}
* type variables. One of the codec variations is required.
*/
def codec[Req1, Rep1](
codec: Codec[Req1, Rep1]
): ClientBuilder[Req1, Rep1, HasCluster, Yes, HasHostConnectionLimit] =
this.codec(Function.const(codec)(_))
/**
* A variation of {{codec}} that supports codec factories. This is
* used by codecs that need dynamic construction, but should be
* transparent to the user.
*/
def codec[Req1, Rep1](
codecFactory: CodecFactory[Req1, Rep1]
): ClientBuilder[Req1, Rep1, HasCluster, Yes, HasHostConnectionLimit] =
this.codec(codecFactory.client)
/**
* A variation of codec for codecs that support only client-codecs.
*/
def codec[Req1, Rep1](
codecFactory: CodecFactory[Req1, Rep1]#Client
): ClientBuilder[Req1, Rep1, HasCluster, Yes, HasHostConnectionLimit] =
_stack({ prms =>
val Label(name) = prms[Label]
val Timer(timer) = params[Timer]
val codec = codecFactory(ClientCodecConfig(name))
val prepConn = new Stack.Simple[ServiceFactory[Req1, Rep1]] {
val role = StackClient.Role.prepConn
val description = "Connection preparation phase as defined by a Codec"
def make(next: ServiceFactory[Req1, Rep1])(implicit params: Params) = {
val Stats(stats) = params[Stats]
val underlying = codec.prepareConnFactory(next)
new ServiceFactoryProxy(underlying) {
val stat = stats.stat("codec_connection_preparation_latency_ms")
override def apply(conn: ClientConnection) = {
val begin = Time.now
super.apply(conn) ensure {
stat.add((Time.now - begin).inMilliseconds)
}
}
}
}
}
val clientStack = {
val stack = StackClient.newStack[Req1, Rep1]
.replace(StackClient.Role.prepConn, prepConn)
.replace(StackClient.Role.prepFactory, (next: ServiceFactory[Req1, Rep1]) =>
codec.prepareServiceFactory(next))
// transform stack wrt. failure accrual
val newStack =
if (prms.contains[FailureAccrualFac]) {
val FailureAccrualFac(fac) = prms[FailureAccrualFac]
stack.replace(FailureAccrualFactory.role, (next: ServiceFactory[Req1, Rep1]) =>
fac(timer) andThen next)
} else {
stack
}
// disable failFast if the codec requests it or it is
// disabled via the ClientBuilder paramater.
val FailFast(failFast) = prms[FailFast]
if (!codec.failFastOk || !failFast) newStack.remove(FailFastFactory.role)
else newStack
}
new StackClient[Req1, Rep1](clientStack, prms) {
protected type In = Any
protected type Out = Any
protected val newTransporter: Stack.Params => Transporter[Any, Any] = { ps =>
val Stats(stats) = ps[Stats]
val newTransport = (ch: Channel) => codec.newClientTransport(ch, stats)
Netty3Transporter[Any, Any](codec.pipelineFactory,
ps + Netty3Transporter.TransportFactory(newTransport))
}
protected val newDispatcher: Stack.Params => Dispatcher =
Function.const(trans => codec.newClientDispatcher(trans))
}
})
/** Used internally by `codec` to require hostConnectionLimit */
private[this] def _stack[Req1, Rep1](
mk: Stack.Params => Client[Req1, Rep1]
): ClientBuilder[Req1, Rep1, HasCluster, Yes, HasHostConnectionLimit] =
copy(params, mk)
/**
* Overrides the stack and [[com.twitter.finagle.Client]] that will be used
* by this builder. The `mk` function is passed the state of configuration
* when `build` is called. There is no guarantee that all the builder parameters
* may be used by the client created by `mk`, it is up to the discretion of
* the client and protocol implementation. For example, most of connection pool
* parameters (hostConnectionLimit, etc) don't apply to [[com.twitter.finagle.ThriftMux]].
* For this reason, the builder assumes that hostConnectionLimit is irrelevant
* when using `stack`.
*/
def stack[Req1, Rep1](
mk: Stack.Params => Client[Req1, Rep1]
): ClientBuilder[Req1, Rep1, HasCluster, Yes, Yes] =
copy(params, mk)
@deprecated("Use tcpConnectTimeout instead", "5.0.1")
def connectionTimeout(duration: Duration): This = tcpConnectTimeout(duration)
/**
* Specify the TCP connection timeout.
*/
def tcpConnectTimeout(duration: Duration): This =
configured(Transporter.ConnectTimeout(duration))
/**
* The request timeout is the time given to a *single* request (if
* there are retries, they each get a fresh request timeout). The
* timeout is applied only after a connection has been acquired.
* That is: it is applied to the interval between the dispatch of
* the request and the receipt of the response.
*/
def requestTimeout(duration: Duration): This =
configured(TimeoutFilter.Param(duration))
/**
* The connect timeout is the timeout applied to the acquisition of
* a Service. This includes both queueing time (eg. because we
* cannot create more connections due to {{hostConnectionLimit}} and
* there are more than {{hostConnectionLimit}} requests outstanding)
* as well as physical connection time. Futures returned from
* {{factory()}} will always be satisfied within this timeout.
*/
def connectTimeout(duration: Duration): This =
configured(TimeoutFactory.Param(duration))
/**
* Total request timeout. This timeout is applied from the issuance
* of a request (through {{service(request)}}) until the
* satisfaction of that reply future. No request will take longer
* than this.
*
* Applicable only to service-builds ({{build()}})
*/
def timeout(duration: Duration): This =
configured(GlobalTimeout(duration))
/**
* Apply TCP keepAlive ({{SO_KEEPALIVE}} socket option).
*/
def keepAlive(value: Boolean): This =
configured(params[Transport.Liveness].copy(keepAlive = Some(value)))
/**
* The maximum time a connection may have received no data.
*/
def readerIdleTimeout(duration: Duration): This =
configured(params[Transport.Liveness].copy(readTimeout = duration))
/**
* The maximum time a connection may not have sent any data.
*/
def writerIdleTimeout(duration: Duration): This =
configured(params[Transport.Liveness].copy(writeTimeout = duration))
/**
* Report stats to the given {{StatsReceiver}}. This will report
* verbose global statistics and counters, that in turn may be
* exported to monitoring applications.
* NB: per hosts statistics will *NOT* be exported to this receiver
* @see reportHostStats(receiver: StatsReceiver)
*/
def reportTo(receiver: StatsReceiver): This =
configured(Stats(receiver))
/**
* Report per host stats to the given {{StatsReceiver}}.
* The statsReceiver will be scoped per client, like this:
* client/connect_latency_ms_max/0.0.0.0:64754
*/
def reportHostStats(receiver: StatsReceiver): This =
configured(LoadBalancerFactory.HostStats(receiver))
/**
* Give a meaningful name to the client. Required.
*/
def name(value: String): This =
configured(Label(value))
/**
* The maximum number of connections that are allowed per host.
* Required. Finagle guarantees to never have more active
* connections than this limit.
*/
def hostConnectionLimit(value: Int): ClientBuilder[Req, Rep, HasCluster, HasCodec, Yes] =
configured(params[DefaultPool.Param].copy(high = value))
/**
* The core size of the connection pool: the pool is not shrinked below this limit.
*/
def hostConnectionCoresize(value: Int): This =
configured(params[DefaultPool.Param].copy(low = value))
/**
* The amount of time a connection is allowed to linger (when it
* otherwise would have been closed by the pool) before being
* closed.
*/
def hostConnectionIdleTime(timeout: Duration): This =
configured(params[DefaultPool.Param].copy(idleTime = timeout))
/**
* The maximum queue size for the connection pool.
*/
def hostConnectionMaxWaiters(nWaiters: Int): This =
configured(params[DefaultPool.Param].copy(maxWaiters = nWaiters))
/**
* The maximum time a connection is allowed to linger unused.
*/
def hostConnectionMaxIdleTime(timeout: Duration): This =
configured(params[ExpiringService.Param].copy(idleTime = timeout))
/**
* The maximum time a connection is allowed to exist, regardless of occupancy.
*/
def hostConnectionMaxLifeTime(timeout: Duration): This =
configured(params[ExpiringService.Param].copy(lifeTime = timeout))
/**
* Experimental option to buffer `size` connections from the pool.
* The buffer is fast and lock-free, reducing contention for
* services with very high requests rates. The buffer size should
* be sized roughly to the expected concurrency. Buffers sized by
* power-of-twos may be faster due to the use of modular
* arithmetic.
*
* '''Note:''' This will be integrated into the mainline pool, at
* which time the experimental option will go away.
*/
def expHostConnectionBufferSize(size: Int): This =
configured(params[DefaultPool.Param].copy(bufferSize = size))
/**
* The number of retries applied. Only applicable to service-builds ({{build()}})
*/
def retries(value: Int): This =
retryPolicy(RetryPolicy.tries(value))
def retryPolicy(value: RetryPolicy[Try[Nothing]]): This =
configured(Retries(value))
/**
* Sets the TCP send buffer size.
*/
def sendBufferSize(value: Int): This =
configured(params[Transport.BufferSizes].copy(send = Some(value)))
/**
* Sets the TCP recv buffer size.
*/
def recvBufferSize(value: Int): This =
configured(params[Transport.BufferSizes].copy(recv = Some(value)))
/**
* Use the given channel factory instead of the default. Note that
* when using a non-default ChannelFactory, finagle can't
* meaningfully reference count factory usage, and so the caller is
* responsible for calling ``releaseExternalResources()''.
*/
def channelFactory(cf: ChannelFactory): This =
configured(Netty3Transporter.ChannelFactory(cf))
/**
* Encrypt the connection with SSL. Hostname verification will be
* provided against the given hostname.
*/
def tls(hostname: String): This = {
configured(Transport.TLSEngine(Some({ () => Ssl.client() })))
.configured(Transporter.TLSHostname(Some(hostname)))
}
/**
* Encrypt the connection with SSL. The Engine to use can be passed into the client.
* This allows the user to use client certificates
* No SSL Hostname Validation is performed
*/
def tls(sslContext: SSLContext): This =
configured(Transport.TLSEngine(Some({ () => Ssl.client(sslContext) })))
/**
* Encrypt the connection with SSL. The Engine to use can be passed into the client.
* This allows the user to use client certificates
* SSL Hostname Validation is performed, on the passed in hostname
*/
def tls(sslContext: SSLContext, hostname: Option[String]): This = {
configured((Transport.TLSEngine(Some({ () => Ssl.client(sslContext) }))))
.configured(Transporter.TLSHostname(hostname))
}
/**
* Do not perform TLS validation. Probably dangerous.
*/
def tlsWithoutValidation(): This =
configured(Transport.TLSEngine(Some({ () => Ssl.clientWithoutCertificateValidation() })))
/**
* Make connections via the given HTTP proxy.
* If this is defined concurrently with socksProxy, the order in which they are applied is undefined.
*/
def httpProxy(httpProxy: SocketAddress): This =
configured(Transporter.HttpProxy(Some(httpProxy)))
/**
* Make connections via the given SOCKS proxy.
* If this is defined concurrently with httpProxy, the order in which they are applied is undefined.
*/
def socksProxy(socksProxy: SocketAddress): This =
configured(params[Transporter.SocksProxy].copy(sa = Some(socksProxy)))
/**
* For the socks proxy use this username for authentication.
* socksPassword and socksProxy must be set as well
*/
def socksUsernameAndPassword(credentials: (String,String)): This =
configured(params[Transporter.SocksProxy].copy(credentials = Some(credentials)))
/**
* Specifies a tracer that receives trace events.
* See [[com.twitter.finagle.tracing]] for details.
*/
@deprecated("Use tracer() instead", "7.0.0")
def tracerFactory(factory: com.twitter.finagle.tracing.Tracer.Factory): This =
tracer(factory())
// API compatibility method
@deprecated("Use tracer() instead", "7.0.0")
def tracerFactory(t: com.twitter.finagle.tracing.Tracer): This =
tracer(t)
/**
* Specifies a tracer that receives trace events.
* See [[com.twitter.finagle.tracing]] for details.
*/
def tracer(t: com.twitter.finagle.tracing.Tracer): This =
configured(Tracer(t))
def monitor(mFactory: String => com.twitter.util.Monitor): This =
configured(MonitorFactory(mFactory))
/**
* Log very detailed debug information to the given logger.
*/
def logger(logger: java.util.logging.Logger): This =
configured(Logger(logger))
/**
* Use the given paramters for failure accrual. The first parameter
* is the number of *successive* failures that are required to mark
* a host failed. The second paramter specifies how long the host
* is dead for, once marked.
*/
def failureAccrualParams(pair: (Int, Duration)): This = {
val (numFailures, markDeadFor) = pair
failureAccrualFactory(FailureAccrualFactory.wrapper(numFailures, markDeadFor)(_))
}
def failureAccrual(failureAccrual: ServiceFactoryWrapper): This =
failureAccrualFactory { _ => failureAccrual }
def failureAccrualFactory(factory: com.twitter.util.Timer => ServiceFactoryWrapper): This =
configured(FailureAccrualFac(factory))
@deprecated(
"No longer experimental: Use failFast()." +
"The new default value is true, so replace .expFailFast(true) with nothing at all",
"5.3.10")
def expFailFast(onOrOff: Boolean): This =
failFast(onOrOff)
/**
* Marks a host dead on connection failure. The host remains dead
* until we succesfully connect. Intermediate connection attempts
* *are* respected, but host availability is turned off during the
* reconnection period.
*/
def failFast(onOrOff: Boolean): This =
configured(FailFast(onOrOff))
/**
* When true, the client is daemonized. As with java threads, a
* process can exit only when all remaining clients are daemonized.
* False by default.
*/
def daemon(daemonize: Boolean): This =
configured(Daemonize(daemonize))
/*** BUILD ***/
// still used by finagle-memcached.
private[finagle] lazy val statsReceiver = {
val Stats(sr) = params[Stats]
val Label(label) = params[Label]
sr.scope(label)
}
/**
* Construct a ServiceFactory. This is useful for stateful protocols
* (e.g., those that support transactions or authentication).
*/
def buildFactory()(
implicit THE_BUILDER_IS_NOT_FULLY_SPECIFIED_SEE_ClientBuilder_DOCUMENTATION:
ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]
): ServiceFactory[Req, Rep] = {
val Label(label) = params[Label]
val Daemonize(daemon) = params[Daemonize]
val Logger(logger) = params[Logger]
val DestName(dest) = params[DestName]
val MonitorFactory(mFactory) = params[MonitorFactory]
val clientParams = params +
Monitor(mFactory(label))
val factory = mk(clientParams).newClient(dest, label)
if (!daemon) ExitGuard.guard()
new ServiceFactoryProxy[Req, Rep](factory) {
private[this] val closed = new AtomicBoolean(false)
override def close(deadline: Time): Future[Unit] = {
if (!closed.compareAndSet(false, true)) {
logger.log(Level.WARNING, "Close on ServiceFactory called multiple times!",
new Exception/*stack trace please*/)
return Future.exception(new IllegalStateException)
}
super.close(deadline) ensure {
if (!daemon) ExitGuard.unguard()
}
}
}
}
@deprecated("Used for ABI compat", "5.0.1")
def buildFactory(
THE_BUILDER_IS_NOT_FULLY_SPECIFIED_SEE_ClientBuilder_DOCUMENTATION:
ThisConfig =:= FullySpecifiedConfig
): ServiceFactory[Req, Rep] = buildFactory()(
new ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]{})
/**
* Construct a Service.
*/
def build()(
implicit THE_BUILDER_IS_NOT_FULLY_SPECIFIED_SEE_ClientBuilder_DOCUMENTATION:
ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]
): Service[Req, Rep] = {
val service: Service[Req, Rep] = new FactoryToService[Req, Rep](buildFactory())
val Label(label) = params[Label]
val Timer(timer) = params[Timer]
val exceptionSourceFilter = new ExceptionSourceFilter[Req, Rep](label)
// We keep the retrying filter after the load balancer so we can
// retry across different hosts rather than the same one repeatedly.
val filter = exceptionSourceFilter andThen globalTimeoutFilter(timer) andThen retryFilter(timer)
val composed = filter andThen service
new ServiceProxy[Req, Rep](composed) {
private[this] val released = new AtomicBoolean(false)
override def close(deadline: Time): Future[Unit] = {
if (!released.compareAndSet(false, true)) {
val Logger(logger) = params[Logger]
logger.log(java.util.logging.Level.WARNING, "Release on Service called multiple times!",
new Exception/*stack trace please*/)
return Future.exception(new IllegalStateException)
}
super.close(deadline)
}
}
}
@deprecated("Used for ABI compat", "5.0.1")
def build(
THE_BUILDER_IS_NOT_FULLY_SPECIFIED_SEE_ClientBuilder_DOCUMENTATION:
ThisConfig =:= FullySpecifiedConfig
): Service[Req, Rep] = build()(
new ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]{})
private[this] def validated = {
if (!params.contains[DestName])
throw new IncompleteSpecification("No destination was specified")
this.asInstanceOf[ClientBuilder[Req, Rep, Yes, Yes, Yes]]
}
/**
* Construct a Service, with runtime checks for builder
* completeness.
*/
def unsafeBuild(): Service[Req, Rep] =
validated.build()
/**
* Construct a ServiceFactory, with runtime checks for builder
* completeness.
*/
def unsafeBuildFactory(): ServiceFactory[Req, Rep] =
validated.buildFactory()
private def retryFilter(timer: com.twitter.util.Timer) =
params[Retries] match {
case Retries(policy) if params.contains[Retries] =>
val Label(label) = params[Label]
val stats = new StatsFilter[Req, Rep](new RollupStatsReceiver(statsReceiver.scope("tries")))
val retries = new RetryingFilter[Req, Rep](policy, timer, statsReceiver)
stats andThen retries
case _ => identityFilter
}
private def globalTimeoutFilter(timer: com.twitter.util.Timer) = {
val GlobalTimeout(timeout) = params[GlobalTimeout]
if (timeout < Duration.Top) {
val exception = new GlobalRequestTimeoutException(timeout)
new TimeoutFilter[Req, Rep](timeout, exception, timer)
} else {
identityFilter
}
}
private val identityFilter = Filter.identity[Req, Rep]
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy