From f46da9ce69400df7f67458ddc0a2da2c4d2c5d90 Mon Sep 17 00:00:00 2001 From: dencoded <33698537+dencoded@users.noreply.github.com> Date: Thu, 4 Oct 2018 12:00:06 -0400 Subject: [PATCH 1/2] switching to TykTechnologies/gorpc --- api_definition_test.go | 4 +- main.go | 2 +- rpc/rpc_client.go | 13 +- rpc_test.go | 3 +- vendor/github.com/lonelycode/gorpc/LICENSE | 22 - vendor/github.com/lonelycode/gorpc/Makefile | 24 - vendor/github.com/lonelycode/gorpc/README.md | 121 --- vendor/github.com/lonelycode/gorpc/TODO | 3 - vendor/github.com/lonelycode/gorpc/client.go | 717 ------------------ vendor/github.com/lonelycode/gorpc/common.go | 118 --- .../github.com/lonelycode/gorpc/conn_stats.go | 125 --- .../lonelycode/gorpc/conn_stats_386.go | 113 --- .../lonelycode/gorpc/conn_stats_generic.go | 92 --- .../github.com/lonelycode/gorpc/dispatcher.go | 620 --------------- vendor/github.com/lonelycode/gorpc/doc.go | 15 - .../github.com/lonelycode/gorpc/encoding.go | 118 --- vendor/github.com/lonelycode/gorpc/server.go | 433 ----------- .../github.com/lonelycode/gorpc/transport.go | 229 ------ vendor/vendor.json | 12 +- 19 files changed, 18 insertions(+), 2766 deletions(-) delete mode 100644 vendor/github.com/lonelycode/gorpc/LICENSE delete mode 100644 vendor/github.com/lonelycode/gorpc/Makefile delete mode 100644 vendor/github.com/lonelycode/gorpc/README.md delete mode 100644 vendor/github.com/lonelycode/gorpc/TODO delete mode 100644 vendor/github.com/lonelycode/gorpc/client.go delete mode 100644 vendor/github.com/lonelycode/gorpc/common.go delete mode 100644 vendor/github.com/lonelycode/gorpc/conn_stats.go delete mode 100644 vendor/github.com/lonelycode/gorpc/conn_stats_386.go delete mode 100644 vendor/github.com/lonelycode/gorpc/conn_stats_generic.go delete mode 100644 vendor/github.com/lonelycode/gorpc/dispatcher.go delete mode 100644 vendor/github.com/lonelycode/gorpc/doc.go delete mode 100644 vendor/github.com/lonelycode/gorpc/encoding.go delete mode 100644 vendor/github.com/lonelycode/gorpc/server.go delete mode 100644 vendor/github.com/lonelycode/gorpc/transport.go diff --git a/api_definition_test.go b/api_definition_test.go index da99fb89f283..63da47d85f72 100644 --- a/api_definition_test.go +++ b/api_definition_test.go @@ -392,7 +392,7 @@ func (ln *customListener) Init(addr string) (err error) { return } -func (ln *customListener) Accept() (conn io.ReadWriteCloser, clientAddr string, err error) { +func (ln *customListener) Accept() (conn net.Conn, err error) { c, err := ln.L.Accept() if err != nil { return @@ -419,7 +419,7 @@ func (ln *customListener) Accept() (conn io.ReadWriteCloser, clientAddr string, return } - return c, string(id), nil + return c, nil } func (ln *customListener) Close() error { diff --git a/main.go b/main.go index 150a788b45a2..d04ceb012e01 100644 --- a/main.go +++ b/main.go @@ -31,13 +31,13 @@ import ( graylogHook "github.com/gemnasium/logrus-graylog-hook" "github.com/gorilla/mux" "github.com/justinas/alice" - "github.com/lonelycode/gorpc" "github.com/lonelycode/osin" "github.com/rs/cors" "github.com/satori/go.uuid" "rsc.io/letsencrypt" "github.com/TykTechnologies/goagain" + "github.com/TykTechnologies/gorpc" "github.com/TykTechnologies/tyk/apidef" "github.com/TykTechnologies/tyk/certs" cli "github.com/TykTechnologies/tyk/cli" diff --git a/rpc/rpc_client.go b/rpc/rpc_client.go index 99d48d279898..c51cd43f501a 100644 --- a/rpc/rpc_client.go +++ b/rpc/rpc_client.go @@ -3,7 +3,6 @@ package rpc import ( "crypto/tls" "errors" - "io" "net" "strconv" "strings" @@ -13,8 +12,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/gocraft/health" - "github.com/lonelycode/gorpc" "github.com/satori/go.uuid" + + "github.com/TykTechnologies/gorpc" ) var ( @@ -174,7 +174,7 @@ func Connect(connConfig Config, suppressRegister bool, dispatcherFuncs map[strin clientSingleton.Conns = 20 } - clientSingleton.Dial = func(addr string) (conn io.ReadWriteCloser, err error) { + clientSingleton.Dial = func(addr string) (conn net.Conn, err error) { dialer := &net.Dialer{ Timeout: 10 * time.Second, KeepAlive: 30 * time.Second, @@ -349,12 +349,15 @@ func FuncClientSingleton(funcName string, request interface{}) (interface{}, err return funcClientSingleton.CallTimeout(funcName, request, GlobalRPCCallTimeout) } -func onConnectFunc(remoteAddr string, rwc io.ReadWriteCloser) (io.ReadWriteCloser, error) { +func onConnectFunc(conn net.Conn) (net.Conn, string, error) { clientSingletonMu.Lock() defer clientSingletonMu.Unlock() clientIsConnected = true - return rwc, nil + remoteAddr := conn.RemoteAddr().String() + Log.WithField("remoteAddr", remoteAddr).Debug("connected to RPC server") + + return conn, remoteAddr, nil } func Disconnect() bool { diff --git a/rpc_test.go b/rpc_test.go index cdeb4b284ae3..7c9197e75e7c 100644 --- a/rpc_test.go +++ b/rpc_test.go @@ -6,8 +6,7 @@ import ( "testing" "time" - "github.com/lonelycode/gorpc" - + "github.com/TykTechnologies/gorpc" "github.com/TykTechnologies/tyk/config" "github.com/TykTechnologies/tyk/rpc" "github.com/TykTechnologies/tyk/test" diff --git a/vendor/github.com/lonelycode/gorpc/LICENSE b/vendor/github.com/lonelycode/gorpc/LICENSE deleted file mode 100644 index a43e7ad1c928..000000000000 --- a/vendor/github.com/lonelycode/gorpc/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Aliaksandr Valialkin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/vendor/github.com/lonelycode/gorpc/Makefile b/vendor/github.com/lonelycode/gorpc/Makefile deleted file mode 100644 index a59dca751c50..000000000000 --- a/vendor/github.com/lonelycode/gorpc/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -test: - GOMAXPROCS=1 go test - GOMAXPROCS=2 go test - GOMAXPROCS=4 go test - GOMAXPROCS=8 go test - -test-386: - GOARCH=386 GOMAXPROCS=1 go test - GOARCH=386 GOMAXPROCS=2 go test - GOARCH=386 GOMAXPROCS=4 go test - GOARCH=386 GOMAXPROCS=8 go test - -bench-1-goprocs: - GOMAXPROCS=1 go test -test.bench=".*" - -bench-2-goprocs: - GOMAXPROCS=2 go test -test.bench=".*" - -bench-4-goprocs: - GOMAXPROCS=4 go test -test.bench=".*" - -bench-8-goprocs: - GOMAXPROCS=8 go test -test.bench=".*" - diff --git a/vendor/github.com/lonelycode/gorpc/README.md b/vendor/github.com/lonelycode/gorpc/README.md deleted file mode 100644 index 0b3d7e5f68bb..000000000000 --- a/vendor/github.com/lonelycode/gorpc/README.md +++ /dev/null @@ -1,121 +0,0 @@ -gorpc -===== - -Simple, fast and scalable golang RPC library for high load. - - -Gorpc provides the following features useful for highly loaded projects -with RPC: - -* It minimizes the number of connect() syscalls by pipelining request - and response messages over a single connection. - -* It minimizes the number of send() syscalls by packing as much - as possible pending requests and responses into a single compressed buffer - before passing it into send() syscall. - -* It minimizes the number of recv() syscalls by reading and buffering as much - as possible data from the network. - -* It supports RPC batching, which allows preparing multiple requests and sending - them to the server in a single batch. - -These features help the OS minimizing overhead (CPU load, the number of -TCP connections in TIME_WAIT and CLOSE_WAIT states, the number of network -packets and the amount of network bandwidth) required for RPC processing under -high load. - - -Gorpc additionally provides the following features missing -in [net/rpc](http://golang.org/pkg/net/rpc/): - -* Client automatically manages connections and automatically reconnects - to the server on connection errors. -* Client supports response timeouts out of the box. -* Client supports RPC batching out of the box. -* Client detects stuck servers and immediately returns error to the caller. -* Client supports fast message passing to the Server, i.e. requests - without responses. -* Both Client and Server provide network stats and RPC stats out of the box. -* Commonly used RPC transports such as TCP, TLS and unix socket are available - out of the box. -* RPC transport compression is provided out of the box. -* Server provides graceful shutdown out of the box. -* Server supports RPC handlers' councurrency throttling out of the box. -* Server may pass client address to RPC handlers. -* Server gracefully handles panic in RPC handlers. -* Dispatcher accepts functions as RPC handlers. -* Dispatcher supports registering multiple receiver objects of the same type - under distinct names. -* Dispatcher supports RPC handlers with zero, one (request) or two (client - address and request) arguments and zero, one (either response or error) - or two (response, error) return values. - - -Dispatcher API provided by gorpc allows easily converting usual functions -and/or struct methods into RPC versions on both client and server sides. -See [Dispatcher examples](http://godoc.org/github.com/valyala/gorpc#Dispatcher) -for more details. - - -By default TCP connections are used as underlying gorpc transport. -But it is possible using arbitrary underlying transport - just provide custom -implementations for Client.Dial and Server.Listener. -RPC authentication, authorization and encryption can be easily implemented -via custom underlying transport and/or via OnConnect callbacks. -Currently gorpc provides TCP, TLS and unix socket transport out of the box. - - -Currently gorpc with default settings is successfully used in highly loaded -production environment serving up to 40K qps. Switching from http-based rpc -to gorpc reduced required network bandwidth from 300 Mbit/s to 24 Mbit/s. - - -Docs -==== - -See http://godoc.org/github.com/valyala/gorpc . - - -Usage -===== - -Server: -```go -s := &gorpc.Server{ - // Accept clients on this TCP address. - Addr: ":12345", - - // Echo handler - just return back the message we received from the client - Handler: func(clientAddr string, request interface{}) interface{} { - log.Printf("Obtained request %+v from the client %s\n", request, clientAddr) - return request - }, -} -if err := s.Serve(); err != nil { - log.Fatalf("Cannot start rpc server: %s", err) -} -``` - -Client: -```go -c := &gorpc.Client{ - // TCP address of the server. - Addr: "rpc.server.addr:12345", -} -c.Start() - -resp, err := c.Call("foobar") -if err != nil { - log.Fatalf("Error when sending request to server: %s", err) -} -if resp.(string) != "foobar" { - log.Fatalf("Unexpected response from the server: %+v", resp) -} -``` - -Both client and server collect connection stats - the number of bytes -read / written and the number of calls / errors to send(), recv(), connect() -and accept(). This stats is available at Client.Stats and Server.Stats. - -See tests for more usage examples. diff --git a/vendor/github.com/lonelycode/gorpc/TODO b/vendor/github.com/lonelycode/gorpc/TODO deleted file mode 100644 index 31eac72237e2..000000000000 --- a/vendor/github.com/lonelycode/gorpc/TODO +++ /dev/null @@ -1,3 +0,0 @@ -- Add support for channel request and response. -- Add support for io.Writer, io.Reader and io.ReadWriter request and response. -- Add HTTP transport via HTTP connection hijacking similar to net/rpc. diff --git a/vendor/github.com/lonelycode/gorpc/client.go b/vendor/github.com/lonelycode/gorpc/client.go deleted file mode 100644 index 9780414ba417..000000000000 --- a/vendor/github.com/lonelycode/gorpc/client.go +++ /dev/null @@ -1,717 +0,0 @@ -package gorpc - -import ( - "fmt" - "io" - "sync" - "time" -) - -// Client implements RPC client. -// -// The client must be started with Client.Start() before use. -// -// It is absolutely safe and encouraged using a single client across arbitrary -// number of concurrently running goroutines. -// -// Default client settings are optimized for high load, so don't override -// them without valid reason. -type Client struct { - // Server address to connect to. - // - // The address format depends on the underlying transport provided - // by Client.Dial. The following transports are provided out of the box: - // * TCP - see NewTCPClient() and NewTCPServer(). - // * TLS - see NewTLSClient() and NewTLSServer(). - // * Unix sockets - see NewUnixClient() and NewUnixServer(). - // - // By default TCP transport is used. - Addr string - - // The number of concurrent connections the client should establish - // to the sever. - // By default only one connection is established. - Conns int - - // The maximum number of pending requests in the queue. - // - // The number of pending requsts should exceed the expected number - // of concurrent goroutines calling client's methods. - // Otherwise a lot of ClientError.Overflow errors may appear. - // - // Default is DefaultPendingMessages. - PendingRequests int - - // Delay between request flushes. - // - // Negative values lead to immediate requests' sending to the server - // without their buffering. This minimizes rpc latency at the cost - // of higher CPU and network usage. - // - // Default value is DefaultFlushDelay. - FlushDelay time.Duration - - // Maximum request time. - // Default value is DefaultRequestTimeout. - RequestTimeout time.Duration - - // Disable data compression. - // By default data compression is enabled. - DisableCompression bool - - // Size of send buffer per each underlying connection in bytes. - // Default value is DefaultBufferSize. - SendBufferSize int - - // Size of recv buffer per each underlying connection in bytes. - // Default value is DefaultBufferSize. - RecvBufferSize int - - // OnConnect is called whenever connection to server is established. - // The callback can be used for authentication/authorization/encryption - // and/or for custom transport wrapping. - // - // See also Dial callback, which can be used for sophisticated - // transport implementation. - OnConnect OnConnectFunc - - // The client calls this callback when it needs new connection - // to the server. - // The client passes Client.Addr into Dial(). - // - // Override this callback if you want custom underlying transport - // and/or authentication/authorization. - // Don't forget overriding Server.Listener accordingly. - // - // See also OnConnect for authentication/authorization purposes. - // - // * NewTLSClient() and NewTLSServer() can be used for encrypted rpc. - // * NewUnixClient() and NewUnixServer() can be used for fast local - // inter-process rpc. - // - // By default it returns TCP connections established to the Client.Addr. - Dial DialFunc - - // LogError is used for error logging. - // - // By default the function set via SetErrorLogger() is used. - LogError LoggerFunc - - // Connection statistics. - // - // The stats doesn't reset automatically. Feel free resetting it - // any time you wish. - Stats ConnStats - - requestsChan chan *AsyncResult - - clientStopChan chan struct{} - stopWg sync.WaitGroup -} - -// Start starts rpc client. Establishes connection to the server on Client.Addr. -// -// All the response types the server may return must be registered -// via gorpc.RegisterType() before starting the client. -// There is no need in registering base Go types such as int, string, bool, -// float64, etc. or arrays, slices and maps containing base Go types. -func (c *Client) Start() { - if c.LogError == nil { - c.LogError = errorLogger - } - if c.clientStopChan != nil { - panic("gorpc.Client: the given client is already started. Call Client.Stop() before calling Client.Start() again!") - } - - if c.PendingRequests <= 0 { - c.PendingRequests = DefaultPendingMessages - } - if c.FlushDelay == 0 { - c.FlushDelay = DefaultFlushDelay - } - if c.RequestTimeout <= 0 { - c.RequestTimeout = DefaultRequestTimeout - } - if c.SendBufferSize <= 0 { - c.SendBufferSize = DefaultBufferSize - } - if c.RecvBufferSize <= 0 { - c.RecvBufferSize = DefaultBufferSize - } - - c.requestsChan = make(chan *AsyncResult, c.PendingRequests) - c.clientStopChan = make(chan struct{}) - - if c.Conns <= 0 { - c.Conns = 1 - } - if c.Dial == nil { - c.Dial = defaultDial - } - - for i := 0; i < c.Conns; i++ { - c.stopWg.Add(1) - go clientHandler(c) - } -} - -// Stop stops rpc client. Stopped client can be started again. -func (c *Client) Stop() { - if c.clientStopChan == nil { - panic("gorpc.Client: the client must be started before stopping it") - } - close(c.clientStopChan) - c.stopWg.Wait() - c.clientStopChan = nil -} - -// Call sends the given request to the server and obtains response -// from the server. -// Returns non-nil error if the response cannot be obtained during -// Client.RequestTimeout or server connection problems occur. -// The returned error can be casted to ClientError. -// -// Request and response types may be arbitrary. All the response types -// the server may return must be registered via gorpc.RegisterType() before -// starting the client. -// There is no need in registering base Go types such as int, string, bool, -// float64, etc. or arrays, slices and maps containing base Go types. -// -// Hint: use Dispatcher for distinct calls' construction. -// -// Don't forget starting the client with Client.Start() before calling Client.Call(). -func (c *Client) Call(request interface{}) (response interface{}, err error) { - return c.CallTimeout(request, c.RequestTimeout) -} - -// CallTimeout sends the given request to the server and obtains response -// from the server. -// Returns non-nil error if the response cannot be obtained during -// the given timeout or server connection problems occur. -// The returned error can be casted to ClientError. -// -// Request and response types may be arbitrary. All the response types -// the server may return must be registered via gorpc.RegisterType() before -// starting the client. -// There is no need in registering base Go types such as int, string, bool, -// float64, etc. or arrays, slices and maps containing base Go types. -// -// Hint: use Dispatcher for distinct calls' construction. -// -// Don't forget starting the client with Client.Start() before calling Client.Call(). -func (c *Client) CallTimeout(request interface{}, timeout time.Duration) (response interface{}, err error) { - var m *AsyncResult - if m, err = c.CallAsync(request); err != nil { - return nil, err - } - - t := acquireTimer(timeout) - - select { - case <-m.Done: - response, err = m.Response, m.Error - case <-t.C: - err = getClientTimeoutError(c, timeout) - } - - releaseTimer(t) - return -} - -func getClientTimeoutError(c *Client, timeout time.Duration) error { - err := fmt.Errorf("gorpc.Client: [%s]. Cannot obtain response during timeout=%s", c.Addr, timeout) - c.LogError("%s", err) - return &ClientError{ - Timeout: true, - err: err, - } -} - -// Send sends the given request to the server and doesn't wait for response. -// -// Since this is 'fire and forget' function, which never waits for response, -// it cannot guarantee that the server receives and successfully processes -// the given request. Though in most cases under normal conditions requests -// should reach the server and it should successfully process them. -// Send semantics is similar to UDP messages' semantics. -// -// The server may return arbitrary response on Send() request, but the response -// is totally ignored. -// -// Don't forget starting the client with Client.Start() before calling Client.Send(). -func (c *Client) Send(request interface{}) error { - _, err := c.callAsync(request, true) - return err -} - -// AsyncResult is a result returned from Client.CallAsync(). -type AsyncResult struct { - // The response can be read only after <-Done unblocks. - Response interface{} - - // The error can be read only after <-Done unblocks. - // The error can be casted to ClientError. - Error error - - // Response and Error become available after <-Done unblocks. - Done <-chan struct{} - - request interface{} - t time.Time - done chan struct{} -} - -// CallAsync starts async rpc call. -// -// Rpc call is complete after <-AsyncResult.Done unblocks. -// If you want canceling the request, just throw away the returned AsyncResult. -// -// CallAsync doesn't respect Client.RequestTimeout - response timeout -// may be controlled by the caller via something like: -// -// r := c.CallAsync("foobar") -// select { -// case <-time.After(c.RequestTimeout): -// log.Printf("rpc timeout!") -// case <-r.Done: -// processResponse(r.Response, r.Error) -// } -// -// Don't forget starting the client with Client.Start() before -// calling Client.CallAsync(). -func (c *Client) CallAsync(request interface{}) (*AsyncResult, error) { - return c.callAsync(request, false) -} - -func (c *Client) callAsync(request interface{}, skipResponse bool) (ar *AsyncResult, err error) { - m := &AsyncResult{ - request: request, - } - if !skipResponse { - m.t = time.Now() - m.done = make(chan struct{}) - m.Done = m.done - } - - select { - case c.requestsChan <- m: - return m, nil - default: - err = fmt.Errorf("gorpc.Client: [%s]. Requests' queue with size=%d is overflown. Try increasing Client.PendingRequests value", c.Addr, cap(c.requestsChan)) - c.LogError("%s", err) - err = &ClientError{ - Overflow: true, - err: err, - } - return nil, err - } -} - -// Batch allows grouping and executing multiple RPCs in a single batch. -// -// Batch may be created via Client.NewBatch(). -type Batch struct { - c *Client - ops []*BatchResult - opsLock sync.Mutex -} - -// BatchResult is a result returned from Batch.Add*(). -type BatchResult struct { - // The response can be read only after Batch.Call*() returns. - Response interface{} - - // The error can be read only after Batch.Call*() returns. - // The error can be casted to ClientError. - Error error - - // <-Done unblocks after Batch.Call*() returns. - // Response and Error become available after <-Done unblocks. - Done <-chan struct{} - - request interface{} - ctx interface{} - done chan struct{} -} - -// NewBatch creates new RPC batch. -// -// It is safe creating multiple concurrent batches from a single client. -// -// Don't forget starting the client with Client.Start() before working -// with batched RPC. -func (c *Client) NewBatch() *Batch { - return &Batch{ - c: c, - } -} - -// Add ads new request to the RPC batch. -// -// The order of batched RPCs execution on the server is unspecified. -// -// All the requests added to the batch are sent to the server at once -// when Batch.Call*() is called. -// -// It is safe adding multiple requests to the same batch from concurrently -// running goroutines. -func (b *Batch) Add(request interface{}) *BatchResult { - return b.add(request, false) -} - -// AddSkipResponse adds new request to the RPC batch and doesn't care -// about the response. -// -// The order of batched RPCs execution on the server is unspecified. -// -// All the requests added to the batch are sent to the server at once -// when Batch.Call*() is called. -// -// It is safe adding multiple requests to the same batch from concurrently -// running goroutines. -func (b *Batch) AddSkipResponse(request interface{}) { - b.add(request, true) -} - -func (b *Batch) add(request interface{}, skipResponse bool) *BatchResult { - br := &BatchResult{ - request: request, - } - if !skipResponse { - br.done = make(chan struct{}) - br.Done = br.done - } - - b.opsLock.Lock() - b.ops = append(b.ops, br) - b.opsLock.Unlock() - - return br -} - -// Call calls all the RPCs added via Batch.Add(). -// -// The order of batched RPCs execution on the server is unspecified. -// -// The caller may read all BatchResult contents returned from Batch.Add() -// after the Call returns. -// -// It is guaranteed that all <-BatchResult.Done channels are unblocked after -// the Call returns. -func (b *Batch) Call() error { - return b.CallTimeout(b.c.RequestTimeout) -} - -// CallTimeout calls all the RPCs added via Batch.Add() and waits for -// all the RPC responses during the given timeout. -// -// The caller may read all BatchResult contents returned from Batch.Add() -// after the CallTimeout returns. -// -// It is guaranteed that all <-BatchResult.Done channels are unblocked after -// the CallTimeout returns. -func (b *Batch) CallTimeout(timeout time.Duration) error { - b.opsLock.Lock() - ops := b.ops - b.ops = nil - b.opsLock.Unlock() - - results := make([]*AsyncResult, len(ops)) - for i := range ops { - op := ops[i] - r, err := callAsyncRetry(b.c, op.request, op.done == nil, 5) - if err != nil { - return err - } - results[i] = r - } - - t := acquireTimer(timeout) - - for i := range results { - r := results[i] - op := ops[i] - if op.done == nil { - continue - } - - select { - case <-r.Done: - op.Response, op.Error = r.Response, r.Error - close(op.done) - case <-t.C: - releaseTimer(t) - err := getClientTimeoutError(b.c, timeout) - for ; i < len(results); i++ { - op = ops[i] - op.Error = err - if op.done != nil { - close(op.done) - } - } - return err - } - } - - releaseTimer(t) - - return nil -} - -func callAsyncRetry(c *Client, request interface{}, skipResponse bool, retriesCount int) (*AsyncResult, error) { - retriesCount++ - for { - ar, err := c.callAsync(request, skipResponse) - if err == nil { - return ar, nil - } - if !err.(*ClientError).Overflow { - return nil, err - } - retriesCount-- - if retriesCount <= 0 { - return nil, err - } - time.Sleep(10 * time.Millisecond) - } -} - -// ClientError is an error Client methods can return. -type ClientError struct { - // Set if the error is timeout-related. - Timeout bool - - // Set if the error is connection-related. - Connection bool - - // Set if the error is server-related. - Server bool - - // Set if the error is related to internal resources' overflow. - // Increase PendingRequests if you see a lot of such errors. - Overflow bool - - err error -} - -func (e *ClientError) Error() string { - return e.err.Error() -} - -func clientHandler(c *Client) { - defer c.stopWg.Done() - - var conn io.ReadWriteCloser - var err error - - for { - dialChan := make(chan struct{}) - go func() { - if conn, err = c.Dial(c.Addr); err != nil { - c.LogError("gorpc.Client: [%s]. Cannot establish rpc connection: [%s]", c.Addr, err) - time.Sleep(time.Second) - } - close(dialChan) - }() - - select { - case <-c.clientStopChan: - return - case <-dialChan: - c.Stats.incDialCalls() - } - - if err != nil { - c.Stats.incDialErrors() - continue - } - clientHandleConnection(c, conn) - } -} - -func clientHandleConnection(c *Client, conn io.ReadWriteCloser) { - if c.OnConnect != nil { - newConn, err := c.OnConnect(c.Addr, conn) - if err != nil { - c.LogError("gorpc.Client: [%s]. OnConnect error: [%s]", c.Addr, err) - conn.Close() - return - } - conn = newConn - } - - var buf [1]byte - if !c.DisableCompression { - buf[0] = 1 - } - _, err := conn.Write(buf[:]) - if err != nil { - c.LogError("gorpc.Client: [%s]. Error when writing handshake to server: [%s]", c.Addr, err) - conn.Close() - return - } - - stopChan := make(chan struct{}) - - pendingRequests := make(map[uint64]*AsyncResult) - var pendingRequestsLock sync.Mutex - - writerDone := make(chan error, 1) - go clientWriter(c, conn, pendingRequests, &pendingRequestsLock, stopChan, writerDone) - - readerDone := make(chan error, 1) - go clientReader(c, conn, pendingRequests, &pendingRequestsLock, readerDone) - - select { - case err = <-writerDone: - close(stopChan) - conn.Close() - <-readerDone - case err = <-readerDone: - close(stopChan) - conn.Close() - <-writerDone - case <-c.clientStopChan: - close(stopChan) - conn.Close() - <-readerDone - <-writerDone - } - - if err != nil { - c.LogError("%s", err) - err = &ClientError{ - Connection: true, - err: err, - } - } - for _, m := range pendingRequests { - m.Error = err - if m.done != nil { - close(m.done) - } - } -} - -func clientWriter(c *Client, w io.Writer, pendingRequests map[uint64]*AsyncResult, pendingRequestsLock *sync.Mutex, stopChan <-chan struct{}, done chan<- error) { - var err error - defer func() { done <- err }() - - e := newMessageEncoder(w, c.SendBufferSize, !c.DisableCompression, &c.Stats) - defer e.Close() - - t := time.NewTimer(c.FlushDelay) - var flushChan <-chan time.Time - var wr wireRequest - var msgID uint64 - for { - var m *AsyncResult - - select { - case m = <-c.requestsChan: - default: - select { - case <-stopChan: - return - case m = <-c.requestsChan: - case <-flushChan: - if err = e.Flush(); err != nil { - err = fmt.Errorf("gorpc.Client: [%s]. Cannot flush requests to underlying stream: [%s]", c.Addr, err) - return - } - flushChan = nil - continue - } - } - - if flushChan == nil { - flushChan = getFlushChan(t, c.FlushDelay) - } - - if m.done == nil { - wr.ID = 0 - } else { - msgID++ - if msgID == 0 { - msgID = 1 - } - pendingRequestsLock.Lock() - n := len(pendingRequests) - for { - if _, ok := pendingRequests[msgID]; !ok { - break - } - msgID++ - } - pendingRequests[msgID] = m - pendingRequestsLock.Unlock() - - if n > 10*c.PendingRequests { - err = fmt.Errorf("gorpc.Client: [%s]. The server didn't return %d responses yet. Closing server connection in order to prevent client resource leaks", c.Addr, n) - return - } - - wr.ID = msgID - } - - wr.Request = m.request - m.request = nil - if err = e.Encode(wr); err != nil { - err = fmt.Errorf("gorpc.Client: [%s]. Cannot send request to wire: [%s]", c.Addr, err) - return - } - wr.Request = nil - } -} - -func clientReader(c *Client, r io.Reader, pendingRequests map[uint64]*AsyncResult, pendingRequestsLock *sync.Mutex, done chan<- error) { - var err error - defer func() { - if r := recover(); r != nil { - if err == nil { - err = fmt.Errorf("gorpc.Client: [%s]. Panic when reading data from server: %v", c.Addr, r) - } - } - done <- err - }() - - d := newMessageDecoder(r, c.RecvBufferSize, !c.DisableCompression, &c.Stats) - defer d.Close() - - var wr wireResponse - for { - if err = d.Decode(&wr); err != nil { - err = fmt.Errorf("gorpc.Client: [%s]. Cannot decode response: [%s]", c.Addr, err) - return - } - - pendingRequestsLock.Lock() - m, ok := pendingRequests[wr.ID] - if ok { - delete(pendingRequests, wr.ID) - } - pendingRequestsLock.Unlock() - - if !ok { - err = fmt.Errorf("gorpc.Client: [%s]. Unexpected msgID=[%d] obtained from server", c.Addr, wr.ID) - return - } - - m.Response = wr.Response - - wr.ID = 0 - wr.Response = nil - if wr.Error != "" { - m.Error = &ClientError{ - Server: true, - err: fmt.Errorf("gorpc.Client: [%s]. Server error: [%s]", c.Addr, wr.Error), - } - wr.Error = "" - } - - close(m.done) - - c.Stats.incRPCCalls() - c.Stats.incRPCTime(uint64(time.Since(m.t).Seconds() * 1000)) - } -} diff --git a/vendor/github.com/lonelycode/gorpc/common.go b/vendor/github.com/lonelycode/gorpc/common.go deleted file mode 100644 index 3a69dc1723af..000000000000 --- a/vendor/github.com/lonelycode/gorpc/common.go +++ /dev/null @@ -1,118 +0,0 @@ -package gorpc - -import ( - "fmt" - "io" - "log" - "sync" - "time" -) - -const ( - // DefaultConcurrency is the default number of concurrent rpc calls - // the server can process. - DefaultConcurrency = 8 * 1024 - - // DefaultRequestTimeout is the default timeout for client request. - DefaultRequestTimeout = 20 * time.Second - - // DefaultPendingMessages is the default number of pending messages - // handled by Client and Server. - DefaultPendingMessages = 32 * 1024 - - // DefaultFlushDelay is the default delay between message flushes - // on Client and Server. - DefaultFlushDelay = -1 - - // DefaultBufferSize is the default size for Client and Server buffers. - DefaultBufferSize = 64 * 1024 -) - -// OnConnectFunc is a callback, which may be called by both Client and Server -// on every connection creation if assigned -// to Client.OnConnect / Server.OnConnect. -// -// remoteAddr is the address of the remote end for the established -// connection rwc. -// -// The callback must return either rwc itself or a rwc wrapper. -// The returned connection wrapper MUST send all the data to the underlying -// rwc on every Write() call, otherwise the connection will hang forever. -// -// The callback may be used for authentication/authorization and/or custom -// transport wrapping. -type OnConnectFunc func(remoteAddr string, rwc io.ReadWriteCloser) (io.ReadWriteCloser, error) - -// LoggerFunc is an error logging function to pass to gorpc.SetErrorLogger(). -type LoggerFunc func(format string, args ...interface{}) - -var errorLogger = LoggerFunc(log.Printf) - -// SetErrorLogger sets the given error logger to use in gorpc. -// -// By default log.Printf is used for error logging. -func SetErrorLogger(f LoggerFunc) { - errorLogger = f -} - -// NilErrorLogger discards all error messages. -// -// Pass NilErrorLogger to SetErrorLogger() in order to suppress error log generated -// by gorpc. -func NilErrorLogger(format string, args ...interface{}) {} - -func logPanic(format string, args ...interface{}) { - errorLogger(format, args...) - s := fmt.Sprintf(format, args...) - panic(s) -} - -var timerPool sync.Pool - -func acquireTimer(timeout time.Duration) *time.Timer { - tv := timerPool.Get() - if tv == nil { - return time.NewTimer(timeout) - } - - t := tv.(*time.Timer) - if t.Reset(timeout) { - panic("BUG: Active timer trapped into acquireTimer()") - } - return t -} - -func releaseTimer(t *time.Timer) { - if !t.Stop() { - // Collect possibly added time from the channel - // if timer has been stopped and nobody collected its' value. - select { - case <-t.C: - default: - } - } - - timerPool.Put(t) -} - -var closedFlushChan = make(chan time.Time) - -func init() { - close(closedFlushChan) -} - -func getFlushChan(t *time.Timer, flushDelay time.Duration) <-chan time.Time { - if flushDelay <= 0 { - return closedFlushChan - } - - if !t.Stop() { - // Exhaust expired timer's chan. - select { - case <-t.C: - default: - } - } - t.Reset(flushDelay) - return t.C -} diff --git a/vendor/github.com/lonelycode/gorpc/conn_stats.go b/vendor/github.com/lonelycode/gorpc/conn_stats.go deleted file mode 100644 index eadd6166da82..000000000000 --- a/vendor/github.com/lonelycode/gorpc/conn_stats.go +++ /dev/null @@ -1,125 +0,0 @@ -package gorpc - -import ( - "io" - "sync" - "time" -) - -// ConnStats provides connection statistics. Applied to both gorpc.Client -// and gorpc.Server. -// -// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, -// since the original stats can be updated by concurrently running goroutines. -type ConnStats struct { - // The number of rpc calls performed. - RPCCalls uint64 - - // The total aggregate time for all rpc calls in milliseconds. - // - // This time can be used for calculating the average response time - // per RPC: - // avgRPCTtime = RPCTime / RPCCalls - RPCTime uint64 - - // The number of bytes written to the underlying connections. - BytesWritten uint64 - - // The number of bytes read from the underlying connections. - BytesRead uint64 - - // The number of Read() calls. - ReadCalls uint64 - - // The number of Read() errors. - ReadErrors uint64 - - // The number of Write() calls. - WriteCalls uint64 - - // The number of Write() errors. - WriteErrors uint64 - - // The number of Dial() calls. - DialCalls uint64 - - // The number of Dial() errors. - DialErrors uint64 - - // The number of Accept() calls. - AcceptCalls uint64 - - // The number of Accept() errors. - AcceptErrors uint64 - - // lock is for 386 builds. See https://github.com/valyala/gorpc/issues/5 . - lock sync.Mutex -} - -// AvgRPCTime returns the average RPC execution time. -// -// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, -// since the original stats can be updated by concurrently running goroutines. -func (cs *ConnStats) AvgRPCTime() time.Duration { - return time.Duration(float64(cs.RPCTime)/float64(cs.RPCCalls)) * time.Millisecond -} - -// AvgRPCBytes returns the average bytes sent / received per RPC. -// -// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, -// since the original stats can be updated by concurrently running goroutines. -func (cs *ConnStats) AvgRPCBytes() (send float64, recv float64) { - return float64(cs.BytesWritten) / float64(cs.RPCCalls), float64(cs.BytesRead) / float64(cs.RPCCalls) -} - -// AvgRPCCalls returns the average number of write() / read() syscalls per PRC. -// -// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, -// since the original stats can be updated by concurrently running goroutines. -func (cs *ConnStats) AvgRPCCalls() (write float64, read float64) { - return float64(cs.WriteCalls) / float64(cs.RPCCalls), float64(cs.ReadCalls) / float64(cs.RPCCalls) -} - -type writerCounter struct { - w io.Writer - cs *ConnStats -} - -type readerCounter struct { - r io.Reader - cs *ConnStats -} - -func newWriterCounter(w io.Writer, cs *ConnStats) io.Writer { - return &writerCounter{ - w: w, - cs: cs, - } -} - -func newReaderCounter(r io.Reader, cs *ConnStats) io.Reader { - return &readerCounter{ - r: r, - cs: cs, - } -} - -func (w *writerCounter) Write(p []byte) (int, error) { - n, err := w.w.Write(p) - w.cs.incWriteCalls() - if err != nil { - w.cs.incWriteErrors() - } - w.cs.addBytesWritten(uint64(n)) - return n, err -} - -func (r *readerCounter) Read(p []byte) (int, error) { - n, err := r.r.Read(p) - r.cs.incReadCalls() - if err != nil { - r.cs.incReadErrors() - } - r.cs.addBytesRead(uint64(n)) - return n, err -} diff --git a/vendor/github.com/lonelycode/gorpc/conn_stats_386.go b/vendor/github.com/lonelycode/gorpc/conn_stats_386.go deleted file mode 100644 index 03c7b4d17307..000000000000 --- a/vendor/github.com/lonelycode/gorpc/conn_stats_386.go +++ /dev/null @@ -1,113 +0,0 @@ -// Separate implementation for 386, since it has broken support for atomics. -// See https://github.com/valyala/gorpc/issues/5 for details. - -// +build 386 - -package gorpc - -import ( - "sync" -) - -// Snapshot returns connection statistics' snapshot. -// -// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, -// since the original stats can be updated by concurrently running goroutines. -func (cs *ConnStats) Snapshot() *ConnStats { - cs.lock.Lock() - snapshot := *cs - cs.lock.Unlock() - - snapshot.lock = sync.Mutex{} - return &snapshot -} - -// Reset resets all the stats counters. -func (cs *ConnStats) Reset() { - cs.lock.Lock() - cs.RPCCalls = 0 - cs.RPCTime = 0 - cs.BytesWritten = 0 - cs.BytesRead = 0 - cs.WriteCalls = 0 - cs.WriteErrors = 0 - cs.ReadCalls = 0 - cs.ReadErrors = 0 - cs.DialCalls = 0 - cs.DialErrors = 0 - cs.AcceptCalls = 0 - cs.AcceptErrors = 0 - cs.lock.Unlock() -} - -func (cs *ConnStats) incRPCCalls() { - cs.lock.Lock() - cs.RPCCalls++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incRPCTime(dt uint64) { - cs.lock.Lock() - cs.RPCTime += dt - cs.lock.Unlock() -} - -func (cs *ConnStats) addBytesWritten(n uint64) { - cs.lock.Lock() - cs.BytesWritten += n - cs.lock.Unlock() -} - -func (cs *ConnStats) addBytesRead(n uint64) { - cs.lock.Lock() - cs.BytesRead += n - cs.lock.Unlock() -} - -func (cs *ConnStats) incReadCalls() { - cs.lock.Lock() - cs.ReadCalls++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incReadErrors() { - cs.lock.Lock() - cs.ReadErrors++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incWriteCalls() { - cs.lock.Lock() - cs.WriteCalls++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incWriteErrors() { - cs.lock.Lock() - cs.WriteErrors++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incDialCalls() { - cs.lock.Lock() - cs.DialCalls++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incDialErrors() { - cs.lock.Lock() - cs.DialErrors++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incAcceptCalls() { - cs.lock.Lock() - cs.AcceptCalls++ - cs.lock.Unlock() -} - -func (cs *ConnStats) incAcceptErrors() { - cs.lock.Lock() - cs.AcceptErrors++ - cs.lock.Unlock() -} diff --git a/vendor/github.com/lonelycode/gorpc/conn_stats_generic.go b/vendor/github.com/lonelycode/gorpc/conn_stats_generic.go deleted file mode 100644 index 110010876d8a..000000000000 --- a/vendor/github.com/lonelycode/gorpc/conn_stats_generic.go +++ /dev/null @@ -1,92 +0,0 @@ -// +build !386 - -package gorpc - -import ( - "sync/atomic" -) - -// Snapshot returns connection statistics' snapshot. -// -// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, -// since the original stats can be updated by concurrently running goroutines. -func (cs *ConnStats) Snapshot() *ConnStats { - return &ConnStats{ - RPCCalls: atomic.LoadUint64(&cs.RPCCalls), - RPCTime: atomic.LoadUint64(&cs.RPCTime), - BytesWritten: atomic.LoadUint64(&cs.BytesWritten), - BytesRead: atomic.LoadUint64(&cs.BytesRead), - ReadCalls: atomic.LoadUint64(&cs.ReadCalls), - ReadErrors: atomic.LoadUint64(&cs.ReadErrors), - WriteCalls: atomic.LoadUint64(&cs.WriteCalls), - WriteErrors: atomic.LoadUint64(&cs.WriteErrors), - DialCalls: atomic.LoadUint64(&cs.DialCalls), - DialErrors: atomic.LoadUint64(&cs.DialErrors), - AcceptCalls: atomic.LoadUint64(&cs.AcceptCalls), - AcceptErrors: atomic.LoadUint64(&cs.AcceptErrors), - } -} - -// Reset resets all the stats counters. -func (cs *ConnStats) Reset() { - atomic.StoreUint64(&cs.RPCCalls, 0) - atomic.StoreUint64(&cs.RPCTime, 0) - atomic.StoreUint64(&cs.BytesWritten, 0) - atomic.StoreUint64(&cs.BytesRead, 0) - atomic.StoreUint64(&cs.WriteCalls, 0) - atomic.StoreUint64(&cs.WriteErrors, 0) - atomic.StoreUint64(&cs.ReadCalls, 0) - atomic.StoreUint64(&cs.ReadErrors, 0) - atomic.StoreUint64(&cs.DialCalls, 0) - atomic.StoreUint64(&cs.DialErrors, 0) - atomic.StoreUint64(&cs.AcceptCalls, 0) - atomic.StoreUint64(&cs.AcceptErrors, 0) -} - -func (cs *ConnStats) incRPCCalls() { - atomic.AddUint64(&cs.RPCCalls, 1) -} - -func (cs *ConnStats) incRPCTime(dt uint64) { - atomic.AddUint64(&cs.RPCTime, dt) -} - -func (cs *ConnStats) addBytesWritten(n uint64) { - atomic.AddUint64(&cs.BytesWritten, n) -} - -func (cs *ConnStats) addBytesRead(n uint64) { - atomic.AddUint64(&cs.BytesRead, n) -} - -func (cs *ConnStats) incReadCalls() { - atomic.AddUint64(&cs.ReadCalls, 1) -} - -func (cs *ConnStats) incReadErrors() { - atomic.AddUint64(&cs.ReadErrors, 1) -} - -func (cs *ConnStats) incWriteCalls() { - atomic.AddUint64(&cs.WriteCalls, 1) -} - -func (cs *ConnStats) incWriteErrors() { - atomic.AddUint64(&cs.WriteErrors, 1) -} - -func (cs *ConnStats) incDialCalls() { - atomic.AddUint64(&cs.DialCalls, 1) -} - -func (cs *ConnStats) incDialErrors() { - atomic.AddUint64(&cs.DialErrors, 1) -} - -func (cs *ConnStats) incAcceptCalls() { - atomic.AddUint64(&cs.AcceptCalls, 1) -} - -func (cs *ConnStats) incAcceptErrors() { - atomic.AddUint64(&cs.AcceptErrors, 1) -} diff --git a/vendor/github.com/lonelycode/gorpc/dispatcher.go b/vendor/github.com/lonelycode/gorpc/dispatcher.go deleted file mode 100644 index 84f698060430..000000000000 --- a/vendor/github.com/lonelycode/gorpc/dispatcher.go +++ /dev/null @@ -1,620 +0,0 @@ -package gorpc - -import ( - "errors" - "fmt" - "reflect" - "strings" - "sync" - "time" -) - -// Dispatcher helps constructing HandlerFunc for dispatching across multiple -// functions and/or services. -// -// Dispatcher also automatically registers all request and response types -// for all functions and/or methods registered via AddFunc() and AddService(), -// so there is no need in calling RegisterType() for them. -// -// See examples for details. -type Dispatcher struct { - serviceMap map[string]*serviceData -} - -type serviceData struct { - sv reflect.Value - funcMap map[string]*funcData -} - -type funcData struct { - inNum int - reqt reflect.Type - fv reflect.Value -} - -// NewDispatcher returns new dispatcher. -func NewDispatcher() *Dispatcher { - return &Dispatcher{ - serviceMap: make(map[string]*serviceData), - } -} - -// AddFunc registers the given function f under the name funcName. -// -// The function must accept zero, one or two input arguments. -// If the function has two arguments, then the first argument must have -// string type - the server will pass client address in this parameter. -// -// The function must return zero, one or two values. -// * If the function has two return values, then the second value must have -// error type - the server will propagate this error to the client. -// -// * If the function returns only error value, then the server treats it -// as error, not return value, when sending to the client. -// -// Arbitrary number of functions can be registered in the dispatcher. -// -// See examples for details. -func (d *Dispatcher) AddFunc(funcName string, f interface{}) { - sd, ok := d.serviceMap[""] - if !ok { - sd = &serviceData{ - funcMap: make(map[string]*funcData), - } - d.serviceMap[""] = sd - } - - if _, ok := sd.funcMap[funcName]; ok { - logPanic("gorpc.Dispatcher: function %s has been already registered", funcName) - } - - fd := &funcData{ - fv: reflect.Indirect(reflect.ValueOf(f)), - } - var err error - if fd.inNum, fd.reqt, err = validateFunc(funcName, fd.fv, false); err != nil { - logPanic("gorpc.Disaptcher: %s", err) - } - sd.funcMap[funcName] = fd -} - -// AddService registers public methods of the given service under -// the given name serviceName. -// -// Since only public methods are registered, the service must have at least -// one public method. -// -// All public methods must conform requirements described in AddFunc(). -func (d *Dispatcher) AddService(serviceName string, service interface{}) { - if serviceName == "" { - logPanic("gorpc.Dispatcher: serviceName cannot be empty") - } - if _, ok := d.serviceMap[serviceName]; ok { - logPanic("gorpc.Dispatcher: service with name=[%s] has been already registered", serviceName) - } - - funcMap := make(map[string]*funcData) - - st := reflect.TypeOf(service) - if st.Kind() == reflect.Struct { - logPanic("gorpc.Dispatcher: service [%s] must be a pointer to struct, i.e. *%s", serviceName, st) - } - - for i := 0; i < st.NumMethod(); i++ { - mv := st.Method(i) - - if mv.PkgPath != "" { - // skip unexported methods - continue - } - - funcName := serviceName + "." + mv.Name - fd := &funcData{ - fv: mv.Func, - } - var err error - if fd.inNum, fd.reqt, err = validateFunc(funcName, fd.fv, true); err != nil { - logPanic("gorpc.Dispatcher: %s", err) - } - funcMap[mv.Name] = fd - } - - if len(funcMap) == 0 { - logPanic("gorpc.Dispatcher: the service %s has no methods suitable for rpc", serviceName) - } - - d.serviceMap[serviceName] = &serviceData{ - sv: reflect.ValueOf(service), - funcMap: funcMap, - } -} - -func validateFunc(funcName string, fv reflect.Value, isMethod bool) (inNum int, reqt reflect.Type, err error) { - if funcName == "" { - err = fmt.Errorf("funcName cannot be empty") - return - } - - ft := fv.Type() - if ft.Kind() != reflect.Func { - err = fmt.Errorf("function [%s] must be a function instead of %s", funcName, ft) - return - } - - inNum = ft.NumIn() - outNum := ft.NumOut() - - dt := 0 - if isMethod { - dt = 1 - } - - if inNum == 2+dt { - if ft.In(dt).Kind() != reflect.String { - err = fmt.Errorf("unexpected type for the first argument of the function [%s]: [%s]. Expected string", funcName, ft.In(dt)) - return - } - } else if inNum > 2+dt { - err = fmt.Errorf("unexpected number of arguments in the function [%s]: %d. Expected 0, 1 (request) or 2 (clientAddr, request)", funcName, inNum-dt) - return - } - - if outNum == 2 { - if !isErrorType(ft.Out(1)) { - err = fmt.Errorf("unexpected type for the second return value of the function [%s]: [%s]. Expected [%s]", funcName, ft.Out(1), errt) - return - } - } else if outNum > 2 { - err = fmt.Errorf("unexpected number of return values for the function %s: %d. Expected 0, 1 (response) or 2 (response, error)", funcName, outNum) - return - } - - if inNum > dt { - reqt = ft.In(inNum - 1) - if err = registerType("request", funcName, reqt); err != nil { - return - } - } - - if outNum > 0 { - respt := ft.Out(0) - if !isErrorType(respt) { - if err = registerType("response", funcName, ft.Out(0)); err != nil { - return - } - } - } - - return -} - -func registerType(s, funcName string, t reflect.Type) error { - if t.Kind() == reflect.Struct { - return fmt.Errorf("%s in the function [%s] should be passed by reference, i.e. *%s", s, funcName, t) - } - if err := validateType(t); err != nil { - return fmt.Errorf("%s in the function [%s] cannot contain %s", s, funcName, err) - } - - t = removePtr(t) - tv := reflect.New(t) - if t.Kind() != reflect.Struct { - tv = reflect.Indirect(tv) - } - - switch t.Kind() { - case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: - RegisterType(tv.Interface()) - default: - } - - return nil -} - -func removePtr(t reflect.Type) reflect.Type { - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - return t -} - -var validatedTypes []*validatedType - -type validatedType struct { - t reflect.Type - err *error -} - -func validateType(t reflect.Type) (err error) { - t = removePtr(t) - for _, vd := range validatedTypes { - if vd.t == t { - return *vd.err - } - } - validatedTypes = append(validatedTypes, &validatedType{ - t: t, - err: &err, - }) - - switch t.Kind() { - case reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer: - err = fmt.Errorf("%s. Found [%s]", t.Kind(), t) - return - case reflect.Array, reflect.Slice: - if err = validateType(t.Elem()); err != nil { - err = fmt.Errorf("%s in the %s [%s]", err, t.Kind(), t) - return - } - case reflect.Map: - if err = validateType(t.Elem()); err != nil { - err = fmt.Errorf("%s in the value of map [%s]", err, t) - return - } - if err = validateType(t.Key()); err != nil { - err = fmt.Errorf("%s in the key of map [%s]", err, t) - return - } - case reflect.Struct: - n := 0 - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - if f.PkgPath == "" { - if err = validateType(f.Type); err != nil { - err = fmt.Errorf("%s in the field [%s] of struct [%s]", err, f.Name, t) - return - } - n++ - } - } - if n == 0 { - err = fmt.Errorf("struct without exported fields [%s]", t) - return - } - } - - return err -} - -type dispatcherRequest struct { - Request interface{} - Name string -} - -type dispatcherResponse struct { - Response interface{} - Error string -} - -func init() { - RegisterType(&dispatcherRequest{}) - RegisterType(&dispatcherResponse{}) -} - -// NewHandlerFunc returns HandlerFunc serving all the functions and/or services -// registered via AddFunc() and AddService(). -// -// The returned HandlerFunc must be assigned to Server.Handler or -// passed to New*Server(). -func (d *Dispatcher) NewHandlerFunc() HandlerFunc { - if len(d.serviceMap) == 0 { - logPanic("gorpc.Dispatcher: register at least one service before calling HandlerFunc()") - } - - serviceMap := copyServiceMap(d.serviceMap) - - return func(clientAddr string, request interface{}) interface{} { - req, ok := request.(*dispatcherRequest) - if !ok { - logPanic("gorpc.Dispatcher: unsupported request type received from the client: %T", request) - } - return dispatchRequest(serviceMap, clientAddr, req) - } -} - -func copyServiceMap(sm map[string]*serviceData) map[string]*serviceData { - serviceMap := make(map[string]*serviceData) - for sk, sv := range sm { - funcMap := make(map[string]*funcData) - for fk, fv := range sv.funcMap { - funcMap[fk] = fv - } - serviceMap[sk] = &serviceData{ - sv: sv.sv, - funcMap: funcMap, - } - } - return serviceMap -} - -func dispatchRequest(serviceMap map[string]*serviceData, clientAddr string, req *dispatcherRequest) *dispatcherResponse { - callName := strings.SplitN(req.Name, ".", 2) - if len(callName) != 2 { - return &dispatcherResponse{ - Error: fmt.Sprintf("gorpc.Dispatcher: cannot split call name into service name and method name [%s]", req.Name), - } - } - - serviceName, funcName := callName[0], callName[1] - s, ok := serviceMap[serviceName] - if !ok { - return &dispatcherResponse{ - Error: fmt.Sprintf("gorpc.Dispatcher: unknown service name [%s]", serviceName), - } - } - - fd, ok := s.funcMap[funcName] - if !ok { - return &dispatcherResponse{ - Error: fmt.Sprintf("gorpc.Dispatcher: unknown method [%s]", req.Name), - } - } - - var inArgs []reflect.Value - if fd.inNum > 0 { - inArgs = make([]reflect.Value, fd.inNum) - - dt := 0 - if serviceName != "" { - dt = 1 - inArgs[0] = s.sv - } - if fd.inNum == 2+dt { - inArgs[dt] = reflect.ValueOf(clientAddr) - } - if fd.inNum > dt { - reqv := reflect.ValueOf(req.Request) - reqt := reflect.TypeOf(req.Request) - if reqt != fd.reqt { - return &dispatcherResponse{ - Error: fmt.Sprintf("gorpc.Dispatcher: unexpected request type for method [%s]: %s. Expected %s", req.Name, reqt, fd.reqt), - } - } - inArgs[len(inArgs)-1] = reqv - } - } - - outArgs := fd.fv.Call(inArgs) - - resp := &dispatcherResponse{} - - if len(outArgs) == 1 { - if isErrorType(outArgs[0].Type()) { - resp.Error = getErrorString(outArgs[0]) - } else { - resp.Response = outArgs[0].Interface() - } - } else if len(outArgs) == 2 { - resp.Error = getErrorString(outArgs[1]) - if resp.Error == "" { - resp.Response = outArgs[0].Interface() - } - } - - return resp -} - -var errt = reflect.TypeOf((*error)(nil)).Elem() - -func isErrorType(t reflect.Type) bool { - return t.Implements(errt) -} - -func getErrorString(v reflect.Value) string { - if v.IsNil() { - return "" - } - return v.Interface().(error).Error() -} - -// DispatcherClient is a Client wrapper suitable for calling registered -// functions and/or for calling methods of the registered services. -type DispatcherClient struct { - c *Client - serviceName string -} - -// NewFuncClient returns a client suitable for calling functions registered -// via AddFunc(). -func (d *Dispatcher) NewFuncClient(c *Client) *DispatcherClient { - if len(d.serviceMap) == 0 || d.serviceMap[""] == nil { - logPanic("gorpc.Dispatcher: register at least one function with AddFunc() before calling NewFuncClient()") - } - - return &DispatcherClient{ - c: c, - } -} - -// NewServiceClient returns a client suitable for calling methods -// of the service with name serviceName registered via AddService(). -// -// It is safe creating multiple service clients over a single underlying client. -func (d *Dispatcher) NewServiceClient(serviceName string, c *Client) *DispatcherClient { - if len(d.serviceMap) == 0 || d.serviceMap[serviceName] == nil { - logPanic("gorpc.Dispatcher: service [%s] must be registered with AddService() before calling NewServiceClient()", serviceName) - } - - return &DispatcherClient{ - c: c, - serviceName: serviceName, - } -} - -// Call calls the given function. -func (dc *DispatcherClient) Call(funcName string, request interface{}) (response interface{}, err error) { - return dc.CallTimeout(funcName, request, dc.c.RequestTimeout) -} - -// CallTimeout calls the given function and waits for response during the given timeout. -func (dc *DispatcherClient) CallTimeout(funcName string, request interface{}, timeout time.Duration) (response interface{}, err error) { - req := dc.getRequest(funcName, request) - resp, err := dc.c.CallTimeout(req, timeout) - return getResponse(resp, err) -} - -// Send sends the given request to the given function and doesn't -// wait for response. -func (dc *DispatcherClient) Send(funcName string, request interface{}) error { - req := dc.getRequest(funcName, request) - return dc.c.Send(req) -} - -// CallAsync calls the given function asynchronously. -func (dc *DispatcherClient) CallAsync(funcName string, request interface{}) (*AsyncResult, error) { - req := dc.getRequest(funcName, request) - - innerAr, err := dc.c.CallAsync(req) - if err != nil { - return nil, err - } - - ch := make(chan struct{}) - ar := &AsyncResult{ - Done: ch, - } - - go func() { - <-innerAr.Done - ar.Response, ar.Error = getResponse(innerAr.Response, innerAr.Error) - close(ch) - }() - - return ar, nil -} - -// DispatcherBatch allows grouping and executing multiple RPCs in a single batch. -// -// DispatcherBatch may be created via DispatcherClient.NewBatch(). -type DispatcherBatch struct { - lock sync.Mutex - c *DispatcherClient - b *Batch - ops []*BatchResult -} - -// NewBatch creates new RPC batch for the given DispatcherClient. -// -// It is safe creating multiple concurrent batches from a single client. -func (dc *DispatcherClient) NewBatch() *DispatcherBatch { - return &DispatcherBatch{ - c: dc, - b: dc.c.NewBatch(), - } -} - -// Add ads new request to the RPC batch. -// -// The order of batched RPCs execution on the server is unspecified. -// -// All the requests added to the batch are sent to the server at once -// when DispatcherBatch.Call*() is called. -// -// It is safe adding multiple requests to the same batch from concurrently -// running goroutines. -func (b *DispatcherBatch) Add(funcName string, request interface{}) *BatchResult { - return b.add(funcName, request, false) -} - -// AddSkipResponse adds new request to the RPC batch and doesn't care -// about the response. -// -// The order of batched RPCs execution on the server is unspecified. -// -// All the requests added to the batch are sent to the server at once -// when DispatcherBatch.Call*() is called. -// -// It is safe adding multiple requests to the same batch from concurrently -// running goroutines. -func (b *DispatcherBatch) AddSkipResponse(funcName string, request interface{}) { - b.add(funcName, request, true) -} - -func (b *DispatcherBatch) add(funcName string, request interface{}, skipResponse bool) *BatchResult { - req := b.c.getRequest(funcName, request) - - var br *BatchResult - b.lock.Lock() - if !skipResponse { - br = &BatchResult{ - ctx: b.b.Add(req), - done: make(chan struct{}), - } - br.Done = br.done - b.ops = append(b.ops, br) - } else { - b.b.AddSkipResponse(req) - } - b.lock.Unlock() - - return br -} - -// Call calls all the RPCs added via DispatcherBatch.Add(). -// -// The order of batched RPCs execution on the server is unspecified. -// -// The caller may read all BatchResult contents returned -// from DispatcherBatch.Add() after the Call returns. -// -// It is guaranteed that all <-BatchResult.Done channels are unblocked after -// the Call returns. -func (b *DispatcherBatch) Call() error { - return b.CallTimeout(b.c.c.RequestTimeout) -} - -// CallTimeout calls all the RPCs added via DispatcherBatch.Add() and waits -// for all the RPC responses during the given timeout. -// -// The caller may read all BatchResult contents returned -// from DispatcherBatch.Add() after the CallTimeout returns. -// -// It is guaranteed that all <-BatchResult.Done channels are unblocked after -// the CallTimeout returns. -func (b *DispatcherBatch) CallTimeout(timeout time.Duration) error { - b.lock.Lock() - bb := b.b - b.b = b.c.c.NewBatch() - ops := b.ops - b.ops = nil - b.lock.Unlock() - - if err := bb.CallTimeout(timeout); err != nil { - return err - } - - for _, op := range ops { - br := op.ctx.(*BatchResult) - op.Response, op.Error = getResponse(br.Response, br.Error) - close(op.done) - } - - return nil -} - -func (dc *DispatcherClient) getRequest(funcName string, request interface{}) *dispatcherRequest { - return &dispatcherRequest{ - Name: dc.serviceName + "." + funcName, - Request: request, - } -} - -func getResponse(respv interface{}, err error) (interface{}, error) { - if err != nil { - return nil, err - } - resp, ok := respv.(*dispatcherResponse) - if !ok { - return nil, &ClientError{ - Server: true, - err: fmt.Errorf("gorpc.DispatcherClient: unexpected response type: %T. Expected *dispatcherResponse", respv), - } - } - if resp.Error != "" { - return nil, &ClientError{ - Server: true, - err: errors.New(resp.Error), - } - } - return resp.Response, nil -} diff --git a/vendor/github.com/lonelycode/gorpc/doc.go b/vendor/github.com/lonelycode/gorpc/doc.go deleted file mode 100644 index 9acb63d907e9..000000000000 --- a/vendor/github.com/lonelycode/gorpc/doc.go +++ /dev/null @@ -1,15 +0,0 @@ -/* -Package gorpc provides simple RPC API for highload projects. - -Gorpc has the following features: - - * Easy-to-use API. - * Optimized for high load (>10K qps). - * Uses as low network bandwidth as possible. - * Minimizes the number of TCP connections in TIME_WAIT and WAIT_CLOSE states. - * Minimizes the number of send() and recv() syscalls. - * Provides ability to use arbitrary underlying transport. - By default TCP is used, but TLS and UNIX sockets are already available. - -*/ -package gorpc diff --git a/vendor/github.com/lonelycode/gorpc/encoding.go b/vendor/github.com/lonelycode/gorpc/encoding.go deleted file mode 100644 index 21a1a0a10725..000000000000 --- a/vendor/github.com/lonelycode/gorpc/encoding.go +++ /dev/null @@ -1,118 +0,0 @@ -package gorpc - -import ( - "bufio" - "compress/flate" - "encoding/gob" - "io" -) - -// RegisterType registers the given type to send via rpc. -// -// The client must register all the response types the server may send. -// The server must register all the request types the client may send. -// -// There is no need in registering base Go types such as int, string, bool, -// float64, etc. or arrays, slices and maps containing base Go types. -// -// There is no need in registering argument and return value types -// for functions and methods registered via Dispatcher. -func RegisterType(x interface{}) { - gob.Register(x) -} - -type wireRequest struct { - ID uint64 - Request interface{} -} - -type wireResponse struct { - ID uint64 - Response interface{} - Error string -} - -type messageEncoder struct { - e *gob.Encoder - bw *bufio.Writer - zw *flate.Writer - ww *bufio.Writer -} - -func (e *messageEncoder) Close() error { - if e.zw != nil { - return e.zw.Close() - } - return nil -} - -func (e *messageEncoder) Flush() error { - if e.zw != nil { - if err := e.ww.Flush(); err != nil { - return err - } - if err := e.zw.Flush(); err != nil { - return err - } - } - if err := e.bw.Flush(); err != nil { - return err - } - return nil -} - -func (e *messageEncoder) Encode(msg interface{}) error { - return e.e.Encode(msg) -} - -func newMessageEncoder(w io.Writer, bufferSize int, enableCompression bool, s *ConnStats) *messageEncoder { - w = newWriterCounter(w, s) - bw := bufio.NewWriterSize(w, bufferSize) - - ww := bw - var zw *flate.Writer - if enableCompression { - zw, _ = flate.NewWriter(bw, flate.BestSpeed) - ww = bufio.NewWriterSize(zw, bufferSize) - } - - return &messageEncoder{ - e: gob.NewEncoder(ww), - bw: bw, - zw: zw, - ww: ww, - } -} - -type messageDecoder struct { - d *gob.Decoder - zr io.ReadCloser -} - -func (d *messageDecoder) Close() error { - if d.zr != nil { - return d.zr.Close() - } - return nil -} - -func (d *messageDecoder) Decode(msg interface{}) error { - return d.d.Decode(msg) -} - -func newMessageDecoder(r io.Reader, bufferSize int, enableCompression bool, s *ConnStats) *messageDecoder { - r = newReaderCounter(r, s) - br := bufio.NewReaderSize(r, bufferSize) - - rr := br - var zr io.ReadCloser - if enableCompression { - zr = flate.NewReader(br) - rr = bufio.NewReaderSize(zr, bufferSize) - } - - return &messageDecoder{ - d: gob.NewDecoder(rr), - zr: zr, - } -} diff --git a/vendor/github.com/lonelycode/gorpc/server.go b/vendor/github.com/lonelycode/gorpc/server.go deleted file mode 100644 index cf2baa36da33..000000000000 --- a/vendor/github.com/lonelycode/gorpc/server.go +++ /dev/null @@ -1,433 +0,0 @@ -package gorpc - -import ( - "fmt" - "io" - "runtime" - "sync" - "time" -) - -// HandlerFunc is a server handler function. -// -// clientAddr contains client address returned by Listener.Accept(). -// Request and response types may be arbitrary. -// All the request types the client may send to the server must be registered -// with gorpc.RegisterType() before starting the server. -// There is no need in registering base Go types such as int, string, bool, -// float64, etc. or arrays, slices and maps containing base Go types. -// -// Hint: use Dispatcher for HandlerFunc construction. -type HandlerFunc func(clientAddr string, request interface{}) (response interface{}) - -// Server implements RPC server. -// -// Default server settings are optimized for high load, so don't override -// them without valid reason. -type Server struct { - // Address to listen to for incoming connections. - // - // The address format depends on the underlying transport provided - // by Server.Listener. The following transports are provided - // out of the box: - // * TCP - see NewTCPServer() and NewTCPClient(). - // * TLS (aka SSL) - see NewTLSServer() and NewTLSClient(). - // * Unix sockets - see NewUnixServer() and NewUnixClient(). - // - // By default TCP transport is used. - Addr string - - // Handler function for incoming requests. - // - // Server calls this function for each incoming request. - // The function must process the request and return the corresponding response. - // - // Hint: use Dispatcher for HandlerFunc construction. - Handler HandlerFunc - - // The maximum number of concurrent rpc calls the server may perform. - // Default is DefaultConcurrency. - Concurrency int - - // The maximum delay between response flushes to clients. - // - // Negative values lead to immediate requests' sending to the client - // without their buffering. This minimizes rpc latency at the cost - // of higher CPU and network usage. - // - // Default is DefaultFlushDelay. - FlushDelay time.Duration - - // The maximum number of pending responses in the queue. - // Default is DefaultPendingMessages. - PendingResponses int - - // Size of send buffer per each underlying connection in bytes. - // Default is DefaultBufferSize. - SendBufferSize int - - // Size of recv buffer per each underlying connection in bytes. - // Default is DefaultBufferSize. - RecvBufferSize int - - // OnConnect is called whenever connection from client is accepted. - // The callback can be used for authentication/authorization/encryption - // and/or for custom transport wrapping. - // - // See also Listener, which can be used for sophisticated transport - // implementation. - OnConnect OnConnectFunc - - // The server obtains new client connections via Listener.Accept(). - // - // Override the listener if you want custom underlying transport - // and/or client authentication/authorization. - // Don't forget overriding Client.Dial() callback accordingly. - // - // See also OnConnect for authentication/authorization purposes. - // - // * NewTLSClient() and NewTLSServer() can be used for encrypted rpc. - // * NewUnixClient() and NewUnixServer() can be used for fast local - // inter-process rpc. - // - // By default it returns TCP connections accepted from Server.Addr. - Listener Listener - - // LogError is used for error logging. - // - // By default the function set via SetErrorLogger() is used. - LogError LoggerFunc - - // Connection statistics. - // - // The stats doesn't reset automatically. Feel free resetting it - // any time you wish. - Stats ConnStats - - serverStopChan chan struct{} - stopWg sync.WaitGroup -} - -// Start starts rpc server. -// -// All the request types the client may send to the server must be registered -// with gorpc.RegisterType() before starting the server. -// There is no need in registering base Go types such as int, string, bool, -// float64, etc. or arrays, slices and maps containing base Go types. -func (s *Server) Start() error { - if s.LogError == nil { - s.LogError = errorLogger - } - if s.Handler == nil { - panic("gorpc.Server: Server.Handler cannot be nil") - } - - if s.serverStopChan != nil { - panic("gorpc.Server: server is already running. Stop it before starting it again") - } - s.serverStopChan = make(chan struct{}) - - if s.Concurrency <= 0 { - s.Concurrency = DefaultConcurrency - } - if s.FlushDelay == 0 { - s.FlushDelay = DefaultFlushDelay - } - if s.PendingResponses <= 0 { - s.PendingResponses = DefaultPendingMessages - } - if s.SendBufferSize <= 0 { - s.SendBufferSize = DefaultBufferSize - } - if s.RecvBufferSize <= 0 { - s.RecvBufferSize = DefaultBufferSize - } - - if s.Listener == nil { - s.Listener = &defaultListener{} - } - if err := s.Listener.Init(s.Addr); err != nil { - err = fmt.Errorf("gorpc.Server: [%s]. Cannot listen to: [%s]", s.Addr, err) - s.LogError("%s", err) - return err - } - - workersCh := make(chan struct{}, s.Concurrency) - s.stopWg.Add(1) - go serverHandler(s, workersCh) - return nil -} - -// Stop stops rpc server. Stopped server can be started again. -func (s *Server) Stop() { - if s.serverStopChan == nil { - panic("gorpc.Server: server must be started before stopping it") - } - close(s.serverStopChan) - s.stopWg.Wait() - s.serverStopChan = nil -} - -// Serve starts rpc server and blocks until it is stopped. -func (s *Server) Serve() error { - if err := s.Start(); err != nil { - return err - } - s.stopWg.Wait() - return nil -} - -func serverHandler(s *Server, workersCh chan struct{}) { - defer s.stopWg.Done() - - var conn io.ReadWriteCloser - var clientAddr string - var err error - - for { - acceptChan := make(chan struct{}) - go func() { - if conn, clientAddr, err = s.Listener.Accept(); err != nil { - s.LogError("gorpc.Server: [%s]. Cannot accept new connection: [%s]", s.Addr, err) - time.Sleep(time.Second) - } - close(acceptChan) - }() - - select { - case <-s.serverStopChan: - s.Listener.Close() - return - case <-acceptChan: - s.Stats.incAcceptCalls() - } - - if err != nil { - s.Stats.incAcceptErrors() - continue - } - - s.stopWg.Add(1) - go serverHandleConnection(s, conn, clientAddr, workersCh) - } -} - -func serverHandleConnection(s *Server, conn io.ReadWriteCloser, clientAddr string, workersCh chan struct{}) { - defer s.stopWg.Done() - - if s.OnConnect != nil { - newConn, err := s.OnConnect(clientAddr, conn) - if err != nil { - s.LogError("gorpc.Server: [%s]->[%s]. OnConnect error: [%s]", clientAddr, s.Addr, err) - conn.Close() - return - } - conn = newConn - } - - var enabledCompression bool - var err error - zChan := make(chan bool, 1) - go func() { - var buf [1]byte - if _, err = conn.Read(buf[:]); err != nil { - s.LogError("gorpc.Server: [%s]->[%s]. Error when reading handshake from client: [%s]", clientAddr, s.Addr, err) - } - zChan <- (buf[0] != 0) - }() - select { - case enabledCompression = <-zChan: - if err != nil { - conn.Close() - return - } - case <-s.serverStopChan: - conn.Close() - return - case <-time.After(10 * time.Second): - s.LogError("gorpc.Server: [%s]->[%s]. Cannot obtain handshake from client during 10s", clientAddr, s.Addr) - conn.Close() - return - } - - responsesChan := make(chan *serverMessage, s.PendingResponses) - stopChan := make(chan struct{}) - - readerDone := make(chan struct{}) - go serverReader(s, conn, clientAddr, responsesChan, stopChan, readerDone, enabledCompression, workersCh) - - writerDone := make(chan struct{}) - go serverWriter(s, conn, clientAddr, responsesChan, stopChan, writerDone, enabledCompression) - - select { - case <-readerDone: - close(stopChan) - conn.Close() - <-writerDone - case <-writerDone: - close(stopChan) - conn.Close() - <-readerDone - case <-s.serverStopChan: - close(stopChan) - conn.Close() - <-readerDone - <-writerDone - } -} - -type serverMessage struct { - ID uint64 - Request interface{} - Response interface{} - Error string - ClientAddr string -} - -var serverMessagePool = &sync.Pool{ - New: func() interface{} { - return &serverMessage{} - }, -} - -func serverReader(s *Server, r io.Reader, clientAddr string, responsesChan chan<- *serverMessage, - stopChan <-chan struct{}, done chan<- struct{}, enabledCompression bool, workersCh chan struct{}) { - - defer func() { - if r := recover(); r != nil { - s.LogError("gorpc.Server: [%s]->[%s]. Panic when reading data from client: %v", clientAddr, s.Addr, r) - } - close(done) - }() - - d := newMessageDecoder(r, s.RecvBufferSize, enabledCompression, &s.Stats) - defer d.Close() - - var wr wireRequest - for { - if err := d.Decode(&wr); err != nil { - s.LogError("gorpc.Server: [%s]->[%s]. Cannot decode request: [%s]", clientAddr, s.Addr, err) - return - } - - m := serverMessagePool.Get().(*serverMessage) - m.ID = wr.ID - m.Request = wr.Request - m.ClientAddr = clientAddr - - wr.ID = 0 - wr.Request = nil - - select { - case workersCh <- struct{}{}: - default: - select { - case workersCh <- struct{}{}: - case <-stopChan: - return - } - } - go serveRequest(s, responsesChan, stopChan, m, workersCh) - } -} - -func serveRequest(s *Server, responsesChan chan<- *serverMessage, stopChan <-chan struct{}, m *serverMessage, workersCh <-chan struct{}) { - request := m.Request - m.Request = nil - clientAddr := m.ClientAddr - m.ClientAddr = "" - skipResponse := (m.ID == 0) - - if skipResponse { - m.Response = nil - m.Error = "" - serverMessagePool.Put(m) - } - - t := time.Now() - response, err := callHandlerWithRecover(s.LogError, s.Handler, clientAddr, s.Addr, request) - s.Stats.incRPCTime(uint64(time.Since(t).Seconds() * 1000)) - - if !skipResponse { - m.Response = response - m.Error = err - - // Select hack for better performance. - // See https://github.com/valyala/gorpc/pull/1 for details. - select { - case responsesChan <- m: - default: - select { - case responsesChan <- m: - case <-stopChan: - } - } - } - - <-workersCh -} - -func callHandlerWithRecover(logErrorFunc LoggerFunc, handler HandlerFunc, clientAddr, serverAddr string, request interface{}) (response interface{}, errStr string) { - defer func() { - if x := recover(); x != nil { - stackTrace := make([]byte, 1<<20) - n := runtime.Stack(stackTrace, false) - errStr = fmt.Sprintf("Panic occured: %v\nStack trace: %s", x, stackTrace[:n]) - logErrorFunc("gorpc.Server: [%s]->[%s]. %s", clientAddr, serverAddr, errStr) - } - }() - response = handler(clientAddr, request) - return -} - -func serverWriter(s *Server, w io.Writer, clientAddr string, responsesChan <-chan *serverMessage, stopChan <-chan struct{}, done chan<- struct{}, enabledCompression bool) { - defer func() { close(done) }() - - e := newMessageEncoder(w, s.SendBufferSize, enabledCompression, &s.Stats) - defer e.Close() - - var flushChan <-chan time.Time - t := time.NewTimer(s.FlushDelay) - var wr wireResponse - for { - var m *serverMessage - - select { - case m = <-responsesChan: - default: - select { - case <-stopChan: - return - case m = <-responsesChan: - case <-flushChan: - if err := e.Flush(); err != nil { - s.LogError("gorpc.Server: [%s]->[%s]: Cannot flush responses to underlying stream: [%s]", clientAddr, s.Addr, err) - return - } - flushChan = nil - continue - } - } - - if flushChan == nil { - flushChan = getFlushChan(t, s.FlushDelay) - } - - wr.ID = m.ID - wr.Response = m.Response - wr.Error = m.Error - - m.Response = nil - m.Error = "" - serverMessagePool.Put(m) - - if err := e.Encode(wr); err != nil { - s.LogError("gorpc.Server: [%s]->[%s]. Cannot send response to wire: [%s]", clientAddr, s.Addr, err) - return - } - wr.Response = nil - wr.Error = "" - - s.Stats.incRPCCalls() - } -} diff --git a/vendor/github.com/lonelycode/gorpc/transport.go b/vendor/github.com/lonelycode/gorpc/transport.go deleted file mode 100644 index 16c0e9cb196a..000000000000 --- a/vendor/github.com/lonelycode/gorpc/transport.go +++ /dev/null @@ -1,229 +0,0 @@ -package gorpc - -import ( - "crypto/tls" - "io" - "net" - "time" -) - -var ( - dialer = &net.Dialer{ - Timeout: 10 * time.Second, - KeepAlive: 30 * time.Second, - } -) - -// DialFunc is a function intended for setting to Client.Dial. -// -// It is expected that the returned conn immediately -// sends all the data passed via Write() to the server. -// Otherwise gorpc may hang. -// The conn implementation must call Flush() on underlying buffered -// streams before returning from Write(). -type DialFunc func(addr string) (conn io.ReadWriteCloser, err error) - -// Listener is an interface for custom listeners intended for the Server. -type Listener interface { - // Init is called on server start. - // - // addr contains the address set at Server.Addr. - Init(addr string) error - - // Accept must return incoming connections from clients. - // clientAddr must contain client's address in user-readable view. - // - // It is expected that the returned conn immediately - // sends all the data passed via Write() to the client. - // Otherwise gorpc may hang. - // The conn implementation must call Flush() on underlying buffered - // streams before returning from Write(). - Accept() (conn io.ReadWriteCloser, clientAddr string, err error) - - // Close closes the listener. - // All pending calls to Accept() must immediately return errors after - // Close is called. - // All subsequent calls to Accept() must immediately return error. - Close() error -} - -func defaultDial(addr string) (conn io.ReadWriteCloser, err error) { - return dialer.Dial("tcp", addr) -} - -type defaultListener struct { - L net.Listener -} - -func (ln *defaultListener) Init(addr string) (err error) { - ln.L, err = net.Listen("tcp", addr) - return -} - -func (ln *defaultListener) Accept() (conn io.ReadWriteCloser, clientAddr string, err error) { - c, err := ln.L.Accept() - if err != nil { - return nil, "", err - } - if err = setupKeepalive(c); err != nil { - c.Close() - return nil, "", err - } - return c, c.RemoteAddr().String(), nil -} - -func (ln *defaultListener) Close() error { - return ln.L.Close() -} - -func setupKeepalive(conn net.Conn) error { - tcpConn := conn.(*net.TCPConn) - if err := tcpConn.SetKeepAlive(true); err != nil { - return err - } - if err := tcpConn.SetKeepAlivePeriod(30 * time.Second); err != nil { - return err - } - return nil -} - -type netListener struct { - F func(addr string) (net.Listener, error) - L net.Listener -} - -func (ln *netListener) Init(addr string) (err error) { - ln.L, err = ln.F(addr) - return -} - -func (ln *netListener) Accept() (conn io.ReadWriteCloser, clientAddr string, err error) { - c, err := ln.L.Accept() - if err != nil { - return nil, "", err - } - return c, c.RemoteAddr().String(), nil -} - -func (ln *netListener) Close() error { - return ln.L.Close() -} - -func unixDial(addr string) (conn io.ReadWriteCloser, err error) { - c, err := net.Dial("unix", addr) - if err != nil { - return nil, err - } - return c, err -} - -// NewTCPClient creates a client connecting over TCP to the server -// listening to the given addr. -// -// The returned client must be started after optional settings' adjustment. -// -// The corresponding server must be created with NewTCPServer(). -func NewTCPClient(addr string) *Client { - return &Client{ - Addr: addr, - Dial: defaultDial, - } -} - -// NewTCPServer creates a server listening for TCP connections -// on the given addr and processing incoming requests -// with the given HandlerFunc. -// -// The returned server must be started after optional settings' adjustment. -// -// The corresponding client must be created with NewTCPClient(). -func NewTCPServer(addr string, handler HandlerFunc) *Server { - return &Server{ - Addr: addr, - Handler: handler, - Listener: &defaultListener{}, - } -} - -// NewUnixClient creates a client connecting over unix socket -// to the server listening to the given addr. -// -// The returned client must be started after optional settings' adjustment. -// -// The corresponding server must be created with NewUnixServer(). -func NewUnixClient(addr string) *Client { - return &Client{ - Addr: addr, - Dial: unixDial, - - // There is little sense in compressing rpc data passed - // over local unix sockets. - DisableCompression: true, - - // Sacrifice the number of Write() calls to the smallest - // possible latency, since it has higher priority in local IPC. - FlushDelay: -1, - } -} - -// NewUnixServer creates a server listening for unix connections -// on the given addr and processing incoming requests -// with the given HandlerFunc. -// -// The returned server must be started after optional settings' adjustment. -// -// The corresponding client must be created with NewUnixClient(). -func NewUnixServer(addr string, handler HandlerFunc) *Server { - return &Server{ - Addr: addr, - Handler: handler, - Listener: &netListener{ - F: func(addr string) (net.Listener, error) { - return net.Listen("unix", addr) - }, - }, - - // Sacrifice the number of Write() calls to the smallest - // possible latency, since it has higher priority in local IPC. - FlushDelay: -1, - } -} - -// NewTLSClient creates a client connecting over TLS (aka SSL) to the server -// listening to the given addr using the given TLS config. -// -// The returned client must be started after optional settings' adjustment. -// -// The corresponding server must be created with NewTLSServer(). -func NewTLSClient(addr string, cfg *tls.Config) *Client { - return &Client{ - Addr: addr, - Dial: func(addr string) (conn io.ReadWriteCloser, err error) { - c, err := tls.DialWithDialer(dialer, "tcp", addr, cfg) - if err != nil { - return nil, err - } - return c, err - }, - } -} - -// NewTLSServer creates a server listening for TLS (aka SSL) connections -// on the given addr and processing incoming requests -// with the given HandlerFunc. -// cfg must contain TLS settings for the server. -// -// The returned server must be started after optional settings' adjustment. -// -// The corresponding client must be created with NewTLSClient(). -func NewTLSServer(addr string, handler HandlerFunc, cfg *tls.Config) *Server { - return &Server{ - Addr: addr, - Handler: handler, - Listener: &netListener{ - F: func(addr string) (net.Listener, error) { - return tls.Listen("tcp", addr, cfg) - }, - }, - } -} diff --git a/vendor/vendor.json b/vendor/vendor.json index 51da0bef3e89..0fa89968460a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -48,6 +48,12 @@ "revision": "dcb3e4bb79906c5695321440deca10592f4eeb2d", "revisionTime": "2017-02-22T15:40:38Z" }, + { + "checksumSHA1": "pk+Fj0KfcNdwugEfI9657E7flgI=", + "path": "github.com/TykTechnologies/gorpc", + "revision": "2fd6ca5242c4dbee5ab151010ee844f3fb5507a8", + "revisionTime": "2018-09-28T16:00:09Z" + }, { "checksumSHA1": "FEq3KG6Kgarh8P5XIUx3bH13zDM=", "path": "github.com/TykTechnologies/goverify", @@ -296,12 +302,6 @@ "revision": "ed3ca8a15a931b141440a7e98e4f716eec255f7d", "revisionTime": "2014-12-02T16:54:02Z" }, - { - "checksumSHA1": "OQ6h/9ehSr5avWJD3CbaZdZ8pwU=", - "path": "github.com/lonelycode/gorpc", - "revision": "5108a99af7137601fdc00c899caa4a3f0a8b31ef", - "revisionTime": "2015-06-11T14:24:37Z" - }, { "checksumSHA1": "5OpChAvGZcoxGkZAp3qf5ZxKR84=", "path": "github.com/lonelycode/osin", From e1b175aa36fb29b4bca5a86ecfc050e575706692 Mon Sep 17 00:00:00 2001 From: dencoded <33698537+dencoded@users.noreply.github.com> Date: Thu, 4 Oct 2018 12:00:34 -0400 Subject: [PATCH 2/2] TykTechnologies/gorpc vendored --- .../github.com/TykTechnologies/gorpc/LICENSE | 22 + .../github.com/TykTechnologies/gorpc/Makefile | 24 + .../TykTechnologies/gorpc/README.md | 121 +++ vendor/github.com/TykTechnologies/gorpc/TODO | 3 + .../TykTechnologies/gorpc/client.go | 718 ++++++++++++++++++ .../TykTechnologies/gorpc/common.go | 118 +++ .../TykTechnologies/gorpc/conn_stats.go | 125 +++ .../TykTechnologies/gorpc/conn_stats_386.go | 113 +++ .../gorpc/conn_stats_generic.go | 92 +++ .../TykTechnologies/gorpc/dispatcher.go | 620 +++++++++++++++ .../github.com/TykTechnologies/gorpc/doc.go | 15 + .../TykTechnologies/gorpc/encoding.go | 118 +++ .../TykTechnologies/gorpc/server.go | 438 +++++++++++ .../TykTechnologies/gorpc/transport.go | 228 ++++++ 14 files changed, 2755 insertions(+) create mode 100644 vendor/github.com/TykTechnologies/gorpc/LICENSE create mode 100644 vendor/github.com/TykTechnologies/gorpc/Makefile create mode 100644 vendor/github.com/TykTechnologies/gorpc/README.md create mode 100644 vendor/github.com/TykTechnologies/gorpc/TODO create mode 100644 vendor/github.com/TykTechnologies/gorpc/client.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/common.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/conn_stats.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/conn_stats_386.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/conn_stats_generic.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/dispatcher.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/doc.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/encoding.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/server.go create mode 100644 vendor/github.com/TykTechnologies/gorpc/transport.go diff --git a/vendor/github.com/TykTechnologies/gorpc/LICENSE b/vendor/github.com/TykTechnologies/gorpc/LICENSE new file mode 100644 index 000000000000..a43e7ad1c928 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Aliaksandr Valialkin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/TykTechnologies/gorpc/Makefile b/vendor/github.com/TykTechnologies/gorpc/Makefile new file mode 100644 index 000000000000..a59dca751c50 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/Makefile @@ -0,0 +1,24 @@ +test: + GOMAXPROCS=1 go test + GOMAXPROCS=2 go test + GOMAXPROCS=4 go test + GOMAXPROCS=8 go test + +test-386: + GOARCH=386 GOMAXPROCS=1 go test + GOARCH=386 GOMAXPROCS=2 go test + GOARCH=386 GOMAXPROCS=4 go test + GOARCH=386 GOMAXPROCS=8 go test + +bench-1-goprocs: + GOMAXPROCS=1 go test -test.bench=".*" + +bench-2-goprocs: + GOMAXPROCS=2 go test -test.bench=".*" + +bench-4-goprocs: + GOMAXPROCS=4 go test -test.bench=".*" + +bench-8-goprocs: + GOMAXPROCS=8 go test -test.bench=".*" + diff --git a/vendor/github.com/TykTechnologies/gorpc/README.md b/vendor/github.com/TykTechnologies/gorpc/README.md new file mode 100644 index 000000000000..0b3d7e5f68bb --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/README.md @@ -0,0 +1,121 @@ +gorpc +===== + +Simple, fast and scalable golang RPC library for high load. + + +Gorpc provides the following features useful for highly loaded projects +with RPC: + +* It minimizes the number of connect() syscalls by pipelining request + and response messages over a single connection. + +* It minimizes the number of send() syscalls by packing as much + as possible pending requests and responses into a single compressed buffer + before passing it into send() syscall. + +* It minimizes the number of recv() syscalls by reading and buffering as much + as possible data from the network. + +* It supports RPC batching, which allows preparing multiple requests and sending + them to the server in a single batch. + +These features help the OS minimizing overhead (CPU load, the number of +TCP connections in TIME_WAIT and CLOSE_WAIT states, the number of network +packets and the amount of network bandwidth) required for RPC processing under +high load. + + +Gorpc additionally provides the following features missing +in [net/rpc](http://golang.org/pkg/net/rpc/): + +* Client automatically manages connections and automatically reconnects + to the server on connection errors. +* Client supports response timeouts out of the box. +* Client supports RPC batching out of the box. +* Client detects stuck servers and immediately returns error to the caller. +* Client supports fast message passing to the Server, i.e. requests + without responses. +* Both Client and Server provide network stats and RPC stats out of the box. +* Commonly used RPC transports such as TCP, TLS and unix socket are available + out of the box. +* RPC transport compression is provided out of the box. +* Server provides graceful shutdown out of the box. +* Server supports RPC handlers' councurrency throttling out of the box. +* Server may pass client address to RPC handlers. +* Server gracefully handles panic in RPC handlers. +* Dispatcher accepts functions as RPC handlers. +* Dispatcher supports registering multiple receiver objects of the same type + under distinct names. +* Dispatcher supports RPC handlers with zero, one (request) or two (client + address and request) arguments and zero, one (either response or error) + or two (response, error) return values. + + +Dispatcher API provided by gorpc allows easily converting usual functions +and/or struct methods into RPC versions on both client and server sides. +See [Dispatcher examples](http://godoc.org/github.com/valyala/gorpc#Dispatcher) +for more details. + + +By default TCP connections are used as underlying gorpc transport. +But it is possible using arbitrary underlying transport - just provide custom +implementations for Client.Dial and Server.Listener. +RPC authentication, authorization and encryption can be easily implemented +via custom underlying transport and/or via OnConnect callbacks. +Currently gorpc provides TCP, TLS and unix socket transport out of the box. + + +Currently gorpc with default settings is successfully used in highly loaded +production environment serving up to 40K qps. Switching from http-based rpc +to gorpc reduced required network bandwidth from 300 Mbit/s to 24 Mbit/s. + + +Docs +==== + +See http://godoc.org/github.com/valyala/gorpc . + + +Usage +===== + +Server: +```go +s := &gorpc.Server{ + // Accept clients on this TCP address. + Addr: ":12345", + + // Echo handler - just return back the message we received from the client + Handler: func(clientAddr string, request interface{}) interface{} { + log.Printf("Obtained request %+v from the client %s\n", request, clientAddr) + return request + }, +} +if err := s.Serve(); err != nil { + log.Fatalf("Cannot start rpc server: %s", err) +} +``` + +Client: +```go +c := &gorpc.Client{ + // TCP address of the server. + Addr: "rpc.server.addr:12345", +} +c.Start() + +resp, err := c.Call("foobar") +if err != nil { + log.Fatalf("Error when sending request to server: %s", err) +} +if resp.(string) != "foobar" { + log.Fatalf("Unexpected response from the server: %+v", resp) +} +``` + +Both client and server collect connection stats - the number of bytes +read / written and the number of calls / errors to send(), recv(), connect() +and accept(). This stats is available at Client.Stats and Server.Stats. + +See tests for more usage examples. diff --git a/vendor/github.com/TykTechnologies/gorpc/TODO b/vendor/github.com/TykTechnologies/gorpc/TODO new file mode 100644 index 000000000000..31eac72237e2 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/TODO @@ -0,0 +1,3 @@ +- Add support for channel request and response. +- Add support for io.Writer, io.Reader and io.ReadWriter request and response. +- Add HTTP transport via HTTP connection hijacking similar to net/rpc. diff --git a/vendor/github.com/TykTechnologies/gorpc/client.go b/vendor/github.com/TykTechnologies/gorpc/client.go new file mode 100644 index 000000000000..cba9e4c7bd8a --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/client.go @@ -0,0 +1,718 @@ +package gorpc + +import ( + "fmt" + "io" + "net" + "sync" + "time" +) + +// Client implements RPC client. +// +// The client must be started with Client.Start() before use. +// +// It is absolutely safe and encouraged using a single client across arbitrary +// number of concurrently running goroutines. +// +// Default client settings are optimized for high load, so don't override +// them without valid reason. +type Client struct { + // Server address to connect to. + // + // The address format depends on the underlying transport provided + // by Client.Dial. The following transports are provided out of the box: + // * TCP - see NewTCPClient() and NewTCPServer(). + // * TLS - see NewTLSClient() and NewTLSServer(). + // * Unix sockets - see NewUnixClient() and NewUnixServer(). + // + // By default TCP transport is used. + Addr string + + // The number of concurrent connections the client should establish + // to the sever. + // By default only one connection is established. + Conns int + + // The maximum number of pending requests in the queue. + // + // The number of pending requsts should exceed the expected number + // of concurrent goroutines calling client's methods. + // Otherwise a lot of ClientError.Overflow errors may appear. + // + // Default is DefaultPendingMessages. + PendingRequests int + + // Delay between request flushes. + // + // Negative values lead to immediate requests' sending to the server + // without their buffering. This minimizes rpc latency at the cost + // of higher CPU and network usage. + // + // Default value is DefaultFlushDelay. + FlushDelay time.Duration + + // Maximum request time. + // Default value is DefaultRequestTimeout. + RequestTimeout time.Duration + + // Disable data compression. + // By default data compression is enabled. + DisableCompression bool + + // Size of send buffer per each underlying connection in bytes. + // Default value is DefaultBufferSize. + SendBufferSize int + + // Size of recv buffer per each underlying connection in bytes. + // Default value is DefaultBufferSize. + RecvBufferSize int + + // OnConnect is called whenever connection to server is established. + // The callback can be used for authentication/authorization/encryption + // and/or for custom transport wrapping. + // + // See also Dial callback, which can be used for sophisticated + // transport implementation. + OnConnect OnConnectFunc + + // The client calls this callback when it needs new connection + // to the server. + // The client passes Client.Addr into Dial(). + // + // Override this callback if you want custom underlying transport + // and/or authentication/authorization. + // Don't forget overriding Server.Listener accordingly. + // + // See also OnConnect for authentication/authorization purposes. + // + // * NewTLSClient() and NewTLSServer() can be used for encrypted rpc. + // * NewUnixClient() and NewUnixServer() can be used for fast local + // inter-process rpc. + // + // By default it returns TCP connections established to the Client.Addr. + Dial DialFunc + + // LogError is used for error logging. + // + // By default the function set via SetErrorLogger() is used. + LogError LoggerFunc + + // Connection statistics. + // + // The stats doesn't reset automatically. Feel free resetting it + // any time you wish. + Stats ConnStats + + requestsChan chan *AsyncResult + + clientStopChan chan struct{} + stopWg sync.WaitGroup +} + +// Start starts rpc client. Establishes connection to the server on Client.Addr. +// +// All the response types the server may return must be registered +// via gorpc.RegisterType() before starting the client. +// There is no need in registering base Go types such as int, string, bool, +// float64, etc. or arrays, slices and maps containing base Go types. +func (c *Client) Start() { + if c.LogError == nil { + c.LogError = errorLogger + } + if c.clientStopChan != nil { + panic("gorpc.Client: the given client is already started. Call Client.Stop() before calling Client.Start() again!") + } + + if c.PendingRequests <= 0 { + c.PendingRequests = DefaultPendingMessages + } + if c.FlushDelay == 0 { + c.FlushDelay = DefaultFlushDelay + } + if c.RequestTimeout <= 0 { + c.RequestTimeout = DefaultRequestTimeout + } + if c.SendBufferSize <= 0 { + c.SendBufferSize = DefaultBufferSize + } + if c.RecvBufferSize <= 0 { + c.RecvBufferSize = DefaultBufferSize + } + + c.requestsChan = make(chan *AsyncResult, c.PendingRequests) + c.clientStopChan = make(chan struct{}) + + if c.Conns <= 0 { + c.Conns = 1 + } + if c.Dial == nil { + c.Dial = defaultDial + } + + for i := 0; i < c.Conns; i++ { + c.stopWg.Add(1) + go clientHandler(c) + } +} + +// Stop stops rpc client. Stopped client can be started again. +func (c *Client) Stop() { + if c.clientStopChan == nil { + panic("gorpc.Client: the client must be started before stopping it") + } + close(c.clientStopChan) + c.stopWg.Wait() + c.clientStopChan = nil +} + +// Call sends the given request to the server and obtains response +// from the server. +// Returns non-nil error if the response cannot be obtained during +// Client.RequestTimeout or server connection problems occur. +// The returned error can be casted to ClientError. +// +// Request and response types may be arbitrary. All the response types +// the server may return must be registered via gorpc.RegisterType() before +// starting the client. +// There is no need in registering base Go types such as int, string, bool, +// float64, etc. or arrays, slices and maps containing base Go types. +// +// Hint: use Dispatcher for distinct calls' construction. +// +// Don't forget starting the client with Client.Start() before calling Client.Call(). +func (c *Client) Call(request interface{}) (response interface{}, err error) { + return c.CallTimeout(request, c.RequestTimeout) +} + +// CallTimeout sends the given request to the server and obtains response +// from the server. +// Returns non-nil error if the response cannot be obtained during +// the given timeout or server connection problems occur. +// The returned error can be casted to ClientError. +// +// Request and response types may be arbitrary. All the response types +// the server may return must be registered via gorpc.RegisterType() before +// starting the client. +// There is no need in registering base Go types such as int, string, bool, +// float64, etc. or arrays, slices and maps containing base Go types. +// +// Hint: use Dispatcher for distinct calls' construction. +// +// Don't forget starting the client with Client.Start() before calling Client.Call(). +func (c *Client) CallTimeout(request interface{}, timeout time.Duration) (response interface{}, err error) { + var m *AsyncResult + if m, err = c.CallAsync(request); err != nil { + return nil, err + } + + t := acquireTimer(timeout) + + select { + case <-m.Done: + response, err = m.Response, m.Error + case <-t.C: + err = getClientTimeoutError(c, timeout) + } + + releaseTimer(t) + return +} + +func getClientTimeoutError(c *Client, timeout time.Duration) error { + err := fmt.Errorf("gorpc.Client: [%s]. Cannot obtain response during timeout=%s", c.Addr, timeout) + c.LogError("%s", err) + return &ClientError{ + Timeout: true, + err: err, + } +} + +// Send sends the given request to the server and doesn't wait for response. +// +// Since this is 'fire and forget' function, which never waits for response, +// it cannot guarantee that the server receives and successfully processes +// the given request. Though in most cases under normal conditions requests +// should reach the server and it should successfully process them. +// Send semantics is similar to UDP messages' semantics. +// +// The server may return arbitrary response on Send() request, but the response +// is totally ignored. +// +// Don't forget starting the client with Client.Start() before calling Client.Send(). +func (c *Client) Send(request interface{}) error { + _, err := c.callAsync(request, true) + return err +} + +// AsyncResult is a result returned from Client.CallAsync(). +type AsyncResult struct { + // The response can be read only after <-Done unblocks. + Response interface{} + + // The error can be read only after <-Done unblocks. + // The error can be casted to ClientError. + Error error + + // Response and Error become available after <-Done unblocks. + Done <-chan struct{} + + request interface{} + t time.Time + done chan struct{} +} + +// CallAsync starts async rpc call. +// +// Rpc call is complete after <-AsyncResult.Done unblocks. +// If you want canceling the request, just throw away the returned AsyncResult. +// +// CallAsync doesn't respect Client.RequestTimeout - response timeout +// may be controlled by the caller via something like: +// +// r := c.CallAsync("foobar") +// select { +// case <-time.After(c.RequestTimeout): +// log.Printf("rpc timeout!") +// case <-r.Done: +// processResponse(r.Response, r.Error) +// } +// +// Don't forget starting the client with Client.Start() before +// calling Client.CallAsync(). +func (c *Client) CallAsync(request interface{}) (*AsyncResult, error) { + return c.callAsync(request, false) +} + +func (c *Client) callAsync(request interface{}, skipResponse bool) (ar *AsyncResult, err error) { + m := &AsyncResult{ + request: request, + } + if !skipResponse { + m.t = time.Now() + m.done = make(chan struct{}) + m.Done = m.done + } + + select { + case c.requestsChan <- m: + return m, nil + default: + err = fmt.Errorf("gorpc.Client: [%s]. Requests' queue with size=%d is overflown. Try increasing Client.PendingRequests value", c.Addr, cap(c.requestsChan)) + c.LogError("%s", err) + err = &ClientError{ + Overflow: true, + err: err, + } + return nil, err + } +} + +// Batch allows grouping and executing multiple RPCs in a single batch. +// +// Batch may be created via Client.NewBatch(). +type Batch struct { + c *Client + ops []*BatchResult + opsLock sync.Mutex +} + +// BatchResult is a result returned from Batch.Add*(). +type BatchResult struct { + // The response can be read only after Batch.Call*() returns. + Response interface{} + + // The error can be read only after Batch.Call*() returns. + // The error can be casted to ClientError. + Error error + + // <-Done unblocks after Batch.Call*() returns. + // Response and Error become available after <-Done unblocks. + Done <-chan struct{} + + request interface{} + ctx interface{} + done chan struct{} +} + +// NewBatch creates new RPC batch. +// +// It is safe creating multiple concurrent batches from a single client. +// +// Don't forget starting the client with Client.Start() before working +// with batched RPC. +func (c *Client) NewBatch() *Batch { + return &Batch{ + c: c, + } +} + +// Add ads new request to the RPC batch. +// +// The order of batched RPCs execution on the server is unspecified. +// +// All the requests added to the batch are sent to the server at once +// when Batch.Call*() is called. +// +// It is safe adding multiple requests to the same batch from concurrently +// running goroutines. +func (b *Batch) Add(request interface{}) *BatchResult { + return b.add(request, false) +} + +// AddSkipResponse adds new request to the RPC batch and doesn't care +// about the response. +// +// The order of batched RPCs execution on the server is unspecified. +// +// All the requests added to the batch are sent to the server at once +// when Batch.Call*() is called. +// +// It is safe adding multiple requests to the same batch from concurrently +// running goroutines. +func (b *Batch) AddSkipResponse(request interface{}) { + b.add(request, true) +} + +func (b *Batch) add(request interface{}, skipResponse bool) *BatchResult { + br := &BatchResult{ + request: request, + } + if !skipResponse { + br.done = make(chan struct{}) + br.Done = br.done + } + + b.opsLock.Lock() + b.ops = append(b.ops, br) + b.opsLock.Unlock() + + return br +} + +// Call calls all the RPCs added via Batch.Add(). +// +// The order of batched RPCs execution on the server is unspecified. +// +// The caller may read all BatchResult contents returned from Batch.Add() +// after the Call returns. +// +// It is guaranteed that all <-BatchResult.Done channels are unblocked after +// the Call returns. +func (b *Batch) Call() error { + return b.CallTimeout(b.c.RequestTimeout) +} + +// CallTimeout calls all the RPCs added via Batch.Add() and waits for +// all the RPC responses during the given timeout. +// +// The caller may read all BatchResult contents returned from Batch.Add() +// after the CallTimeout returns. +// +// It is guaranteed that all <-BatchResult.Done channels are unblocked after +// the CallTimeout returns. +func (b *Batch) CallTimeout(timeout time.Duration) error { + b.opsLock.Lock() + ops := b.ops + b.ops = nil + b.opsLock.Unlock() + + results := make([]*AsyncResult, len(ops)) + for i := range ops { + op := ops[i] + r, err := callAsyncRetry(b.c, op.request, op.done == nil, 5) + if err != nil { + return err + } + results[i] = r + } + + t := acquireTimer(timeout) + + for i := range results { + r := results[i] + op := ops[i] + if op.done == nil { + continue + } + + select { + case <-r.Done: + op.Response, op.Error = r.Response, r.Error + close(op.done) + case <-t.C: + releaseTimer(t) + err := getClientTimeoutError(b.c, timeout) + for ; i < len(results); i++ { + op = ops[i] + op.Error = err + if op.done != nil { + close(op.done) + } + } + return err + } + } + + releaseTimer(t) + + return nil +} + +func callAsyncRetry(c *Client, request interface{}, skipResponse bool, retriesCount int) (*AsyncResult, error) { + retriesCount++ + for { + ar, err := c.callAsync(request, skipResponse) + if err == nil { + return ar, nil + } + if !err.(*ClientError).Overflow { + return nil, err + } + retriesCount-- + if retriesCount <= 0 { + return nil, err + } + time.Sleep(10 * time.Millisecond) + } +} + +// ClientError is an error Client methods can return. +type ClientError struct { + // Set if the error is timeout-related. + Timeout bool + + // Set if the error is connection-related. + Connection bool + + // Set if the error is server-related. + Server bool + + // Set if the error is related to internal resources' overflow. + // Increase PendingRequests if you see a lot of such errors. + Overflow bool + + err error +} + +func (e *ClientError) Error() string { + return e.err.Error() +} + +func clientHandler(c *Client) { + defer c.stopWg.Done() + + var conn net.Conn + var err error + + for { + dialChan := make(chan struct{}) + go func() { + if conn, err = c.Dial(c.Addr); err != nil { + c.LogError("gorpc.Client: [%s]. Cannot establish rpc connection: [%s]", c.Addr, err) + time.Sleep(time.Second) + } + close(dialChan) + }() + + select { + case <-c.clientStopChan: + return + case <-dialChan: + c.Stats.incDialCalls() + } + + if err != nil { + c.Stats.incDialErrors() + continue + } + clientHandleConnection(c, conn) + } +} + +func clientHandleConnection(c *Client, conn net.Conn) { + if c.OnConnect != nil { + newConn, _, err := c.OnConnect(conn) + if err != nil { + c.LogError("gorpc.Client: [%s]. OnConnect error: [%s]", c.Addr, err) + conn.Close() + return + } + conn = newConn + } + + var buf [1]byte + if !c.DisableCompression { + buf[0] = 1 + } + _, err := conn.Write(buf[:]) + if err != nil { + c.LogError("gorpc.Client: [%s]. Error when writing handshake to server: [%s]", c.Addr, err) + conn.Close() + return + } + + stopChan := make(chan struct{}) + + pendingRequests := make(map[uint64]*AsyncResult) + var pendingRequestsLock sync.Mutex + + writerDone := make(chan error, 1) + go clientWriter(c, conn, pendingRequests, &pendingRequestsLock, stopChan, writerDone) + + readerDone := make(chan error, 1) + go clientReader(c, conn, pendingRequests, &pendingRequestsLock, readerDone) + + select { + case err = <-writerDone: + close(stopChan) + conn.Close() + <-readerDone + case err = <-readerDone: + close(stopChan) + conn.Close() + <-writerDone + case <-c.clientStopChan: + close(stopChan) + conn.Close() + <-readerDone + <-writerDone + } + + if err != nil { + c.LogError("%s", err) + err = &ClientError{ + Connection: true, + err: err, + } + } + for _, m := range pendingRequests { + m.Error = err + if m.done != nil { + close(m.done) + } + } +} + +func clientWriter(c *Client, w io.Writer, pendingRequests map[uint64]*AsyncResult, pendingRequestsLock *sync.Mutex, stopChan <-chan struct{}, done chan<- error) { + var err error + defer func() { done <- err }() + + e := newMessageEncoder(w, c.SendBufferSize, !c.DisableCompression, &c.Stats) + defer e.Close() + + t := time.NewTimer(c.FlushDelay) + var flushChan <-chan time.Time + var wr wireRequest + var msgID uint64 + for { + var m *AsyncResult + + select { + case m = <-c.requestsChan: + default: + select { + case <-stopChan: + return + case m = <-c.requestsChan: + case <-flushChan: + if err = e.Flush(); err != nil { + err = fmt.Errorf("gorpc.Client: [%s]. Cannot flush requests to underlying stream: [%s]", c.Addr, err) + return + } + flushChan = nil + continue + } + } + + if flushChan == nil { + flushChan = getFlushChan(t, c.FlushDelay) + } + + if m.done == nil { + wr.ID = 0 + } else { + msgID++ + if msgID == 0 { + msgID = 1 + } + pendingRequestsLock.Lock() + n := len(pendingRequests) + for { + if _, ok := pendingRequests[msgID]; !ok { + break + } + msgID++ + } + pendingRequests[msgID] = m + pendingRequestsLock.Unlock() + + if n > 10*c.PendingRequests { + err = fmt.Errorf("gorpc.Client: [%s]. The server didn't return %d responses yet. Closing server connection in order to prevent client resource leaks", c.Addr, n) + return + } + + wr.ID = msgID + } + + wr.Request = m.request + m.request = nil + if err = e.Encode(wr); err != nil { + err = fmt.Errorf("gorpc.Client: [%s]. Cannot send request to wire: [%s]", c.Addr, err) + return + } + wr.Request = nil + } +} + +func clientReader(c *Client, r io.Reader, pendingRequests map[uint64]*AsyncResult, pendingRequestsLock *sync.Mutex, done chan<- error) { + var err error + defer func() { + if r := recover(); r != nil { + if err == nil { + err = fmt.Errorf("gorpc.Client: [%s]. Panic when reading data from server: %v", c.Addr, r) + } + } + done <- err + }() + + d := newMessageDecoder(r, c.RecvBufferSize, !c.DisableCompression, &c.Stats) + defer d.Close() + + var wr wireResponse + for { + if err = d.Decode(&wr); err != nil { + err = fmt.Errorf("gorpc.Client: [%s]. Cannot decode response: [%s]", c.Addr, err) + return + } + + pendingRequestsLock.Lock() + m, ok := pendingRequests[wr.ID] + if ok { + delete(pendingRequests, wr.ID) + } + pendingRequestsLock.Unlock() + + if !ok { + err = fmt.Errorf("gorpc.Client: [%s]. Unexpected msgID=[%d] obtained from server", c.Addr, wr.ID) + return + } + + m.Response = wr.Response + + wr.ID = 0 + wr.Response = nil + if wr.Error != "" { + m.Error = &ClientError{ + Server: true, + err: fmt.Errorf("gorpc.Client: [%s]. Server error: [%s]", c.Addr, wr.Error), + } + wr.Error = "" + } + + close(m.done) + + c.Stats.incRPCCalls() + c.Stats.incRPCTime(uint64(time.Since(m.t).Seconds() * 1000)) + } +} diff --git a/vendor/github.com/TykTechnologies/gorpc/common.go b/vendor/github.com/TykTechnologies/gorpc/common.go new file mode 100644 index 000000000000..bb0823b036da --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/common.go @@ -0,0 +1,118 @@ +package gorpc + +import ( + "fmt" + "log" + "net" + "sync" + "time" +) + +const ( + // DefaultConcurrency is the default number of concurrent rpc calls + // the server can process. + DefaultConcurrency = 8 * 1024 + + // DefaultRequestTimeout is the default timeout for client request. + DefaultRequestTimeout = 20 * time.Second + + // DefaultPendingMessages is the default number of pending messages + // handled by Client and Server. + DefaultPendingMessages = 32 * 1024 + + // DefaultFlushDelay is the default delay between message flushes + // on Client and Server. + DefaultFlushDelay = -1 + + // DefaultBufferSize is the default size for Client and Server buffers. + DefaultBufferSize = 64 * 1024 +) + +// OnConnectFunc is a callback, which may be called by both Client and Server +// on every connection creation if assigned +// to Client.OnConnect / Server.OnConnect. +// +// remoteAddr is the address of the remote end for the established +// connection rwc. +// +// The callback must return either rwc itself or a rwc wrapper. +// The returned connection wrapper MUST send all the data to the underlying +// rwc on every Write() call, otherwise the connection will hang forever. +// +// The callback may be used for authentication/authorization and/or custom +// transport wrapping. +type OnConnectFunc func(rwc net.Conn) (net.Conn, string, error) + +// LoggerFunc is an error logging function to pass to gorpc.SetErrorLogger(). +type LoggerFunc func(format string, args ...interface{}) + +var errorLogger = LoggerFunc(log.Printf) + +// SetErrorLogger sets the given error logger to use in gorpc. +// +// By default log.Printf is used for error logging. +func SetErrorLogger(f LoggerFunc) { + errorLogger = f +} + +// NilErrorLogger discards all error messages. +// +// Pass NilErrorLogger to SetErrorLogger() in order to suppress error log generated +// by gorpc. +func NilErrorLogger(format string, args ...interface{}) {} + +func logPanic(format string, args ...interface{}) { + errorLogger(format, args...) + s := fmt.Sprintf(format, args...) + panic(s) +} + +var timerPool sync.Pool + +func acquireTimer(timeout time.Duration) *time.Timer { + tv := timerPool.Get() + if tv == nil { + return time.NewTimer(timeout) + } + + t := tv.(*time.Timer) + if t.Reset(timeout) { + panic("BUG: Active timer trapped into acquireTimer()") + } + return t +} + +func releaseTimer(t *time.Timer) { + if !t.Stop() { + // Collect possibly added time from the channel + // if timer has been stopped and nobody collected its' value. + select { + case <-t.C: + default: + } + } + + timerPool.Put(t) +} + +var closedFlushChan = make(chan time.Time) + +func init() { + close(closedFlushChan) +} + +func getFlushChan(t *time.Timer, flushDelay time.Duration) <-chan time.Time { + if flushDelay <= 0 { + return closedFlushChan + } + + if !t.Stop() { + // Exhaust expired timer's chan. + select { + case <-t.C: + default: + } + } + t.Reset(flushDelay) + return t.C +} diff --git a/vendor/github.com/TykTechnologies/gorpc/conn_stats.go b/vendor/github.com/TykTechnologies/gorpc/conn_stats.go new file mode 100644 index 000000000000..eadd6166da82 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/conn_stats.go @@ -0,0 +1,125 @@ +package gorpc + +import ( + "io" + "sync" + "time" +) + +// ConnStats provides connection statistics. Applied to both gorpc.Client +// and gorpc.Server. +// +// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, +// since the original stats can be updated by concurrently running goroutines. +type ConnStats struct { + // The number of rpc calls performed. + RPCCalls uint64 + + // The total aggregate time for all rpc calls in milliseconds. + // + // This time can be used for calculating the average response time + // per RPC: + // avgRPCTtime = RPCTime / RPCCalls + RPCTime uint64 + + // The number of bytes written to the underlying connections. + BytesWritten uint64 + + // The number of bytes read from the underlying connections. + BytesRead uint64 + + // The number of Read() calls. + ReadCalls uint64 + + // The number of Read() errors. + ReadErrors uint64 + + // The number of Write() calls. + WriteCalls uint64 + + // The number of Write() errors. + WriteErrors uint64 + + // The number of Dial() calls. + DialCalls uint64 + + // The number of Dial() errors. + DialErrors uint64 + + // The number of Accept() calls. + AcceptCalls uint64 + + // The number of Accept() errors. + AcceptErrors uint64 + + // lock is for 386 builds. See https://github.com/valyala/gorpc/issues/5 . + lock sync.Mutex +} + +// AvgRPCTime returns the average RPC execution time. +// +// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, +// since the original stats can be updated by concurrently running goroutines. +func (cs *ConnStats) AvgRPCTime() time.Duration { + return time.Duration(float64(cs.RPCTime)/float64(cs.RPCCalls)) * time.Millisecond +} + +// AvgRPCBytes returns the average bytes sent / received per RPC. +// +// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, +// since the original stats can be updated by concurrently running goroutines. +func (cs *ConnStats) AvgRPCBytes() (send float64, recv float64) { + return float64(cs.BytesWritten) / float64(cs.RPCCalls), float64(cs.BytesRead) / float64(cs.RPCCalls) +} + +// AvgRPCCalls returns the average number of write() / read() syscalls per PRC. +// +// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, +// since the original stats can be updated by concurrently running goroutines. +func (cs *ConnStats) AvgRPCCalls() (write float64, read float64) { + return float64(cs.WriteCalls) / float64(cs.RPCCalls), float64(cs.ReadCalls) / float64(cs.RPCCalls) +} + +type writerCounter struct { + w io.Writer + cs *ConnStats +} + +type readerCounter struct { + r io.Reader + cs *ConnStats +} + +func newWriterCounter(w io.Writer, cs *ConnStats) io.Writer { + return &writerCounter{ + w: w, + cs: cs, + } +} + +func newReaderCounter(r io.Reader, cs *ConnStats) io.Reader { + return &readerCounter{ + r: r, + cs: cs, + } +} + +func (w *writerCounter) Write(p []byte) (int, error) { + n, err := w.w.Write(p) + w.cs.incWriteCalls() + if err != nil { + w.cs.incWriteErrors() + } + w.cs.addBytesWritten(uint64(n)) + return n, err +} + +func (r *readerCounter) Read(p []byte) (int, error) { + n, err := r.r.Read(p) + r.cs.incReadCalls() + if err != nil { + r.cs.incReadErrors() + } + r.cs.addBytesRead(uint64(n)) + return n, err +} diff --git a/vendor/github.com/TykTechnologies/gorpc/conn_stats_386.go b/vendor/github.com/TykTechnologies/gorpc/conn_stats_386.go new file mode 100644 index 000000000000..03c7b4d17307 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/conn_stats_386.go @@ -0,0 +1,113 @@ +// Separate implementation for 386, since it has broken support for atomics. +// See https://github.com/valyala/gorpc/issues/5 for details. + +// +build 386 + +package gorpc + +import ( + "sync" +) + +// Snapshot returns connection statistics' snapshot. +// +// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, +// since the original stats can be updated by concurrently running goroutines. +func (cs *ConnStats) Snapshot() *ConnStats { + cs.lock.Lock() + snapshot := *cs + cs.lock.Unlock() + + snapshot.lock = sync.Mutex{} + return &snapshot +} + +// Reset resets all the stats counters. +func (cs *ConnStats) Reset() { + cs.lock.Lock() + cs.RPCCalls = 0 + cs.RPCTime = 0 + cs.BytesWritten = 0 + cs.BytesRead = 0 + cs.WriteCalls = 0 + cs.WriteErrors = 0 + cs.ReadCalls = 0 + cs.ReadErrors = 0 + cs.DialCalls = 0 + cs.DialErrors = 0 + cs.AcceptCalls = 0 + cs.AcceptErrors = 0 + cs.lock.Unlock() +} + +func (cs *ConnStats) incRPCCalls() { + cs.lock.Lock() + cs.RPCCalls++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incRPCTime(dt uint64) { + cs.lock.Lock() + cs.RPCTime += dt + cs.lock.Unlock() +} + +func (cs *ConnStats) addBytesWritten(n uint64) { + cs.lock.Lock() + cs.BytesWritten += n + cs.lock.Unlock() +} + +func (cs *ConnStats) addBytesRead(n uint64) { + cs.lock.Lock() + cs.BytesRead += n + cs.lock.Unlock() +} + +func (cs *ConnStats) incReadCalls() { + cs.lock.Lock() + cs.ReadCalls++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incReadErrors() { + cs.lock.Lock() + cs.ReadErrors++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incWriteCalls() { + cs.lock.Lock() + cs.WriteCalls++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incWriteErrors() { + cs.lock.Lock() + cs.WriteErrors++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incDialCalls() { + cs.lock.Lock() + cs.DialCalls++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incDialErrors() { + cs.lock.Lock() + cs.DialErrors++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incAcceptCalls() { + cs.lock.Lock() + cs.AcceptCalls++ + cs.lock.Unlock() +} + +func (cs *ConnStats) incAcceptErrors() { + cs.lock.Lock() + cs.AcceptErrors++ + cs.lock.Unlock() +} diff --git a/vendor/github.com/TykTechnologies/gorpc/conn_stats_generic.go b/vendor/github.com/TykTechnologies/gorpc/conn_stats_generic.go new file mode 100644 index 000000000000..110010876d8a --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/conn_stats_generic.go @@ -0,0 +1,92 @@ +// +build !386 + +package gorpc + +import ( + "sync/atomic" +) + +// Snapshot returns connection statistics' snapshot. +// +// Use stats returned from ConnStats.Snapshot() on live Client and / or Server, +// since the original stats can be updated by concurrently running goroutines. +func (cs *ConnStats) Snapshot() *ConnStats { + return &ConnStats{ + RPCCalls: atomic.LoadUint64(&cs.RPCCalls), + RPCTime: atomic.LoadUint64(&cs.RPCTime), + BytesWritten: atomic.LoadUint64(&cs.BytesWritten), + BytesRead: atomic.LoadUint64(&cs.BytesRead), + ReadCalls: atomic.LoadUint64(&cs.ReadCalls), + ReadErrors: atomic.LoadUint64(&cs.ReadErrors), + WriteCalls: atomic.LoadUint64(&cs.WriteCalls), + WriteErrors: atomic.LoadUint64(&cs.WriteErrors), + DialCalls: atomic.LoadUint64(&cs.DialCalls), + DialErrors: atomic.LoadUint64(&cs.DialErrors), + AcceptCalls: atomic.LoadUint64(&cs.AcceptCalls), + AcceptErrors: atomic.LoadUint64(&cs.AcceptErrors), + } +} + +// Reset resets all the stats counters. +func (cs *ConnStats) Reset() { + atomic.StoreUint64(&cs.RPCCalls, 0) + atomic.StoreUint64(&cs.RPCTime, 0) + atomic.StoreUint64(&cs.BytesWritten, 0) + atomic.StoreUint64(&cs.BytesRead, 0) + atomic.StoreUint64(&cs.WriteCalls, 0) + atomic.StoreUint64(&cs.WriteErrors, 0) + atomic.StoreUint64(&cs.ReadCalls, 0) + atomic.StoreUint64(&cs.ReadErrors, 0) + atomic.StoreUint64(&cs.DialCalls, 0) + atomic.StoreUint64(&cs.DialErrors, 0) + atomic.StoreUint64(&cs.AcceptCalls, 0) + atomic.StoreUint64(&cs.AcceptErrors, 0) +} + +func (cs *ConnStats) incRPCCalls() { + atomic.AddUint64(&cs.RPCCalls, 1) +} + +func (cs *ConnStats) incRPCTime(dt uint64) { + atomic.AddUint64(&cs.RPCTime, dt) +} + +func (cs *ConnStats) addBytesWritten(n uint64) { + atomic.AddUint64(&cs.BytesWritten, n) +} + +func (cs *ConnStats) addBytesRead(n uint64) { + atomic.AddUint64(&cs.BytesRead, n) +} + +func (cs *ConnStats) incReadCalls() { + atomic.AddUint64(&cs.ReadCalls, 1) +} + +func (cs *ConnStats) incReadErrors() { + atomic.AddUint64(&cs.ReadErrors, 1) +} + +func (cs *ConnStats) incWriteCalls() { + atomic.AddUint64(&cs.WriteCalls, 1) +} + +func (cs *ConnStats) incWriteErrors() { + atomic.AddUint64(&cs.WriteErrors, 1) +} + +func (cs *ConnStats) incDialCalls() { + atomic.AddUint64(&cs.DialCalls, 1) +} + +func (cs *ConnStats) incDialErrors() { + atomic.AddUint64(&cs.DialErrors, 1) +} + +func (cs *ConnStats) incAcceptCalls() { + atomic.AddUint64(&cs.AcceptCalls, 1) +} + +func (cs *ConnStats) incAcceptErrors() { + atomic.AddUint64(&cs.AcceptErrors, 1) +} diff --git a/vendor/github.com/TykTechnologies/gorpc/dispatcher.go b/vendor/github.com/TykTechnologies/gorpc/dispatcher.go new file mode 100644 index 000000000000..84f698060430 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/dispatcher.go @@ -0,0 +1,620 @@ +package gorpc + +import ( + "errors" + "fmt" + "reflect" + "strings" + "sync" + "time" +) + +// Dispatcher helps constructing HandlerFunc for dispatching across multiple +// functions and/or services. +// +// Dispatcher also automatically registers all request and response types +// for all functions and/or methods registered via AddFunc() and AddService(), +// so there is no need in calling RegisterType() for them. +// +// See examples for details. +type Dispatcher struct { + serviceMap map[string]*serviceData +} + +type serviceData struct { + sv reflect.Value + funcMap map[string]*funcData +} + +type funcData struct { + inNum int + reqt reflect.Type + fv reflect.Value +} + +// NewDispatcher returns new dispatcher. +func NewDispatcher() *Dispatcher { + return &Dispatcher{ + serviceMap: make(map[string]*serviceData), + } +} + +// AddFunc registers the given function f under the name funcName. +// +// The function must accept zero, one or two input arguments. +// If the function has two arguments, then the first argument must have +// string type - the server will pass client address in this parameter. +// +// The function must return zero, one or two values. +// * If the function has two return values, then the second value must have +// error type - the server will propagate this error to the client. +// +// * If the function returns only error value, then the server treats it +// as error, not return value, when sending to the client. +// +// Arbitrary number of functions can be registered in the dispatcher. +// +// See examples for details. +func (d *Dispatcher) AddFunc(funcName string, f interface{}) { + sd, ok := d.serviceMap[""] + if !ok { + sd = &serviceData{ + funcMap: make(map[string]*funcData), + } + d.serviceMap[""] = sd + } + + if _, ok := sd.funcMap[funcName]; ok { + logPanic("gorpc.Dispatcher: function %s has been already registered", funcName) + } + + fd := &funcData{ + fv: reflect.Indirect(reflect.ValueOf(f)), + } + var err error + if fd.inNum, fd.reqt, err = validateFunc(funcName, fd.fv, false); err != nil { + logPanic("gorpc.Disaptcher: %s", err) + } + sd.funcMap[funcName] = fd +} + +// AddService registers public methods of the given service under +// the given name serviceName. +// +// Since only public methods are registered, the service must have at least +// one public method. +// +// All public methods must conform requirements described in AddFunc(). +func (d *Dispatcher) AddService(serviceName string, service interface{}) { + if serviceName == "" { + logPanic("gorpc.Dispatcher: serviceName cannot be empty") + } + if _, ok := d.serviceMap[serviceName]; ok { + logPanic("gorpc.Dispatcher: service with name=[%s] has been already registered", serviceName) + } + + funcMap := make(map[string]*funcData) + + st := reflect.TypeOf(service) + if st.Kind() == reflect.Struct { + logPanic("gorpc.Dispatcher: service [%s] must be a pointer to struct, i.e. *%s", serviceName, st) + } + + for i := 0; i < st.NumMethod(); i++ { + mv := st.Method(i) + + if mv.PkgPath != "" { + // skip unexported methods + continue + } + + funcName := serviceName + "." + mv.Name + fd := &funcData{ + fv: mv.Func, + } + var err error + if fd.inNum, fd.reqt, err = validateFunc(funcName, fd.fv, true); err != nil { + logPanic("gorpc.Dispatcher: %s", err) + } + funcMap[mv.Name] = fd + } + + if len(funcMap) == 0 { + logPanic("gorpc.Dispatcher: the service %s has no methods suitable for rpc", serviceName) + } + + d.serviceMap[serviceName] = &serviceData{ + sv: reflect.ValueOf(service), + funcMap: funcMap, + } +} + +func validateFunc(funcName string, fv reflect.Value, isMethod bool) (inNum int, reqt reflect.Type, err error) { + if funcName == "" { + err = fmt.Errorf("funcName cannot be empty") + return + } + + ft := fv.Type() + if ft.Kind() != reflect.Func { + err = fmt.Errorf("function [%s] must be a function instead of %s", funcName, ft) + return + } + + inNum = ft.NumIn() + outNum := ft.NumOut() + + dt := 0 + if isMethod { + dt = 1 + } + + if inNum == 2+dt { + if ft.In(dt).Kind() != reflect.String { + err = fmt.Errorf("unexpected type for the first argument of the function [%s]: [%s]. Expected string", funcName, ft.In(dt)) + return + } + } else if inNum > 2+dt { + err = fmt.Errorf("unexpected number of arguments in the function [%s]: %d. Expected 0, 1 (request) or 2 (clientAddr, request)", funcName, inNum-dt) + return + } + + if outNum == 2 { + if !isErrorType(ft.Out(1)) { + err = fmt.Errorf("unexpected type for the second return value of the function [%s]: [%s]. Expected [%s]", funcName, ft.Out(1), errt) + return + } + } else if outNum > 2 { + err = fmt.Errorf("unexpected number of return values for the function %s: %d. Expected 0, 1 (response) or 2 (response, error)", funcName, outNum) + return + } + + if inNum > dt { + reqt = ft.In(inNum - 1) + if err = registerType("request", funcName, reqt); err != nil { + return + } + } + + if outNum > 0 { + respt := ft.Out(0) + if !isErrorType(respt) { + if err = registerType("response", funcName, ft.Out(0)); err != nil { + return + } + } + } + + return +} + +func registerType(s, funcName string, t reflect.Type) error { + if t.Kind() == reflect.Struct { + return fmt.Errorf("%s in the function [%s] should be passed by reference, i.e. *%s", s, funcName, t) + } + if err := validateType(t); err != nil { + return fmt.Errorf("%s in the function [%s] cannot contain %s", s, funcName, err) + } + + t = removePtr(t) + tv := reflect.New(t) + if t.Kind() != reflect.Struct { + tv = reflect.Indirect(tv) + } + + switch t.Kind() { + case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct: + RegisterType(tv.Interface()) + default: + } + + return nil +} + +func removePtr(t reflect.Type) reflect.Type { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t +} + +var validatedTypes []*validatedType + +type validatedType struct { + t reflect.Type + err *error +} + +func validateType(t reflect.Type) (err error) { + t = removePtr(t) + for _, vd := range validatedTypes { + if vd.t == t { + return *vd.err + } + } + validatedTypes = append(validatedTypes, &validatedType{ + t: t, + err: &err, + }) + + switch t.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer: + err = fmt.Errorf("%s. Found [%s]", t.Kind(), t) + return + case reflect.Array, reflect.Slice: + if err = validateType(t.Elem()); err != nil { + err = fmt.Errorf("%s in the %s [%s]", err, t.Kind(), t) + return + } + case reflect.Map: + if err = validateType(t.Elem()); err != nil { + err = fmt.Errorf("%s in the value of map [%s]", err, t) + return + } + if err = validateType(t.Key()); err != nil { + err = fmt.Errorf("%s in the key of map [%s]", err, t) + return + } + case reflect.Struct: + n := 0 + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if f.PkgPath == "" { + if err = validateType(f.Type); err != nil { + err = fmt.Errorf("%s in the field [%s] of struct [%s]", err, f.Name, t) + return + } + n++ + } + } + if n == 0 { + err = fmt.Errorf("struct without exported fields [%s]", t) + return + } + } + + return err +} + +type dispatcherRequest struct { + Request interface{} + Name string +} + +type dispatcherResponse struct { + Response interface{} + Error string +} + +func init() { + RegisterType(&dispatcherRequest{}) + RegisterType(&dispatcherResponse{}) +} + +// NewHandlerFunc returns HandlerFunc serving all the functions and/or services +// registered via AddFunc() and AddService(). +// +// The returned HandlerFunc must be assigned to Server.Handler or +// passed to New*Server(). +func (d *Dispatcher) NewHandlerFunc() HandlerFunc { + if len(d.serviceMap) == 0 { + logPanic("gorpc.Dispatcher: register at least one service before calling HandlerFunc()") + } + + serviceMap := copyServiceMap(d.serviceMap) + + return func(clientAddr string, request interface{}) interface{} { + req, ok := request.(*dispatcherRequest) + if !ok { + logPanic("gorpc.Dispatcher: unsupported request type received from the client: %T", request) + } + return dispatchRequest(serviceMap, clientAddr, req) + } +} + +func copyServiceMap(sm map[string]*serviceData) map[string]*serviceData { + serviceMap := make(map[string]*serviceData) + for sk, sv := range sm { + funcMap := make(map[string]*funcData) + for fk, fv := range sv.funcMap { + funcMap[fk] = fv + } + serviceMap[sk] = &serviceData{ + sv: sv.sv, + funcMap: funcMap, + } + } + return serviceMap +} + +func dispatchRequest(serviceMap map[string]*serviceData, clientAddr string, req *dispatcherRequest) *dispatcherResponse { + callName := strings.SplitN(req.Name, ".", 2) + if len(callName) != 2 { + return &dispatcherResponse{ + Error: fmt.Sprintf("gorpc.Dispatcher: cannot split call name into service name and method name [%s]", req.Name), + } + } + + serviceName, funcName := callName[0], callName[1] + s, ok := serviceMap[serviceName] + if !ok { + return &dispatcherResponse{ + Error: fmt.Sprintf("gorpc.Dispatcher: unknown service name [%s]", serviceName), + } + } + + fd, ok := s.funcMap[funcName] + if !ok { + return &dispatcherResponse{ + Error: fmt.Sprintf("gorpc.Dispatcher: unknown method [%s]", req.Name), + } + } + + var inArgs []reflect.Value + if fd.inNum > 0 { + inArgs = make([]reflect.Value, fd.inNum) + + dt := 0 + if serviceName != "" { + dt = 1 + inArgs[0] = s.sv + } + if fd.inNum == 2+dt { + inArgs[dt] = reflect.ValueOf(clientAddr) + } + if fd.inNum > dt { + reqv := reflect.ValueOf(req.Request) + reqt := reflect.TypeOf(req.Request) + if reqt != fd.reqt { + return &dispatcherResponse{ + Error: fmt.Sprintf("gorpc.Dispatcher: unexpected request type for method [%s]: %s. Expected %s", req.Name, reqt, fd.reqt), + } + } + inArgs[len(inArgs)-1] = reqv + } + } + + outArgs := fd.fv.Call(inArgs) + + resp := &dispatcherResponse{} + + if len(outArgs) == 1 { + if isErrorType(outArgs[0].Type()) { + resp.Error = getErrorString(outArgs[0]) + } else { + resp.Response = outArgs[0].Interface() + } + } else if len(outArgs) == 2 { + resp.Error = getErrorString(outArgs[1]) + if resp.Error == "" { + resp.Response = outArgs[0].Interface() + } + } + + return resp +} + +var errt = reflect.TypeOf((*error)(nil)).Elem() + +func isErrorType(t reflect.Type) bool { + return t.Implements(errt) +} + +func getErrorString(v reflect.Value) string { + if v.IsNil() { + return "" + } + return v.Interface().(error).Error() +} + +// DispatcherClient is a Client wrapper suitable for calling registered +// functions and/or for calling methods of the registered services. +type DispatcherClient struct { + c *Client + serviceName string +} + +// NewFuncClient returns a client suitable for calling functions registered +// via AddFunc(). +func (d *Dispatcher) NewFuncClient(c *Client) *DispatcherClient { + if len(d.serviceMap) == 0 || d.serviceMap[""] == nil { + logPanic("gorpc.Dispatcher: register at least one function with AddFunc() before calling NewFuncClient()") + } + + return &DispatcherClient{ + c: c, + } +} + +// NewServiceClient returns a client suitable for calling methods +// of the service with name serviceName registered via AddService(). +// +// It is safe creating multiple service clients over a single underlying client. +func (d *Dispatcher) NewServiceClient(serviceName string, c *Client) *DispatcherClient { + if len(d.serviceMap) == 0 || d.serviceMap[serviceName] == nil { + logPanic("gorpc.Dispatcher: service [%s] must be registered with AddService() before calling NewServiceClient()", serviceName) + } + + return &DispatcherClient{ + c: c, + serviceName: serviceName, + } +} + +// Call calls the given function. +func (dc *DispatcherClient) Call(funcName string, request interface{}) (response interface{}, err error) { + return dc.CallTimeout(funcName, request, dc.c.RequestTimeout) +} + +// CallTimeout calls the given function and waits for response during the given timeout. +func (dc *DispatcherClient) CallTimeout(funcName string, request interface{}, timeout time.Duration) (response interface{}, err error) { + req := dc.getRequest(funcName, request) + resp, err := dc.c.CallTimeout(req, timeout) + return getResponse(resp, err) +} + +// Send sends the given request to the given function and doesn't +// wait for response. +func (dc *DispatcherClient) Send(funcName string, request interface{}) error { + req := dc.getRequest(funcName, request) + return dc.c.Send(req) +} + +// CallAsync calls the given function asynchronously. +func (dc *DispatcherClient) CallAsync(funcName string, request interface{}) (*AsyncResult, error) { + req := dc.getRequest(funcName, request) + + innerAr, err := dc.c.CallAsync(req) + if err != nil { + return nil, err + } + + ch := make(chan struct{}) + ar := &AsyncResult{ + Done: ch, + } + + go func() { + <-innerAr.Done + ar.Response, ar.Error = getResponse(innerAr.Response, innerAr.Error) + close(ch) + }() + + return ar, nil +} + +// DispatcherBatch allows grouping and executing multiple RPCs in a single batch. +// +// DispatcherBatch may be created via DispatcherClient.NewBatch(). +type DispatcherBatch struct { + lock sync.Mutex + c *DispatcherClient + b *Batch + ops []*BatchResult +} + +// NewBatch creates new RPC batch for the given DispatcherClient. +// +// It is safe creating multiple concurrent batches from a single client. +func (dc *DispatcherClient) NewBatch() *DispatcherBatch { + return &DispatcherBatch{ + c: dc, + b: dc.c.NewBatch(), + } +} + +// Add ads new request to the RPC batch. +// +// The order of batched RPCs execution on the server is unspecified. +// +// All the requests added to the batch are sent to the server at once +// when DispatcherBatch.Call*() is called. +// +// It is safe adding multiple requests to the same batch from concurrently +// running goroutines. +func (b *DispatcherBatch) Add(funcName string, request interface{}) *BatchResult { + return b.add(funcName, request, false) +} + +// AddSkipResponse adds new request to the RPC batch and doesn't care +// about the response. +// +// The order of batched RPCs execution on the server is unspecified. +// +// All the requests added to the batch are sent to the server at once +// when DispatcherBatch.Call*() is called. +// +// It is safe adding multiple requests to the same batch from concurrently +// running goroutines. +func (b *DispatcherBatch) AddSkipResponse(funcName string, request interface{}) { + b.add(funcName, request, true) +} + +func (b *DispatcherBatch) add(funcName string, request interface{}, skipResponse bool) *BatchResult { + req := b.c.getRequest(funcName, request) + + var br *BatchResult + b.lock.Lock() + if !skipResponse { + br = &BatchResult{ + ctx: b.b.Add(req), + done: make(chan struct{}), + } + br.Done = br.done + b.ops = append(b.ops, br) + } else { + b.b.AddSkipResponse(req) + } + b.lock.Unlock() + + return br +} + +// Call calls all the RPCs added via DispatcherBatch.Add(). +// +// The order of batched RPCs execution on the server is unspecified. +// +// The caller may read all BatchResult contents returned +// from DispatcherBatch.Add() after the Call returns. +// +// It is guaranteed that all <-BatchResult.Done channels are unblocked after +// the Call returns. +func (b *DispatcherBatch) Call() error { + return b.CallTimeout(b.c.c.RequestTimeout) +} + +// CallTimeout calls all the RPCs added via DispatcherBatch.Add() and waits +// for all the RPC responses during the given timeout. +// +// The caller may read all BatchResult contents returned +// from DispatcherBatch.Add() after the CallTimeout returns. +// +// It is guaranteed that all <-BatchResult.Done channels are unblocked after +// the CallTimeout returns. +func (b *DispatcherBatch) CallTimeout(timeout time.Duration) error { + b.lock.Lock() + bb := b.b + b.b = b.c.c.NewBatch() + ops := b.ops + b.ops = nil + b.lock.Unlock() + + if err := bb.CallTimeout(timeout); err != nil { + return err + } + + for _, op := range ops { + br := op.ctx.(*BatchResult) + op.Response, op.Error = getResponse(br.Response, br.Error) + close(op.done) + } + + return nil +} + +func (dc *DispatcherClient) getRequest(funcName string, request interface{}) *dispatcherRequest { + return &dispatcherRequest{ + Name: dc.serviceName + "." + funcName, + Request: request, + } +} + +func getResponse(respv interface{}, err error) (interface{}, error) { + if err != nil { + return nil, err + } + resp, ok := respv.(*dispatcherResponse) + if !ok { + return nil, &ClientError{ + Server: true, + err: fmt.Errorf("gorpc.DispatcherClient: unexpected response type: %T. Expected *dispatcherResponse", respv), + } + } + if resp.Error != "" { + return nil, &ClientError{ + Server: true, + err: errors.New(resp.Error), + } + } + return resp.Response, nil +} diff --git a/vendor/github.com/TykTechnologies/gorpc/doc.go b/vendor/github.com/TykTechnologies/gorpc/doc.go new file mode 100644 index 000000000000..9acb63d907e9 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/doc.go @@ -0,0 +1,15 @@ +/* +Package gorpc provides simple RPC API for highload projects. + +Gorpc has the following features: + + * Easy-to-use API. + * Optimized for high load (>10K qps). + * Uses as low network bandwidth as possible. + * Minimizes the number of TCP connections in TIME_WAIT and WAIT_CLOSE states. + * Minimizes the number of send() and recv() syscalls. + * Provides ability to use arbitrary underlying transport. + By default TCP is used, but TLS and UNIX sockets are already available. + +*/ +package gorpc diff --git a/vendor/github.com/TykTechnologies/gorpc/encoding.go b/vendor/github.com/TykTechnologies/gorpc/encoding.go new file mode 100644 index 000000000000..21a1a0a10725 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/encoding.go @@ -0,0 +1,118 @@ +package gorpc + +import ( + "bufio" + "compress/flate" + "encoding/gob" + "io" +) + +// RegisterType registers the given type to send via rpc. +// +// The client must register all the response types the server may send. +// The server must register all the request types the client may send. +// +// There is no need in registering base Go types such as int, string, bool, +// float64, etc. or arrays, slices and maps containing base Go types. +// +// There is no need in registering argument and return value types +// for functions and methods registered via Dispatcher. +func RegisterType(x interface{}) { + gob.Register(x) +} + +type wireRequest struct { + ID uint64 + Request interface{} +} + +type wireResponse struct { + ID uint64 + Response interface{} + Error string +} + +type messageEncoder struct { + e *gob.Encoder + bw *bufio.Writer + zw *flate.Writer + ww *bufio.Writer +} + +func (e *messageEncoder) Close() error { + if e.zw != nil { + return e.zw.Close() + } + return nil +} + +func (e *messageEncoder) Flush() error { + if e.zw != nil { + if err := e.ww.Flush(); err != nil { + return err + } + if err := e.zw.Flush(); err != nil { + return err + } + } + if err := e.bw.Flush(); err != nil { + return err + } + return nil +} + +func (e *messageEncoder) Encode(msg interface{}) error { + return e.e.Encode(msg) +} + +func newMessageEncoder(w io.Writer, bufferSize int, enableCompression bool, s *ConnStats) *messageEncoder { + w = newWriterCounter(w, s) + bw := bufio.NewWriterSize(w, bufferSize) + + ww := bw + var zw *flate.Writer + if enableCompression { + zw, _ = flate.NewWriter(bw, flate.BestSpeed) + ww = bufio.NewWriterSize(zw, bufferSize) + } + + return &messageEncoder{ + e: gob.NewEncoder(ww), + bw: bw, + zw: zw, + ww: ww, + } +} + +type messageDecoder struct { + d *gob.Decoder + zr io.ReadCloser +} + +func (d *messageDecoder) Close() error { + if d.zr != nil { + return d.zr.Close() + } + return nil +} + +func (d *messageDecoder) Decode(msg interface{}) error { + return d.d.Decode(msg) +} + +func newMessageDecoder(r io.Reader, bufferSize int, enableCompression bool, s *ConnStats) *messageDecoder { + r = newReaderCounter(r, s) + br := bufio.NewReaderSize(r, bufferSize) + + rr := br + var zr io.ReadCloser + if enableCompression { + zr = flate.NewReader(br) + rr = bufio.NewReaderSize(zr, bufferSize) + } + + return &messageDecoder{ + d: gob.NewDecoder(rr), + zr: zr, + } +} diff --git a/vendor/github.com/TykTechnologies/gorpc/server.go b/vendor/github.com/TykTechnologies/gorpc/server.go new file mode 100644 index 000000000000..f38994d1a71f --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/server.go @@ -0,0 +1,438 @@ +package gorpc + +import ( + "fmt" + "io" + "net" + "runtime" + "sync" + "time" +) + +// HandlerFunc is a server handler function. +// +// clientAddr contains client address returned by Listener.Accept(). +// Request and response types may be arbitrary. +// All the request types the client may send to the server must be registered +// with gorpc.RegisterType() before starting the server. +// There is no need in registering base Go types such as int, string, bool, +// float64, etc. or arrays, slices and maps containing base Go types. +// +// Hint: use Dispatcher for HandlerFunc construction. +type HandlerFunc func(clientAddr string, request interface{}) (response interface{}) + +// Server implements RPC server. +// +// Default server settings are optimized for high load, so don't override +// them without valid reason. +type Server struct { + // Address to listen to for incoming connections. + // + // The address format depends on the underlying transport provided + // by Server.Listener. The following transports are provided + // out of the box: + // * TCP - see NewTCPServer() and NewTCPClient(). + // * TLS (aka SSL) - see NewTLSServer() and NewTLSClient(). + // * Unix sockets - see NewUnixServer() and NewUnixClient(). + // + // By default TCP transport is used. + Addr string + + // Handler function for incoming requests. + // + // Server calls this function for each incoming request. + // The function must process the request and return the corresponding response. + // + // Hint: use Dispatcher for HandlerFunc construction. + Handler HandlerFunc + + // The maximum number of concurrent rpc calls the server may perform. + // Default is DefaultConcurrency. + Concurrency int + + // The maximum delay between response flushes to clients. + // + // Negative values lead to immediate requests' sending to the client + // without their buffering. This minimizes rpc latency at the cost + // of higher CPU and network usage. + // + // Default is DefaultFlushDelay. + FlushDelay time.Duration + + // The maximum number of pending responses in the queue. + // Default is DefaultPendingMessages. + PendingResponses int + + // Size of send buffer per each underlying connection in bytes. + // Default is DefaultBufferSize. + SendBufferSize int + + // Size of recv buffer per each underlying connection in bytes. + // Default is DefaultBufferSize. + RecvBufferSize int + + // OnConnect is called whenever connection from client is accepted. + // The callback can be used for authentication/authorization/encryption + // and/or for custom transport wrapping. + // + // See also Listener, which can be used for sophisticated transport + // implementation. + OnConnect OnConnectFunc + + // The server obtains new client connections via Listener.Accept(). + // + // Override the listener if you want custom underlying transport + // and/or client authentication/authorization. + // Don't forget overriding Client.Dial() callback accordingly. + // + // See also OnConnect for authentication/authorization purposes. + // + // * NewTLSClient() and NewTLSServer() can be used for encrypted rpc. + // * NewUnixClient() and NewUnixServer() can be used for fast local + // inter-process rpc. + // + // By default it returns TCP connections accepted from Server.Addr. + Listener Listener + + // LogError is used for error logging. + // + // By default the function set via SetErrorLogger() is used. + LogError LoggerFunc + + // Connection statistics. + // + // The stats doesn't reset automatically. Feel free resetting it + // any time you wish. + Stats ConnStats + + serverStopChan chan struct{} + stopWg sync.WaitGroup +} + +// Start starts rpc server. +// +// All the request types the client may send to the server must be registered +// with gorpc.RegisterType() before starting the server. +// There is no need in registering base Go types such as int, string, bool, +// float64, etc. or arrays, slices and maps containing base Go types. +func (s *Server) Start() error { + if s.LogError == nil { + s.LogError = errorLogger + } + if s.Handler == nil { + panic("gorpc.Server: Server.Handler cannot be nil") + } + + if s.serverStopChan != nil { + panic("gorpc.Server: server is already running. Stop it before starting it again") + } + s.serverStopChan = make(chan struct{}) + + if s.Concurrency <= 0 { + s.Concurrency = DefaultConcurrency + } + if s.FlushDelay == 0 { + s.FlushDelay = DefaultFlushDelay + } + if s.PendingResponses <= 0 { + s.PendingResponses = DefaultPendingMessages + } + if s.SendBufferSize <= 0 { + s.SendBufferSize = DefaultBufferSize + } + if s.RecvBufferSize <= 0 { + s.RecvBufferSize = DefaultBufferSize + } + + if s.Listener == nil { + s.Listener = &defaultListener{} + } + if err := s.Listener.Init(s.Addr); err != nil { + err = fmt.Errorf("gorpc.Server: [%s]. Cannot listen to: [%s]", s.Addr, err) + s.LogError("%s", err) + return err + } + + workersCh := make(chan struct{}, s.Concurrency) + s.stopWg.Add(1) + go serverHandler(s, workersCh) + return nil +} + +// Stop stops rpc server. Stopped server can be started again. +func (s *Server) Stop() { + if s.serverStopChan == nil { + panic("gorpc.Server: server must be started before stopping it") + } + close(s.serverStopChan) + s.stopWg.Wait() + s.serverStopChan = nil +} + +// Serve starts rpc server and blocks until it is stopped. +func (s *Server) Serve() error { + if err := s.Start(); err != nil { + return err + } + s.stopWg.Wait() + return nil +} + +func serverHandler(s *Server, workersCh chan struct{}) { + defer s.stopWg.Done() + + var conn net.Conn + var err error + + for { + acceptChan := make(chan struct{}) + go func() { + if conn, err = s.Listener.Accept(); err != nil { + s.LogError("gorpc.Server: [%s]. Cannot accept new connection: [%s]", s.Addr, err) + time.Sleep(time.Second) + } + close(acceptChan) + }() + + select { + case <-s.serverStopChan: + s.Listener.Close() + return + case <-acceptChan: + s.Stats.incAcceptCalls() + } + + if err != nil { + s.Stats.incAcceptErrors() + continue + } + + s.stopWg.Add(1) + go serverHandleConnection(s, conn, workersCh) + } +} + +func serverHandleConnection(s *Server, conn net.Conn, workersCh chan struct{}) { + defer s.stopWg.Done() + var clientAddr string + + if s.OnConnect != nil { + newConn, clientAddr, err := s.OnConnect(conn) + if err != nil { + s.LogError("gorpc.Server: [%s]->[%s]. OnConnect error: [%s]", clientAddr, s.Addr, err) + conn.Close() + return + } + conn = newConn + } + + if clientAddr == "" { + clientAddr = conn.RemoteAddr().String() + } + + var enabledCompression bool + var err error + zChan := make(chan bool, 1) + go func() { + var buf [1]byte + if _, err = conn.Read(buf[:]); err != nil { + s.LogError("gorpc.Server: [%s]->[%s]. Error when reading handshake from client: [%s]", clientAddr, s.Addr, err) + } + zChan <- (buf[0] != 0) + }() + select { + case enabledCompression = <-zChan: + if err != nil { + conn.Close() + return + } + case <-s.serverStopChan: + conn.Close() + return + case <-time.After(10 * time.Second): + s.LogError("gorpc.Server: [%s]->[%s]. Cannot obtain handshake from client during 10s", clientAddr, s.Addr) + conn.Close() + return + } + + responsesChan := make(chan *serverMessage, s.PendingResponses) + stopChan := make(chan struct{}) + + readerDone := make(chan struct{}) + go serverReader(s, conn, clientAddr, responsesChan, stopChan, readerDone, enabledCompression, workersCh) + + writerDone := make(chan struct{}) + go serverWriter(s, conn, clientAddr, responsesChan, stopChan, writerDone, enabledCompression) + + select { + case <-readerDone: + close(stopChan) + conn.Close() + <-writerDone + case <-writerDone: + close(stopChan) + conn.Close() + <-readerDone + case <-s.serverStopChan: + close(stopChan) + conn.Close() + <-readerDone + <-writerDone + } +} + +type serverMessage struct { + ID uint64 + Request interface{} + Response interface{} + Error string + ClientAddr string +} + +var serverMessagePool = &sync.Pool{ + New: func() interface{} { + return &serverMessage{} + }, +} + +func serverReader(s *Server, r io.Reader, clientAddr string, responsesChan chan<- *serverMessage, + stopChan <-chan struct{}, done chan<- struct{}, enabledCompression bool, workersCh chan struct{}) { + + defer func() { + if r := recover(); r != nil { + s.LogError("gorpc.Server: [%s]->[%s]. Panic when reading data from client: %v", clientAddr, s.Addr, r) + } + close(done) + }() + + d := newMessageDecoder(r, s.RecvBufferSize, enabledCompression, &s.Stats) + defer d.Close() + + var wr wireRequest + for { + if err := d.Decode(&wr); err != nil { + s.LogError("gorpc.Server: [%s]->[%s]. Cannot decode request: [%s]", clientAddr, s.Addr, err) + return + } + + m := serverMessagePool.Get().(*serverMessage) + m.ID = wr.ID + m.Request = wr.Request + m.ClientAddr = clientAddr + + wr.ID = 0 + wr.Request = nil + + select { + case workersCh <- struct{}{}: + default: + select { + case workersCh <- struct{}{}: + case <-stopChan: + return + } + } + go serveRequest(s, responsesChan, stopChan, m, workersCh) + } +} + +func serveRequest(s *Server, responsesChan chan<- *serverMessage, stopChan <-chan struct{}, m *serverMessage, workersCh <-chan struct{}) { + request := m.Request + m.Request = nil + clientAddr := m.ClientAddr + m.ClientAddr = "" + skipResponse := (m.ID == 0) + + if skipResponse { + m.Response = nil + m.Error = "" + serverMessagePool.Put(m) + } + + t := time.Now() + response, err := callHandlerWithRecover(s.LogError, s.Handler, clientAddr, s.Addr, request) + s.Stats.incRPCTime(uint64(time.Since(t).Seconds() * 1000)) + + if !skipResponse { + m.Response = response + m.Error = err + + // Select hack for better performance. + // See https://github.com/valyala/gorpc/pull/1 for details. + select { + case responsesChan <- m: + default: + select { + case responsesChan <- m: + case <-stopChan: + } + } + } + + <-workersCh +} + +func callHandlerWithRecover(logErrorFunc LoggerFunc, handler HandlerFunc, clientAddr, serverAddr string, request interface{}) (response interface{}, errStr string) { + defer func() { + if x := recover(); x != nil { + stackTrace := make([]byte, 1<<20) + n := runtime.Stack(stackTrace, false) + errStr = fmt.Sprintf("Panic occured: %v\nStack trace: %s", x, stackTrace[:n]) + logErrorFunc("gorpc.Server: [%s]->[%s]. %s", clientAddr, serverAddr, errStr) + } + }() + response = handler(clientAddr, request) + return +} + +func serverWriter(s *Server, w io.Writer, clientAddr string, responsesChan <-chan *serverMessage, stopChan <-chan struct{}, done chan<- struct{}, enabledCompression bool) { + defer func() { close(done) }() + + e := newMessageEncoder(w, s.SendBufferSize, enabledCompression, &s.Stats) + defer e.Close() + + var flushChan <-chan time.Time + t := time.NewTimer(s.FlushDelay) + var wr wireResponse + for { + var m *serverMessage + + select { + case m = <-responsesChan: + default: + select { + case <-stopChan: + return + case m = <-responsesChan: + case <-flushChan: + if err := e.Flush(); err != nil { + s.LogError("gorpc.Server: [%s]->[%s]: Cannot flush responses to underlying stream: [%s]", clientAddr, s.Addr, err) + return + } + flushChan = nil + continue + } + } + + if flushChan == nil { + flushChan = getFlushChan(t, s.FlushDelay) + } + + wr.ID = m.ID + wr.Response = m.Response + wr.Error = m.Error + + m.Response = nil + m.Error = "" + serverMessagePool.Put(m) + + if err := e.Encode(wr); err != nil { + s.LogError("gorpc.Server: [%s]->[%s]. Cannot send response to wire: [%s]", clientAddr, s.Addr, err) + return + } + wr.Response = nil + wr.Error = "" + + s.Stats.incRPCCalls() + } +} diff --git a/vendor/github.com/TykTechnologies/gorpc/transport.go b/vendor/github.com/TykTechnologies/gorpc/transport.go new file mode 100644 index 000000000000..108cd7163771 --- /dev/null +++ b/vendor/github.com/TykTechnologies/gorpc/transport.go @@ -0,0 +1,228 @@ +package gorpc + +import ( + "crypto/tls" + "net" + "time" +) + +var ( + dialer = &net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + } +) + +// DialFunc is a function intended for setting to Client.Dial. +// +// It is expected that the returned conn immediately +// sends all the data passed via Write() to the server. +// Otherwise gorpc may hang. +// The conn implementation must call Flush() on underlying buffered +// streams before returning from Write(). +type DialFunc func(addr string) (conn net.Conn, err error) + +// Listener is an interface for custom listeners intended for the Server. +type Listener interface { + // Init is called on server start. + // + // addr contains the address set at Server.Addr. + Init(addr string) error + + // Accept must return incoming connections from clients. + // clientAddr must contain client's address in user-readable view. + // + // It is expected that the returned conn immediately + // sends all the data passed via Write() to the client. + // Otherwise gorpc may hang. + // The conn implementation must call Flush() on underlying buffered + // streams before returning from Write(). + Accept() (conn net.Conn, err error) + + // Close closes the listener. + // All pending calls to Accept() must immediately return errors after + // Close is called. + // All subsequent calls to Accept() must immediately return error. + Close() error +} + +func defaultDial(addr string) (conn net.Conn, err error) { + return dialer.Dial("tcp", addr) +} + +type defaultListener struct { + L net.Listener +} + +func (ln *defaultListener) Init(addr string) (err error) { + ln.L, err = net.Listen("tcp", addr) + return +} + +func (ln *defaultListener) Accept() (conn net.Conn, err error) { + c, err := ln.L.Accept() + if err != nil { + return nil, err + } + if err = setupKeepalive(c); err != nil { + c.Close() + return nil, err + } + return c, nil +} + +func (ln *defaultListener) Close() error { + return ln.L.Close() +} + +func setupKeepalive(conn net.Conn) error { + tcpConn := conn.(*net.TCPConn) + if err := tcpConn.SetKeepAlive(true); err != nil { + return err + } + if err := tcpConn.SetKeepAlivePeriod(30 * time.Second); err != nil { + return err + } + return nil +} + +type netListener struct { + F func(addr string) (net.Listener, error) + L net.Listener +} + +func (ln *netListener) Init(addr string) (err error) { + ln.L, err = ln.F(addr) + return +} + +func (ln *netListener) Accept() (conn net.Conn, err error) { + c, err := ln.L.Accept() + if err != nil { + return nil, err + } + return c, nil +} + +func (ln *netListener) Close() error { + return ln.L.Close() +} + +func unixDial(addr string) (conn net.Conn, err error) { + c, err := net.Dial("unix", addr) + if err != nil { + return nil, err + } + return c, err +} + +// NewTCPClient creates a client connecting over TCP to the server +// listening to the given addr. +// +// The returned client must be started after optional settings' adjustment. +// +// The corresponding server must be created with NewTCPServer(). +func NewTCPClient(addr string) *Client { + return &Client{ + Addr: addr, + Dial: defaultDial, + } +} + +// NewTCPServer creates a server listening for TCP connections +// on the given addr and processing incoming requests +// with the given HandlerFunc. +// +// The returned server must be started after optional settings' adjustment. +// +// The corresponding client must be created with NewTCPClient(). +func NewTCPServer(addr string, handler HandlerFunc) *Server { + return &Server{ + Addr: addr, + Handler: handler, + Listener: &defaultListener{}, + } +} + +// NewUnixClient creates a client connecting over unix socket +// to the server listening to the given addr. +// +// The returned client must be started after optional settings' adjustment. +// +// The corresponding server must be created with NewUnixServer(). +func NewUnixClient(addr string) *Client { + return &Client{ + Addr: addr, + Dial: unixDial, + + // There is little sense in compressing rpc data passed + // over local unix sockets. + DisableCompression: true, + + // Sacrifice the number of Write() calls to the smallest + // possible latency, since it has higher priority in local IPC. + FlushDelay: -1, + } +} + +// NewUnixServer creates a server listening for unix connections +// on the given addr and processing incoming requests +// with the given HandlerFunc. +// +// The returned server must be started after optional settings' adjustment. +// +// The corresponding client must be created with NewUnixClient(). +func NewUnixServer(addr string, handler HandlerFunc) *Server { + return &Server{ + Addr: addr, + Handler: handler, + Listener: &netListener{ + F: func(addr string) (net.Listener, error) { + return net.Listen("unix", addr) + }, + }, + + // Sacrifice the number of Write() calls to the smallest + // possible latency, since it has higher priority in local IPC. + FlushDelay: -1, + } +} + +// NewTLSClient creates a client connecting over TLS (aka SSL) to the server +// listening to the given addr using the given TLS config. +// +// The returned client must be started after optional settings' adjustment. +// +// The corresponding server must be created with NewTLSServer(). +func NewTLSClient(addr string, cfg *tls.Config) *Client { + return &Client{ + Addr: addr, + Dial: func(addr string) (conn net.Conn, err error) { + c, err := tls.DialWithDialer(dialer, "tcp", addr, cfg) + if err != nil { + return nil, err + } + return c, err + }, + } +} + +// NewTLSServer creates a server listening for TLS (aka SSL) connections +// on the given addr and processing incoming requests +// with the given HandlerFunc. +// cfg must contain TLS settings for the server. +// +// The returned server must be started after optional settings' adjustment. +// +// The corresponding client must be created with NewTLSClient(). +func NewTLSServer(addr string, handler HandlerFunc, cfg *tls.Config) *Server { + return &Server{ + Addr: addr, + Handler: handler, + Listener: &netListener{ + F: func(addr string) (net.Listener, error) { + return tls.Listen("tcp", addr, cfg) + }, + }, + } +}