Source File
dialoptions.go
Belonging Package
google.golang.org/grpc
/*
*
* Copyright 2018 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package grpc
import (
internalbackoff
)
const (
// https://github.com/grpc/proposal/blob/master/A6-client-retries.md#limits-on-retries-and-hedges
defaultMaxCallAttempts = 5
)
func () {
internal.AddGlobalDialOptions = func( ...DialOption) {
globalDialOptions = append(globalDialOptions, ...)
}
internal.ClearGlobalDialOptions = func() {
globalDialOptions = nil
}
internal.AddGlobalPerTargetDialOptions = func( any) {
if , := .(perTargetDialOption); {
globalPerTargetDialOptions = append(globalPerTargetDialOptions, )
}
}
internal.ClearGlobalPerTargetDialOptions = func() {
globalPerTargetDialOptions = nil
}
internal.WithBinaryLogger = withBinaryLogger
internal.JoinDialOptions = newJoinDialOption
internal.DisableGlobalDialOptions = newDisableGlobalDialOptions
internal.WithBufferPool = withBufferPool
}
// dialOptions configure a Dial call. dialOptions are set by the DialOption
// values passed to Dial.
type dialOptions struct {
unaryInt UnaryClientInterceptor
streamInt StreamClientInterceptor
chainUnaryInts []UnaryClientInterceptor
chainStreamInts []StreamClientInterceptor
compressorV0 Compressor
dc Decompressor
bs internalbackoff.Strategy
block bool
returnLastError bool
timeout time.Duration
authority string
binaryLogger binarylog.Logger
copts transport.ConnectOptions
callOptions []CallOption
channelzParent channelz.Identifier
disableServiceConfig bool
disableRetry bool
disableHealthCheck bool
minConnectTimeout func() time.Duration
defaultServiceConfig *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON.
defaultServiceConfigRawJSON *string
resolvers []resolver.Builder
idleTimeout time.Duration
defaultScheme string
maxCallAttempts int
enableLocalDNSResolution bool // Specifies if target hostnames should be resolved when proxying is enabled.
useProxy bool // Specifies if a server should be connected via proxy.
}
// DialOption configures how we set up the connection.
type DialOption interface {
apply(*dialOptions)
}
var globalDialOptions []DialOption
// perTargetDialOption takes a parsed target and returns a dial option to apply.
//
// This gets called after NewClient() parses the target, and allows per target
// configuration set through a returned DialOption. The DialOption will not take
// effect if specifies a resolver builder, as that Dial Option is factored in
// while parsing target.
type perTargetDialOption interface {
// DialOption returns a Dial Option to apply.
DialOptionForTarget(parsedTarget url.URL) DialOption
}
var globalPerTargetDialOptions []perTargetDialOption
// EmptyDialOption does not alter the dial configuration. It can be embedded in
// another structure to build custom dial options.
//
// # Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type EmptyDialOption struct{}
func (EmptyDialOption) (*dialOptions) {}
type disableGlobalDialOptions struct{}
func (disableGlobalDialOptions) (*dialOptions) {}
// newDisableGlobalDialOptions returns a DialOption that prevents the ClientConn
// from applying the global DialOptions (set via AddGlobalDialOptions).
func () DialOption {
return &disableGlobalDialOptions{}
}
// funcDialOption wraps a function that modifies dialOptions into an
// implementation of the DialOption interface.
type funcDialOption struct {
f func(*dialOptions)
}
func ( *funcDialOption) ( *dialOptions) {
.f()
}
func ( func(*dialOptions)) *funcDialOption {
return &funcDialOption{
f: ,
}
}
type joinDialOption struct {
opts []DialOption
}
func ( *joinDialOption) ( *dialOptions) {
for , := range .opts {
.apply()
}
}
func ( ...DialOption) DialOption {
return &joinDialOption{opts: }
}
// WithSharedWriteBuffer allows reusing per-connection transport write buffer.
// If this option is set to true every connection will release the buffer after
// flushing the data on the wire.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ( bool) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.SharedWriteBuffer =
})
}
// WithWriteBufferSize determines how much data can be batched before doing a
// write on the wire. The default value for this buffer is 32KB.
//
// Zero or negative values will disable the write buffer such that each write
// will be on underlying connection. Note: A Send call may not directly
// translate to a write.
func ( int) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.WriteBufferSize =
})
}
// WithReadBufferSize lets you set the size of read buffer, this determines how
// much data can be read at most for each read syscall.
//
// The default value for this buffer is 32KB. Zero or negative values will
// disable read buffer for a connection so data framer can access the
// underlying conn directly.
func ( int) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.ReadBufferSize =
})
}
// WithInitialWindowSize returns a DialOption which sets the value for initial
// window size on a stream. The lower bound for window size is 64K and any value
// smaller than that will be ignored.
func ( int32) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.InitialWindowSize =
.copts.StaticWindowSize = true
})
}
// WithInitialConnWindowSize returns a DialOption which sets the value for
// initial window size on a connection. The lower bound for window size is 64K
// and any value smaller than that will be ignored.
func ( int32) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.InitialConnWindowSize =
.copts.StaticWindowSize = true
})
}
// WithStaticStreamWindowSize returns a DialOption which sets the initial
// stream window size to the value provided and disables dynamic flow control.
func ( int32) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.InitialWindowSize =
.copts.StaticWindowSize = true
})
}
// WithStaticConnWindowSize returns a DialOption which sets the initial
// connection window size to the value provided and disables dynamic flow
// control.
func ( int32) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.InitialConnWindowSize =
.copts.StaticWindowSize = true
})
}
// WithMaxMsgSize returns a DialOption which sets the maximum message size the
// client can receive.
//
// Deprecated: use WithDefaultCallOptions(MaxCallRecvMsgSize(s)) instead. Will
// be supported throughout 1.x.
func ( int) DialOption {
return WithDefaultCallOptions(MaxCallRecvMsgSize())
}
// WithDefaultCallOptions returns a DialOption which sets the default
// CallOptions for calls over the connection.
func ( ...CallOption) DialOption {
return newFuncDialOption(func( *dialOptions) {
.callOptions = append(.callOptions, ...)
})
}
// WithCodec returns a DialOption which sets a codec for message marshaling and
// unmarshaling.
//
// Deprecated: use WithDefaultCallOptions(ForceCodec(_)) instead. Will be
// supported throughout 1.x.
func ( Codec) DialOption {
return WithDefaultCallOptions(CallCustomCodec())
}
// WithCompressor returns a DialOption which sets a Compressor to use for
// message compression. It has lower priority than the compressor set by the
// UseCompressor CallOption.
//
// Deprecated: use UseCompressor instead. Will be supported throughout 1.x.
func ( Compressor) DialOption {
return newFuncDialOption(func( *dialOptions) {
.compressorV0 =
})
}
// WithDecompressor returns a DialOption which sets a Decompressor to use for
// incoming message decompression. If incoming response messages are encoded
// using the decompressor's Type(), it will be used. Otherwise, the message
// encoding will be used to look up the compressor registered via
// encoding.RegisterCompressor, which will then be used to decompress the
// message. If no compressor is registered for the encoding, an Unimplemented
// status error will be returned.
//
// Deprecated: use encoding.RegisterCompressor instead. Will be supported
// throughout 1.x.
func ( Decompressor) DialOption {
return newFuncDialOption(func( *dialOptions) {
.dc =
})
}
// WithConnectParams configures the ClientConn to use the provided ConnectParams
// for creating and maintaining connections to servers.
//
// The backoff configuration specified as part of the ConnectParams overrides
// all defaults specified in
// https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. Consider
// using the backoff.DefaultConfig as a base, in cases where you want to
// override only a subset of the backoff configuration.
func ( ConnectParams) DialOption {
return newFuncDialOption(func( *dialOptions) {
.bs = internalbackoff.Exponential{Config: .Backoff}
.minConnectTimeout = func() time.Duration {
return .MinConnectTimeout
}
})
}
// WithBackoffMaxDelay configures the dialer to use the provided maximum delay
// when backing off after failed connection attempts.
//
// Deprecated: use WithConnectParams instead. Will be supported throughout 1.x.
func ( time.Duration) DialOption {
return WithBackoffConfig(BackoffConfig{MaxDelay: })
}
// WithBackoffConfig configures the dialer to use the provided backoff
// parameters after connection failures.
//
// Deprecated: use WithConnectParams instead. Will be supported throughout 1.x.
func ( BackoffConfig) DialOption {
:= backoff.DefaultConfig
.MaxDelay = .MaxDelay
return withBackoff(internalbackoff.Exponential{Config: })
}
// withBackoff sets the backoff strategy used for connectRetryNum after a failed
// connection attempt.
//
// This can be exported if arbitrary backoff strategies are allowed by gRPC.
func ( internalbackoff.Strategy) DialOption {
return newFuncDialOption(func( *dialOptions) {
.bs =
})
}
// WithBlock returns a DialOption which makes callers of Dial block until the
// underlying connection is up. Without this, Dial returns immediately and
// connecting the server happens in background.
//
// Use of this feature is not recommended. For more information, please see:
// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md
//
// Deprecated: this DialOption is not supported by NewClient.
// Will be supported throughout 1.x.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.block = true
})
}
// WithReturnConnectionError returns a DialOption which makes the client connection
// return a string containing both the last connection error that occurred and
// the context.DeadlineExceeded error.
// Implies WithBlock()
//
// Use of this feature is not recommended. For more information, please see:
// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md
//
// Deprecated: this DialOption is not supported by NewClient.
// Will be supported throughout 1.x.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.block = true
.returnLastError = true
})
}
// WithInsecure returns a DialOption which disables transport security for this
// ClientConn. Under the hood, it uses insecure.NewCredentials().
//
// Note that using this DialOption with per-RPC credentials (through
// WithCredentialsBundle or WithPerRPCCredentials) which require transport
// security is incompatible and will cause RPCs to fail.
//
// Deprecated: use WithTransportCredentials and insecure.NewCredentials()
// instead. Will be supported throughout 1.x.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.TransportCredentials = insecure.NewCredentials()
})
}
// WithNoProxy returns a DialOption which disables the use of proxies for this
// ClientConn. This is ignored if WithDialer or WithContextDialer are used.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.useProxy = false
})
}
// WithLocalDNSResolution forces local DNS name resolution even when a proxy is
// specified in the environment. By default, the server name is provided
// directly to the proxy as part of the CONNECT handshake. This is ignored if
// WithNoProxy is used.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.enableLocalDNSResolution = true
})
}
// WithTransportCredentials returns a DialOption which configures a connection
// level security credentials (e.g., TLS/SSL). This should not be used together
// with WithCredentialsBundle.
func ( credentials.TransportCredentials) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.TransportCredentials =
})
}
// WithPerRPCCredentials returns a DialOption which sets credentials and places
// auth state on each outbound RPC.
func ( credentials.PerRPCCredentials) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.PerRPCCredentials = append(.copts.PerRPCCredentials, )
})
}
// WithCredentialsBundle returns a DialOption to set a credentials bundle for
// the ClientConn.WithCreds. This should not be used together with
// WithTransportCredentials.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ( credentials.Bundle) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.CredsBundle =
})
}
// WithTimeout returns a DialOption that configures a timeout for dialing a
// ClientConn initially. This is valid if and only if WithBlock() is present.
//
// Deprecated: this DialOption is not supported by NewClient.
// Will be supported throughout 1.x.
func ( time.Duration) DialOption {
return newFuncDialOption(func( *dialOptions) {
.timeout =
})
}
// WithContextDialer returns a DialOption that sets a dialer to create
// connections. If FailOnNonTempDialError() is set to true, and an error is
// returned by f, gRPC checks the error's Temporary() method to decide if it
// should try to reconnect to the network address.
//
// Note that gRPC by default performs name resolution on the target passed to
// NewClient. To bypass name resolution and cause the target string to be
// passed directly to the dialer here instead, use the "passthrough" resolver
// by specifying it in the target string, e.g. "passthrough:target".
//
// Note: All supported releases of Go (as of December 2023) override the OS
// defaults for TCP keepalive time and interval to 15s. To enable TCP keepalive
// with OS defaults for keepalive time and interval, use a net.Dialer that sets
// the KeepAlive field to a negative value, and sets the SO_KEEPALIVE socket
// option to true from the Control field. For a concrete example of how to do
// this, see internal.NetDialerWithTCPKeepalive().
//
// For more information, please see [issue 23459] in the Go GitHub repo.
//
// [issue 23459]: https://github.com/golang/go/issues/23459
func ( func(context.Context, string) (net.Conn, error)) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.Dialer =
})
}
// WithDialer returns a DialOption that specifies a function to use for dialing
// network addresses. If FailOnNonTempDialError() is set to true, and an error
// is returned by f, gRPC checks the error's Temporary() method to decide if it
// should try to reconnect to the network address.
//
// Deprecated: use WithContextDialer instead. Will be supported throughout
// 1.x.
func ( func(string, time.Duration) (net.Conn, error)) DialOption {
return WithContextDialer(
func( context.Context, string) (net.Conn, error) {
if , := .Deadline(); {
return (, time.Until())
}
return (, 0)
})
}
// WithStatsHandler returns a DialOption that specifies the stats handler for
// all the RPCs and underlying network connections in this ClientConn.
func ( stats.Handler) DialOption {
return newFuncDialOption(func( *dialOptions) {
if == nil {
logger.Error("ignoring nil parameter in grpc.WithStatsHandler ClientOption")
// Do not allow a nil stats handler, which would otherwise cause
// panics.
return
}
.copts.StatsHandlers = append(.copts.StatsHandlers, )
})
}
// withBinaryLogger returns a DialOption that specifies the binary logger for
// this ClientConn.
func ( binarylog.Logger) DialOption {
return newFuncDialOption(func( *dialOptions) {
.binaryLogger =
})
}
// FailOnNonTempDialError returns a DialOption that specifies if gRPC fails on
// non-temporary dial errors. If f is true, and dialer returns a non-temporary
// error, gRPC will fail the connection to the network address and won't try to
// reconnect. The default value of FailOnNonTempDialError is false.
//
// FailOnNonTempDialError only affects the initial dial, and does not do
// anything useful unless you are also using WithBlock().
//
// Use of this feature is not recommended. For more information, please see:
// https://github.com/grpc/grpc-go/blob/master/Documentation/anti-patterns.md
//
// Deprecated: this DialOption is not supported by NewClient.
// This API may be changed or removed in a
// later release.
func ( bool) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.FailOnNonTempDialError =
})
}
// WithUserAgent returns a DialOption that specifies a user agent string for all
// the RPCs.
func ( string) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.UserAgent = + " " + grpcUA
})
}
// WithKeepaliveParams returns a DialOption that specifies keepalive parameters
// for the client transport.
//
// Keepalive is disabled by default.
func ( keepalive.ClientParameters) DialOption {
if .Time < internal.KeepaliveMinPingTime {
logger.Warningf("Adjusting keepalive ping interval to minimum period of %v", internal.KeepaliveMinPingTime)
.Time = internal.KeepaliveMinPingTime
}
return newFuncDialOption(func( *dialOptions) {
.copts.KeepaliveParams =
})
}
// WithUnaryInterceptor returns a DialOption that specifies the interceptor for
// unary RPCs.
func ( UnaryClientInterceptor) DialOption {
return newFuncDialOption(func( *dialOptions) {
.unaryInt =
})
}
// WithChainUnaryInterceptor returns a DialOption that specifies the chained
// interceptor for unary RPCs. The first interceptor will be the outer most,
// while the last interceptor will be the inner most wrapper around the real call.
// All interceptors added by this method will be chained, and the interceptor
// defined by WithUnaryInterceptor will always be prepended to the chain.
func ( ...UnaryClientInterceptor) DialOption {
return newFuncDialOption(func( *dialOptions) {
.chainUnaryInts = append(.chainUnaryInts, ...)
})
}
// WithStreamInterceptor returns a DialOption that specifies the interceptor for
// streaming RPCs.
func ( StreamClientInterceptor) DialOption {
return newFuncDialOption(func( *dialOptions) {
.streamInt =
})
}
// WithChainStreamInterceptor returns a DialOption that specifies the chained
// interceptor for streaming RPCs. The first interceptor will be the outer most,
// while the last interceptor will be the inner most wrapper around the real call.
// All interceptors added by this method will be chained, and the interceptor
// defined by WithStreamInterceptor will always be prepended to the chain.
func ( ...StreamClientInterceptor) DialOption {
return newFuncDialOption(func( *dialOptions) {
.chainStreamInts = append(.chainStreamInts, ...)
})
}
// WithAuthority returns a DialOption that specifies the value to be used as the
// :authority pseudo-header and as the server name in authentication handshake.
// This overrides all other ways of setting authority on the channel, but can be
// overridden per-call by using grpc.CallAuthority.
func ( string) DialOption {
return newFuncDialOption(func( *dialOptions) {
.authority =
})
}
// WithChannelzParentID returns a DialOption that specifies the channelz ID of
// current ClientConn's parent. This function is used in nested channel creation
// (e.g. grpclb dial).
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ( channelz.Identifier) DialOption {
return newFuncDialOption(func( *dialOptions) {
.channelzParent =
})
}
// WithDisableServiceConfig returns a DialOption that causes gRPC to ignore any
// service config provided by the resolver and provides a hint to the resolver
// to not fetch service configs.
//
// Note that this dial option only disables service config from resolver. If
// default service config is provided, gRPC will use the default service config.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.disableServiceConfig = true
})
}
// WithDefaultServiceConfig returns a DialOption that configures the default
// service config, which will be used in cases where:
//
// 1. WithDisableServiceConfig is also used, or
//
// 2. The name resolver does not provide a service config or provides an
// invalid service config.
//
// The parameter s is the JSON representation of the default service config.
// For more information about service configs, see:
// https://github.com/grpc/grpc/blob/master/doc/service_config.md
// For a simple example of usage, see:
// examples/features/load_balancing/client/main.go
func ( string) DialOption {
return newFuncDialOption(func( *dialOptions) {
.defaultServiceConfigRawJSON = &
})
}
// WithDisableRetry returns a DialOption that disables retries, even if the
// service config enables them. This does not impact transparent retries, which
// will happen automatically if no data is written to the wire or if the RPC is
// unprocessed by the remote server.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.disableRetry = true
})
}
// MaxHeaderListSizeDialOption is a DialOption that specifies the maximum
// (uncompressed) size of header list that the client is prepared to accept.
type MaxHeaderListSizeDialOption struct {
MaxHeaderListSize uint32
}
func ( MaxHeaderListSizeDialOption) ( *dialOptions) {
.copts.MaxHeaderListSize = &.MaxHeaderListSize
}
// WithMaxHeaderListSize returns a DialOption that specifies the maximum
// (uncompressed) size of header list that the client is prepared to accept.
func ( uint32) DialOption {
return MaxHeaderListSizeDialOption{
MaxHeaderListSize: ,
}
}
// WithDisableHealthCheck disables the LB channel health checking for all
// SubConns of this ClientConn.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func () DialOption {
return newFuncDialOption(func( *dialOptions) {
.disableHealthCheck = true
})
}
func () dialOptions {
return dialOptions{
copts: transport.ConnectOptions{
ReadBufferSize: defaultReadBufSize,
WriteBufferSize: defaultWriteBufSize,
UserAgent: grpcUA,
BufferPool: mem.DefaultBufferPool(),
},
bs: internalbackoff.DefaultExponential,
idleTimeout: 30 * time.Minute,
defaultScheme: "dns",
maxCallAttempts: defaultMaxCallAttempts,
useProxy: true,
enableLocalDNSResolution: false,
}
}
// withMinConnectDeadline specifies the function that clientconn uses to
// get minConnectDeadline. This can be used to make connection attempts happen
// faster/slower.
//
// For testing purpose only.
func ( func() time.Duration) DialOption {
return newFuncDialOption(func( *dialOptions) {
.minConnectTimeout =
})
}
// withDefaultScheme is used to allow Dial to use "passthrough" as the default
// name resolver, while NewClient uses "dns" otherwise.
func ( string) DialOption {
return newFuncDialOption(func( *dialOptions) {
.defaultScheme =
})
}
// WithResolvers allows a list of resolver implementations to be registered
// locally with the ClientConn without needing to be globally registered via
// resolver.Register. They will be matched against the scheme used for the
// current Dial only, and will take precedence over the global registry.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ( ...resolver.Builder) DialOption {
return newFuncDialOption(func( *dialOptions) {
.resolvers = append(.resolvers, ...)
})
}
// WithIdleTimeout returns a DialOption that configures an idle timeout for the
// channel. If the channel is idle for the configured timeout, i.e there are no
// ongoing RPCs and no new RPCs are initiated, the channel will enter idle mode
// and as a result the name resolver and load balancer will be shut down. The
// channel will exit idle mode when the Connect() method is called or when an
// RPC is initiated.
//
// A default timeout of 30 minutes will be used if this dial option is not set
// at dial time and idleness can be disabled by passing a timeout of zero.
//
// # Experimental
//
// Notice: This API is EXPERIMENTAL and may be changed or removed in a
// later release.
func ( time.Duration) DialOption {
return newFuncDialOption(func( *dialOptions) {
.idleTimeout =
})
}
// WithMaxCallAttempts returns a DialOption that configures the maximum number
// of attempts per call (including retries and hedging) using the channel.
// Service owners may specify a higher value for these parameters, but higher
// values will be treated as equal to the maximum value by the client
// implementation. This mitigates security concerns related to the service
// config being transferred to the client via DNS.
//
// A value of 5 will be used if this dial option is not set or n < 2.
func ( int) DialOption {
return newFuncDialOption(func( *dialOptions) {
if < 2 {
= defaultMaxCallAttempts
}
.maxCallAttempts =
})
}
func ( mem.BufferPool) DialOption {
return newFuncDialOption(func( *dialOptions) {
.copts.BufferPool =
})
}
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64)