From e577f6048155b143408032451ca3f8429bced646 Mon Sep 17 00:00:00 2001 From: tidwall Date: Mon, 29 Oct 2018 05:00:54 -0700 Subject: [PATCH] Updated redigo imports --- Gopkg.lock | 21 +- Gopkg.toml | 8 +- internal/endpoint/disque.go | 2 +- internal/endpoint/redis.go | 2 +- tests/107/main.go | 2 +- tests/fence_test.go | 2 +- tests/keys_test.go | 2 +- tests/mock_test.go | 2 +- .../garyburd/redigo/README.markdown | 50 -- .../github.com/garyburd/redigo/redis/pool.go | 416 ------------- .../github.com/garyburd/redigo/redis/redis.go | 41 -- .../redigo/.github/CONTRIBUTING.md | 2 +- .../redigo/.github/ISSUE_TEMPLATE.md | 0 .../{garyburd => gomodule}/redigo/.travis.yml | 16 +- .../{garyburd => gomodule}/redigo/LICENSE | 0 .../gomodule/redigo/README.markdown | 51 ++ .../gomodule/redigo/redis/commandinfo.go | 55 ++ .../redigo/redis}/commandinfo_test.go | 6 +- .../redigo/redis/conn.go | 161 +++-- .../redigo/redis/conn_test.go | 273 +++++++-- .../redigo/redis/doc.go | 8 +- .../redigo/redis/go16.go} | 6 +- .../redigo/redis/go17.go | 8 +- .../github.com/gomodule/redigo/redis/go18.go | 9 + .../gomodule/redigo/redis/list_test.go | 85 +++ .../redigo/redis/log.go | 31 +- .../github.com/gomodule/redigo/redis/pool.go | 560 ++++++++++++++++++ .../gomodule/redigo/redis/pool17.go | 35 ++ .../gomodule/redigo/redis/pool17_test.go | 74 +++ .../redigo/redis/pool_test.go | 128 ++-- .../redigo/redis/pubsub.go | 46 +- .../redigo/redis/pubsub_example_test.go | 165 ++++++ .../redigo/redis/pubsub_test.go | 90 +-- .../github.com/gomodule/redigo/redis/redis.go | 117 ++++ .../gomodule/redigo/redis/redis_test.go | 71 +++ .../redigo/redis/reply.go | 170 ++++-- .../redigo/redis/reply_test.go | 43 +- .../redigo/redis/scan.go | 28 +- .../redigo/redis/scan_test.go | 78 ++- .../redigo/redis/script.go | 5 + .../redigo/redis/script_test.go | 2 +- .../redigo/redis/test_test.go | 20 +- .../redigo/redis/zpop_example_test.go | 3 +- .../redigo/redisx}/commandinfo.go | 34 +- .../redigo/redisx/commandinfo_test.go | 11 + .../redigo/redisx/connmux.go | 5 +- .../redigo/redisx/connmux_test.go | 21 +- .../redigo/redisx/db_test.go} | 6 +- .../redigo/redisx/doc.go | 2 +- 49 files changed, 2067 insertions(+), 906 deletions(-) delete mode 100644 vendor/github.com/garyburd/redigo/README.markdown delete mode 100644 vendor/github.com/garyburd/redigo/redis/pool.go delete mode 100644 vendor/github.com/garyburd/redigo/redis/redis.go rename vendor/github.com/{garyburd => gomodule}/redigo/.github/CONTRIBUTING.md (69%) rename vendor/github.com/{garyburd => gomodule}/redigo/.github/ISSUE_TEMPLATE.md (100%) rename vendor/github.com/{garyburd => gomodule}/redigo/.travis.yml (64%) rename vendor/github.com/{garyburd => gomodule}/redigo/LICENSE (100%) create mode 100644 vendor/github.com/gomodule/redigo/README.markdown create mode 100644 vendor/github.com/gomodule/redigo/redis/commandinfo.go rename vendor/github.com/{garyburd/redigo/internal => gomodule/redigo/redis}/commandinfo_test.go (87%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/conn.go (79%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/conn_test.go (61%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/doc.go (96%) rename vendor/github.com/{garyburd/redigo/redis/pre_go17.go => gomodule/redigo/redis/go16.go} (78%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/go17.go (79%) create mode 100644 vendor/github.com/gomodule/redigo/redis/go18.go create mode 100644 vendor/github.com/gomodule/redigo/redis/list_test.go rename vendor/github.com/{garyburd => gomodule}/redigo/redis/log.go (73%) create mode 100644 vendor/github.com/gomodule/redigo/redis/pool.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pool17.go create mode 100644 vendor/github.com/gomodule/redigo/redis/pool17_test.go rename vendor/github.com/{garyburd => gomodule}/redigo/redis/pool_test.go (84%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/pubsub.go (79%) create mode 100644 vendor/github.com/gomodule/redigo/redis/pubsub_example_test.go rename vendor/github.com/{garyburd => gomodule}/redigo/redis/pubsub_test.go (53%) create mode 100644 vendor/github.com/gomodule/redigo/redis/redis.go create mode 100644 vendor/github.com/gomodule/redigo/redis/redis_test.go rename vendor/github.com/{garyburd => gomodule}/redigo/redis/reply.go (76%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/reply_test.go (78%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/scan.go (95%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/scan_test.go (85%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/script.go (97%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/script_test.go (98%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/test_test.go (83%) rename vendor/github.com/{garyburd => gomodule}/redigo/redis/zpop_example_test.go (98%) rename vendor/github.com/{garyburd/redigo/internal => gomodule/redigo/redisx}/commandinfo.go (58%) create mode 100644 vendor/github.com/gomodule/redigo/redisx/commandinfo_test.go rename vendor/github.com/{garyburd => gomodule}/redigo/redisx/connmux.go (96%) rename vendor/github.com/{garyburd => gomodule}/redigo/redisx/connmux_test.go (91%) rename vendor/github.com/{garyburd/redigo/internal/redistest/testdb.go => gomodule/redigo/redisx/db_test.go} (94%) rename vendor/github.com/{garyburd => gomodule}/redigo/redisx/doc.go (92%) diff --git a/Gopkg.lock b/Gopkg.lock index c93a5a2f..47b1fadb 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -85,17 +85,6 @@ revision = "aff15770515e3c57fc6109da73d42b0d46f7f483" version = "v1.1.0" -[[projects]] - digest = "1:c955959498f5eb42652958ac60bcd9a7fde78b8d6886f2e5b88513114c902a9c" - name = "github.com/garyburd/redigo" - packages = [ - "internal", - "redis", - ] - pruneopts = "" - revision = "433969511232c397de61b1442f9fd49ec06ae9ba" - version = "v1.1.0" - [[projects]] digest = "1:e26d0f8ccaf4087b93306d5e20e4816258116868ad6c6da0f2b4926c72fb92fa" name = "github.com/go-ini/ini" @@ -126,6 +115,14 @@ pruneopts = "" revision = "553a641470496b2327abcac10b36396bd98e45c9" +[[projects]] + branch = "master" + digest = "1:af269c8136b9422554bd4f1c0a024a7ccde3ef18482131ce10c336af80228e88" + name = "github.com/gomodule/redigo" + packages = ["redis"] + pruneopts = "" + revision = "e8fc0692a7e26a05b06517348ed466349062eb47" + [[projects]] digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e" name = "github.com/jmespath/go-jmespath" @@ -459,8 +456,8 @@ "github.com/aws/aws-sdk-go/aws/session", "github.com/aws/aws-sdk-go/service/sqs", "github.com/eclipse/paho.mqtt.golang", - "github.com/garyburd/redigo/redis", "github.com/golang/protobuf/proto", + "github.com/gomodule/redigo/redis", "github.com/mmcloughlin/geohash", "github.com/nats-io/go-nats", "github.com/peterh/liner", diff --git a/Gopkg.toml b/Gopkg.toml index 9f84f88c..9435396f 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -39,10 +39,6 @@ required = [ name = "github.com/eclipse/paho.mqtt.golang" version = "1.1.0" -[[constraint]] - name = "github.com/garyburd/redigo" - version = "1.1.0" - [[constraint]] branch = "master" name = "github.com/golang/protobuf" @@ -51,6 +47,10 @@ required = [ branch = "master" name = "github.com/peterh/liner" +[[constraint]] + branch = "master" + name = "github.com/gomodule/redigo" + [[constraint]] branch = "master" name = "github.com/streadway/amqp" diff --git a/internal/endpoint/disque.go b/internal/endpoint/disque.go index 94b841f2..bb4a1280 100644 --- a/internal/endpoint/disque.go +++ b/internal/endpoint/disque.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" "github.com/tidwall/tile38/internal/log" ) diff --git a/internal/endpoint/redis.go b/internal/endpoint/redis.go index fb4f83d7..ec9cec00 100644 --- a/internal/endpoint/redis.go +++ b/internal/endpoint/redis.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) const ( diff --git a/tests/107/main.go b/tests/107/main.go index 71e20ab3..c4724d51 100644 --- a/tests/107/main.go +++ b/tests/107/main.go @@ -18,7 +18,7 @@ import ( "sync/atomic" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" "github.com/tidwall/gjson" "github.com/tidwall/tile38/controller" ) diff --git a/tests/fence_test.go b/tests/fence_test.go index 4df45500..fb2d9b89 100644 --- a/tests/fence_test.go +++ b/tests/fence_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" "github.com/tidwall/gjson" ) diff --git a/tests/keys_test.go b/tests/keys_test.go index f5e5f1c2..a02ed170 100644 --- a/tests/keys_test.go +++ b/tests/keys_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" "github.com/tidwall/gjson" ) diff --git a/tests/mock_test.go b/tests/mock_test.go index 2deb6c8b..53929d78 100644 --- a/tests/mock_test.go +++ b/tests/mock_test.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" "github.com/tidwall/tile38/core" tlog "github.com/tidwall/tile38/internal/log" "github.com/tidwall/tile38/internal/server" diff --git a/vendor/github.com/garyburd/redigo/README.markdown b/vendor/github.com/garyburd/redigo/README.markdown deleted file mode 100644 index fb0d35c9..00000000 --- a/vendor/github.com/garyburd/redigo/README.markdown +++ /dev/null @@ -1,50 +0,0 @@ -Redigo -====== - -[![Build Status](https://travis-ci.org/garyburd/redigo.svg?branch=master)](https://travis-ci.org/garyburd/redigo) -[![GoDoc](https://godoc.org/github.com/garyburd/redigo/redis?status.svg)](https://godoc.org/github.com/garyburd/redigo/redis) - -Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database. - -Features -------- - -* A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands. -* [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions. -* [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe). -* [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool). -* [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA. -* [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies. - -Documentation -------------- - -- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis) -- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ) - -Installation ------------- - -Install Redigo using the "go get" command: - - go get github.com/garyburd/redigo/redis - -The Go distribution is Redigo's only dependency. - -Related Projects ----------------- - -- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo. -- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation. -- [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo -- [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo - -Contributing ------------- - -See [CONTRIBUTING.md](https://github.com/garyburd/redigo/blob/master/.github/CONTRIBUTING.md). - -License -------- - -Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). diff --git a/vendor/github.com/garyburd/redigo/redis/pool.go b/vendor/github.com/garyburd/redigo/redis/pool.go deleted file mode 100644 index 283a41d5..00000000 --- a/vendor/github.com/garyburd/redigo/redis/pool.go +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "bytes" - "container/list" - "crypto/rand" - "crypto/sha1" - "errors" - "io" - "strconv" - "sync" - "time" - - "github.com/garyburd/redigo/internal" -) - -var nowFunc = time.Now // for testing - -// ErrPoolExhausted is returned from a pool connection method (Do, Send, -// Receive, Flush, Err) when the maximum number of database connections in the -// pool has been reached. -var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") - -var ( - errPoolClosed = errors.New("redigo: connection pool closed") - errConnClosed = errors.New("redigo: connection closed") -) - -// Pool maintains a pool of connections. The application calls the Get method -// to get a connection from the pool and the connection's Close method to -// return the connection's resources to the pool. -// -// The following example shows how to use a pool in a web application. The -// application creates a pool at application startup and makes it available to -// request handlers using a package level variable. The pool configuration used -// here is an example, not a recommendation. -// -// func newPool(addr string) *redis.Pool { -// return &redis.Pool{ -// MaxIdle: 3, -// IdleTimeout: 240 * time.Second, -// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, -// } -// } -// -// var ( -// pool *redis.Pool -// redisServer = flag.String("redisServer", ":6379", "") -// ) -// -// func main() { -// flag.Parse() -// pool = newPool(*redisServer) -// ... -// } -// -// A request handler gets a connection from the pool and closes the connection -// when the handler is done: -// -// func serveHome(w http.ResponseWriter, r *http.Request) { -// conn := pool.Get() -// defer conn.Close() -// ... -// } -// -// Use the Dial function to authenticate connections with the AUTH command or -// select a database with the SELECT command: -// -// pool := &redis.Pool{ -// // Other pool configuration not shown in this example. -// Dial: func () (redis.Conn, error) { -// c, err := redis.Dial("tcp", server) -// if err != nil { -// return nil, err -// } -// if _, err := c.Do("AUTH", password); err != nil { -// c.Close() -// return nil, err -// } -// if _, err := c.Do("SELECT", db); err != nil { -// c.Close() -// return nil, err -// } -// return c, nil -// } -// } -// -// Use the TestOnBorrow function to check the health of an idle connection -// before the connection is returned to the application. This example PINGs -// connections that have been idle more than a minute: -// -// pool := &redis.Pool{ -// // Other pool configuration not shown in this example. -// TestOnBorrow: func(c redis.Conn, t time.Time) error { -// if time.Since(t) < time.Minute { -// return nil -// } -// _, err := c.Do("PING") -// return err -// }, -// } -// -type Pool struct { - - // Dial is an application supplied function for creating and configuring a - // connection. - // - // The connection returned from Dial must not be in a special state - // (subscribed to pubsub channel, transaction started, ...). - Dial func() (Conn, error) - - // TestOnBorrow is an optional application supplied function for checking - // the health of an idle connection before the connection is used again by - // the application. Argument t is the time that the connection was returned - // to the pool. If the function returns an error, then the connection is - // closed. - TestOnBorrow func(c Conn, t time.Time) error - - // Maximum number of idle connections in the pool. - MaxIdle int - - // Maximum number of connections allocated by the pool at a given time. - // When zero, there is no limit on the number of connections in the pool. - MaxActive int - - // Close connections after remaining idle for this duration. If the value - // is zero, then idle connections are not closed. Applications should set - // the timeout to a value less than the server's timeout. - IdleTimeout time.Duration - - // If Wait is true and the pool is at the MaxActive limit, then Get() waits - // for a connection to be returned to the pool before returning. - Wait bool - - // mu protects fields defined below. - mu sync.Mutex - cond *sync.Cond - closed bool - active int - - // Stack of idleConn with most recently used at the front. - idle list.List -} - -type idleConn struct { - c Conn - t time.Time -} - -// NewPool creates a new pool. -// -// Deprecated: Initialize the Pool directory as shown in the example. -func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { - return &Pool{Dial: newFn, MaxIdle: maxIdle} -} - -// Get gets a connection. The application must close the returned connection. -// This method always returns a valid connection so that applications can defer -// error handling to the first use of the connection. If there is an error -// getting an underlying connection, then the connection Err, Do, Send, Flush -// and Receive methods return that error. -func (p *Pool) Get() Conn { - c, err := p.get() - if err != nil { - return errorConnection{err} - } - return &pooledConnection{p: p, c: c} -} - -// ActiveCount returns the number of active connections in the pool. -func (p *Pool) ActiveCount() int { - p.mu.Lock() - active := p.active - p.mu.Unlock() - return active -} - -// Close releases the resources used by the pool. -func (p *Pool) Close() error { - p.mu.Lock() - idle := p.idle - p.idle.Init() - p.closed = true - p.active -= idle.Len() - if p.cond != nil { - p.cond.Broadcast() - } - p.mu.Unlock() - for e := idle.Front(); e != nil; e = e.Next() { - e.Value.(idleConn).c.Close() - } - return nil -} - -// release decrements the active count and signals waiters. The caller must -// hold p.mu during the call. -func (p *Pool) release() { - p.active -= 1 - if p.cond != nil { - p.cond.Signal() - } -} - -// get prunes stale connections and returns a connection from the idle list or -// creates a new connection. -func (p *Pool) get() (Conn, error) { - p.mu.Lock() - - // Prune stale connections. - - if timeout := p.IdleTimeout; timeout > 0 { - for i, n := 0, p.idle.Len(); i < n; i++ { - e := p.idle.Back() - if e == nil { - break - } - ic := e.Value.(idleConn) - if ic.t.Add(timeout).After(nowFunc()) { - break - } - p.idle.Remove(e) - p.release() - p.mu.Unlock() - ic.c.Close() - p.mu.Lock() - } - } - - for { - - // Get idle connection. - - for i, n := 0, p.idle.Len(); i < n; i++ { - e := p.idle.Front() - if e == nil { - break - } - ic := e.Value.(idleConn) - p.idle.Remove(e) - test := p.TestOnBorrow - p.mu.Unlock() - if test == nil || test(ic.c, ic.t) == nil { - return ic.c, nil - } - ic.c.Close() - p.mu.Lock() - p.release() - } - - // Check for pool closed before dialing a new connection. - - if p.closed { - p.mu.Unlock() - return nil, errors.New("redigo: get on closed pool") - } - - // Dial new connection if under limit. - - if p.MaxActive == 0 || p.active < p.MaxActive { - dial := p.Dial - p.active += 1 - p.mu.Unlock() - c, err := dial() - if err != nil { - p.mu.Lock() - p.release() - p.mu.Unlock() - c = nil - } - return c, err - } - - if !p.Wait { - p.mu.Unlock() - return nil, ErrPoolExhausted - } - - if p.cond == nil { - p.cond = sync.NewCond(&p.mu) - } - p.cond.Wait() - } -} - -func (p *Pool) put(c Conn, forceClose bool) error { - err := c.Err() - p.mu.Lock() - if !p.closed && err == nil && !forceClose { - p.idle.PushFront(idleConn{t: nowFunc(), c: c}) - if p.idle.Len() > p.MaxIdle { - c = p.idle.Remove(p.idle.Back()).(idleConn).c - } else { - c = nil - } - } - - if c == nil { - if p.cond != nil { - p.cond.Signal() - } - p.mu.Unlock() - return nil - } - - p.release() - p.mu.Unlock() - return c.Close() -} - -type pooledConnection struct { - p *Pool - c Conn - state int -} - -var ( - sentinel []byte - sentinelOnce sync.Once -) - -func initSentinel() { - p := make([]byte, 64) - if _, err := rand.Read(p); err == nil { - sentinel = p - } else { - h := sha1.New() - io.WriteString(h, "Oops, rand failed. Use time instead.") - io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) - sentinel = h.Sum(nil) - } -} - -func (pc *pooledConnection) Close() error { - c := pc.c - if _, ok := c.(errorConnection); ok { - return nil - } - pc.c = errorConnection{errConnClosed} - - if pc.state&internal.MultiState != 0 { - c.Send("DISCARD") - pc.state &^= (internal.MultiState | internal.WatchState) - } else if pc.state&internal.WatchState != 0 { - c.Send("UNWATCH") - pc.state &^= internal.WatchState - } - if pc.state&internal.SubscribeState != 0 { - c.Send("UNSUBSCRIBE") - c.Send("PUNSUBSCRIBE") - // To detect the end of the message stream, ask the server to echo - // a sentinel value and read until we see that value. - sentinelOnce.Do(initSentinel) - c.Send("ECHO", sentinel) - c.Flush() - for { - p, err := c.Receive() - if err != nil { - break - } - if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { - pc.state &^= internal.SubscribeState - break - } - } - } - c.Do("") - pc.p.put(c, pc.state != 0) - return nil -} - -func (pc *pooledConnection) Err() error { - return pc.c.Err() -} - -func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { - ci := internal.LookupCommandInfo(commandName) - pc.state = (pc.state | ci.Set) &^ ci.Clear - return pc.c.Do(commandName, args...) -} - -func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { - ci := internal.LookupCommandInfo(commandName) - pc.state = (pc.state | ci.Set) &^ ci.Clear - return pc.c.Send(commandName, args...) -} - -func (pc *pooledConnection) Flush() error { - return pc.c.Flush() -} - -func (pc *pooledConnection) Receive() (reply interface{}, err error) { - return pc.c.Receive() -} - -type errorConnection struct{ err error } - -func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } -func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } -func (ec errorConnection) Err() error { return ec.err } -func (ec errorConnection) Close() error { return ec.err } -func (ec errorConnection) Flush() error { return ec.err } -func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } diff --git a/vendor/github.com/garyburd/redigo/redis/redis.go b/vendor/github.com/garyburd/redigo/redis/redis.go deleted file mode 100644 index b7298298..00000000 --- a/vendor/github.com/garyburd/redigo/redis/redis.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -// Error represents an error returned in a command reply. -type Error string - -func (err Error) Error() string { return string(err) } - -// Conn represents a connection to a Redis server. -type Conn interface { - // Close closes the connection. - Close() error - - // Err returns a non-nil value when the connection is not usable. - Err() error - - // Do sends a command to the server and returns the received reply. - Do(commandName string, args ...interface{}) (reply interface{}, err error) - - // Send writes the command to the client's output buffer. - Send(commandName string, args ...interface{}) error - - // Flush flushes the output buffer to the Redis server. - Flush() error - - // Receive receives a single reply from the Redis server - Receive() (reply interface{}, err error) -} diff --git a/vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md b/vendor/github.com/gomodule/redigo/.github/CONTRIBUTING.md similarity index 69% rename from vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md rename to vendor/github.com/gomodule/redigo/.github/CONTRIBUTING.md index 143443a8..6bfd13a8 100644 --- a/vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md +++ b/vendor/github.com/gomodule/redigo/.github/CONTRIBUTING.md @@ -1,5 +1,5 @@ Ask questions at [StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis). -[Open an issue](https://github.com/garyburd/redigo/issues/new) to discuss your +[Open an issue](https://github.com/gomodule/redigo/issues/new) to discuss your plans before doing any work on Redigo. diff --git a/vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md b/vendor/github.com/gomodule/redigo/.github/ISSUE_TEMPLATE.md similarity index 100% rename from vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md rename to vendor/github.com/gomodule/redigo/.github/ISSUE_TEMPLATE.md diff --git a/vendor/github.com/garyburd/redigo/.travis.yml b/vendor/github.com/gomodule/redigo/.travis.yml similarity index 64% rename from vendor/github.com/garyburd/redigo/.travis.yml rename to vendor/github.com/gomodule/redigo/.travis.yml index e70302ea..8fe5ceea 100644 --- a/vendor/github.com/garyburd/redigo/.travis.yml +++ b/vendor/github.com/gomodule/redigo/.travis.yml @@ -4,13 +4,19 @@ services: - redis-server go: - - 1.4 - - 1.5 - - 1.6 - - 1.7 - - 1.8 + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - 1.10.x + - 1.11.x - tip +matrix: + allow_failures: + - go: tip + script: - go get -t -v ./... - diff -u <(echo -n) <(gofmt -d .) diff --git a/vendor/github.com/garyburd/redigo/LICENSE b/vendor/github.com/gomodule/redigo/LICENSE similarity index 100% rename from vendor/github.com/garyburd/redigo/LICENSE rename to vendor/github.com/gomodule/redigo/LICENSE diff --git a/vendor/github.com/gomodule/redigo/README.markdown b/vendor/github.com/gomodule/redigo/README.markdown new file mode 100644 index 00000000..2a637ac3 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/README.markdown @@ -0,0 +1,51 @@ +Redigo +====== + +[![Build Status](https://travis-ci.org/gomodule/redigo.svg?branch=master)](https://travis-ci.org/gomodule/redigo) +[![GoDoc](https://godoc.org/github.com/gomodule/redigo/redis?status.svg)](https://godoc.org/github.com/gomodule/redigo/redis) + +Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database. + +Features +------- + +* A [Print-like](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands. +* [Pipelining](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Pipelining), including pipelined transactions. +* [Publish/Subscribe](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Publish_and_Subscribe). +* [Connection pooling](http://godoc.org/github.com/gomodule/redigo/redis#Pool). +* [Script helper type](http://godoc.org/github.com/gomodule/redigo/redis#Script) with optimistic use of EVALSHA. +* [Helper functions](http://godoc.org/github.com/gomodule/redigo/redis#hdr-Reply_Helpers) for working with command replies. + +Documentation +------------- + +- [API Reference](http://godoc.org/github.com/gomodule/redigo/redis) +- [FAQ](https://github.com/gomodule/redigo/wiki/FAQ) +- [Examples](https://godoc.org/github.com/gomodule/redigo/redis#pkg-examples) + +Installation +------------ + +Install Redigo using the "go get" command: + + go get github.com/gomodule/redigo/redis + +The Go distribution is Redigo's only dependency. + +Related Projects +---------------- + +- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo. +- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation. +- [FZambia/sentinel](https://github.com/FZambia/sentinel) - Redis Sentinel support for Redigo +- [mna/redisc](https://github.com/mna/redisc) - Redis Cluster client built on top of Redigo + +Contributing +------------ + +See [CONTRIBUTING.md](https://github.com/gomodule/redigo/blob/master/.github/CONTRIBUTING.md). + +License +------- + +Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). diff --git a/vendor/github.com/gomodule/redigo/redis/commandinfo.go b/vendor/github.com/gomodule/redigo/redis/commandinfo.go new file mode 100644 index 00000000..b6df6a25 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/commandinfo.go @@ -0,0 +1,55 @@ +// Copyright 2014 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "strings" +) + +const ( + connectionWatchState = 1 << iota + connectionMultiState + connectionSubscribeState + connectionMonitorState +) + +type commandInfo struct { + // Set or Clear these states on connection. + Set, Clear int +} + +var commandInfos = map[string]commandInfo{ + "WATCH": {Set: connectionWatchState}, + "UNWATCH": {Clear: connectionWatchState}, + "MULTI": {Set: connectionMultiState}, + "EXEC": {Clear: connectionWatchState | connectionMultiState}, + "DISCARD": {Clear: connectionWatchState | connectionMultiState}, + "PSUBSCRIBE": {Set: connectionSubscribeState}, + "SUBSCRIBE": {Set: connectionSubscribeState}, + "MONITOR": {Set: connectionMonitorState}, +} + +func init() { + for n, ci := range commandInfos { + commandInfos[strings.ToLower(n)] = ci + } +} + +func lookupCommandInfo(commandName string) commandInfo { + if ci, ok := commandInfos[commandName]; ok { + return ci + } + return commandInfos[strings.ToUpper(commandName)] +} diff --git a/vendor/github.com/garyburd/redigo/internal/commandinfo_test.go b/vendor/github.com/gomodule/redigo/redis/commandinfo_test.go similarity index 87% rename from vendor/github.com/garyburd/redigo/internal/commandinfo_test.go rename to vendor/github.com/gomodule/redigo/redis/commandinfo_test.go index 118e94b6..799b9292 100644 --- a/vendor/github.com/garyburd/redigo/internal/commandinfo_test.go +++ b/vendor/github.com/gomodule/redigo/redis/commandinfo_test.go @@ -1,10 +1,10 @@ -package internal +package redis import "testing" func TestLookupCommandInfo(t *testing.T) { for _, n := range []string{"watch", "WATCH", "wAtch"} { - if LookupCommandInfo(n) == (CommandInfo{}) { + if lookupCommandInfo(n) == (commandInfo{}) { t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n) } } @@ -13,7 +13,7 @@ func TestLookupCommandInfo(t *testing.T) { func benchmarkLookupCommandInfo(b *testing.B, names ...string) { for i := 0; i < b.N; i++ { for _, c := range names { - LookupCommandInfo(c) + lookupCommandInfo(c) } } } diff --git a/vendor/github.com/garyburd/redigo/redis/conn.go b/vendor/github.com/gomodule/redigo/redis/conn.go similarity index 79% rename from vendor/github.com/garyburd/redigo/redis/conn.go rename to vendor/github.com/gomodule/redigo/redis/conn.go index 6ccace07..5aa0f32f 100644 --- a/vendor/github.com/garyburd/redigo/redis/conn.go +++ b/vendor/github.com/gomodule/redigo/redis/conn.go @@ -29,9 +29,12 @@ import ( "time" ) +var ( + _ ConnWithTimeout = (*conn)(nil) +) + // conn is the low-level implementation of Conn type conn struct { - // Shared mu sync.Mutex pending int @@ -73,10 +76,11 @@ type DialOption struct { type dialOptions struct { readTimeout time.Duration writeTimeout time.Duration + dialer *net.Dialer dial func(network, addr string) (net.Conn, error) db int password string - dialTLS bool + useTLS bool skipVerify bool tlsConfig *tls.Config } @@ -95,17 +99,27 @@ func DialWriteTimeout(d time.Duration) DialOption { }} } -// DialConnectTimeout specifies the timeout for connecting to the Redis server. +// DialConnectTimeout specifies the timeout for connecting to the Redis server when +// no DialNetDial option is specified. func DialConnectTimeout(d time.Duration) DialOption { return DialOption{func(do *dialOptions) { - dialer := net.Dialer{Timeout: d} - do.dial = dialer.Dial + do.dialer.Timeout = d + }} +} + +// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server +// when no DialNetDial option is specified. +// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then +// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. +func DialKeepAlive(d time.Duration) DialOption { + return DialOption{func(do *dialOptions) { + do.dialer.KeepAlive = d }} } // DialNetDial specifies a custom dial function for creating TCP -// connections. If this option is left out, then net.Dial is -// used. DialNetDial overrides DialConnectTimeout. +// connections, otherwise a net.Dialer customized via the other options is used. +// DialNetDial overrides DialConnectTimeout and DialKeepAlive. func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { return DialOption{func(do *dialOptions) { do.dial = dial @@ -135,31 +149,49 @@ func DialTLSConfig(c *tls.Config) DialOption { }} } -// DialTLSSkipVerify to disable server name verification when connecting -// over TLS. Has no effect when not dialing a TLS connection. +// DialTLSSkipVerify disables server name verification when connecting over +// TLS. Has no effect when not dialing a TLS connection. func DialTLSSkipVerify(skip bool) DialOption { return DialOption{func(do *dialOptions) { do.skipVerify = skip }} } +// DialUseTLS specifies whether TLS should be used when connecting to the +// server. This option is ignore by DialURL. +func DialUseTLS(useTLS bool) DialOption { + return DialOption{func(do *dialOptions) { + do.useTLS = useTLS + }} +} + // Dial connects to the Redis server at the given network and // address using the specified options. func Dial(network, address string, options ...DialOption) (Conn, error) { do := dialOptions{ - dial: net.Dial, + dialer: &net.Dialer{ + KeepAlive: time.Minute * 5, + }, } for _, option := range options { option.f(&do) } + if do.dial == nil { + do.dial = do.dialer.Dial + } netConn, err := do.dial(network, address) if err != nil { return nil, err } - if do.dialTLS { - tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify) + if do.useTLS { + var tlsConfig *tls.Config + if do.tlsConfig == nil { + tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} + } else { + tlsConfig = cloneTLSConfig(do.tlsConfig) + } if tlsConfig.ServerName == "" { host, _, err := net.SplitHostPort(address) if err != nil { @@ -202,10 +234,6 @@ func Dial(network, address string, options ...DialOption) (Conn, error) { return c, nil } -func dialTLS(do *dialOptions) { - do.dialTLS = true -} - var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) // DialURL connects to a Redis server at the given URL using the Redis @@ -257,9 +285,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) { return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) } - if u.Scheme == "rediss" { - options = append([]DialOption{{dialTLS}}, options...) - } + options = append(options, DialUseTLS(u.Scheme == "rediss")) return Dial("tcp", address, options...) } @@ -344,39 +370,55 @@ func (c *conn) writeFloat64(n float64) error { return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) } -func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { +func (c *conn) writeCommand(cmd string, args []interface{}) error { c.writeLen('*', 1+len(args)) - err = c.writeString(cmd) + if err := c.writeString(cmd); err != nil { + return err + } for _, arg := range args { - if err != nil { - break - } - switch arg := arg.(type) { - case string: - err = c.writeString(arg) - case []byte: - err = c.writeBytes(arg) - case int: - err = c.writeInt64(int64(arg)) - case int64: - err = c.writeInt64(arg) - case float64: - err = c.writeFloat64(arg) - case bool: - if arg { - err = c.writeString("1") - } else { - err = c.writeString("0") - } - case nil: - err = c.writeString("") - default: - var buf bytes.Buffer - fmt.Fprint(&buf, arg) - err = c.writeBytes(buf.Bytes()) + if err := c.writeArg(arg, true); err != nil { + return err } } - return err + return nil +} + +func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { + switch arg := arg.(type) { + case string: + return c.writeString(arg) + case []byte: + return c.writeBytes(arg) + case int: + return c.writeInt64(int64(arg)) + case int64: + return c.writeInt64(arg) + case float64: + return c.writeFloat64(arg) + case bool: + if arg { + return c.writeString("1") + } else { + return c.writeString("0") + } + case nil: + return c.writeString("") + case Argument: + if argumentTypeOK { + return c.writeArg(arg.RedisArg(), false) + } + // See comment in default clause below. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + default: + // This default clause is intended to handle builtin numeric types. + // The function should return an error for other types, but this is not + // done for compatibility with previous versions of the package. + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + return c.writeBytes(buf.Bytes()) + } } type protocolError string @@ -538,10 +580,17 @@ func (c *conn) Flush() error { return nil } -func (c *conn) Receive() (reply interface{}, err error) { - if c.readTimeout != 0 { - c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) +func (c *conn) Receive() (interface{}, error) { + return c.ReceiveWithTimeout(c.readTimeout) +} + +func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + var deadline time.Time + if timeout != 0 { + deadline = time.Now().Add(timeout) } + c.conn.SetReadDeadline(deadline) + if reply, err = c.readReply(); err != nil { return nil, c.fatal(err) } @@ -564,6 +613,10 @@ func (c *conn) Receive() (reply interface{}, err error) { } func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { + return c.DoWithTimeout(c.readTimeout, cmd, args...) +} + +func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { c.mu.Lock() pending := c.pending c.pending = 0 @@ -587,9 +640,11 @@ func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { return nil, c.fatal(err) } - if c.readTimeout != 0 { - c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) + var deadline time.Time + if readTimeout != 0 { + deadline = time.Now().Add(readTimeout) } + c.conn.SetReadDeadline(deadline) if cmd == "" { reply := make([]interface{}, pending) diff --git a/vendor/github.com/garyburd/redigo/redis/conn_test.go b/vendor/github.com/gomodule/redigo/redis/conn_test.go similarity index 61% rename from vendor/github.com/garyburd/redigo/redis/conn_test.go rename to vendor/github.com/gomodule/redigo/redis/conn_test.go index 2ead6332..63a4c23d 100644 --- a/vendor/github.com/garyburd/redigo/redis/conn_test.go +++ b/vendor/github.com/gomodule/redigo/redis/conn_test.go @@ -16,6 +16,9 @@ package redis_test import ( "bytes" + "crypto/tls" + "crypto/x509" + "fmt" "io" "math" "net" @@ -25,27 +28,66 @@ import ( "testing" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) type testConn struct { io.Reader io.Writer + readDeadline time.Time + writeDeadline time.Time } -func (*testConn) Close() error { return nil } -func (*testConn) LocalAddr() net.Addr { return nil } -func (*testConn) RemoteAddr() net.Addr { return nil } -func (*testConn) SetDeadline(t time.Time) error { return nil } -func (*testConn) SetReadDeadline(t time.Time) error { return nil } -func (*testConn) SetWriteDeadline(t time.Time) error { return nil } +func (*testConn) Close() error { return nil } +func (*testConn) LocalAddr() net.Addr { return nil } +func (*testConn) RemoteAddr() net.Addr { return nil } +func (c *testConn) SetDeadline(t time.Time) error { c.readDeadline = t; c.writeDeadline = t; return nil } +func (c *testConn) SetReadDeadline(t time.Time) error { c.readDeadline = t; return nil } +func (c *testConn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t; return nil } -func dialTestConn(r io.Reader, w io.Writer) redis.DialOption { - return redis.DialNetDial(func(net, addr string) (net.Conn, error) { - return &testConn{Reader: r, Writer: w}, nil +func dialTestConn(r string, w io.Writer) redis.DialOption { + return redis.DialNetDial(func(network, addr string) (net.Conn, error) { + return &testConn{Reader: strings.NewReader(r), Writer: w}, nil }) } +type tlsTestConn struct { + net.Conn + done chan struct{} +} + +func (c *tlsTestConn) Close() error { + c.Conn.Close() + <-c.done + return nil +} + +func dialTestConnTLS(r string, w io.Writer) redis.DialOption { + return redis.DialNetDial(func(network, addr string) (net.Conn, error) { + client, server := net.Pipe() + tlsServer := tls.Server(server, &serverTLSConfig) + go io.Copy(tlsServer, strings.NewReader(r)) + done := make(chan struct{}) + go func() { + io.Copy(w, tlsServer) + close(done) + }() + return &tlsTestConn{Conn: client, done: done}, nil + }) +} + +type durationArg struct { + time.Duration +} + +func (t durationArg) RedisArg() interface{} { + return t.Seconds() +} + +type recursiveArg int + +func (v recursiveArg) RedisArg() interface{} { return v } + var writeTests = []struct { args []interface{} expected string @@ -82,6 +124,14 @@ var writeTests = []struct { []interface{}{"SET", "key", nil}, "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", }, + { + []interface{}{"SET", "key", durationArg{time.Minute}}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$2\r\n60\r\n", + }, + { + []interface{}{"SET", "key", recursiveArg(123)}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n123\r\n", + }, { []interface{}{"ECHO", true, false}, "*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n", @@ -91,7 +141,7 @@ var writeTests = []struct { func TestWrite(t *testing.T) { for _, tt := range writeTests { var buf bytes.Buffer - c, _ := redis.Dial("", "", dialTestConn(nil, &buf)) + c, _ := redis.Dial("", "", dialTestConn("", &buf)) err := c.Send(tt.args[0].(string), tt.args[1:]...) if err != nil { t.Errorf("Send(%v) returned error %v", tt.args, err) @@ -190,7 +240,7 @@ var readTests = []struct { func TestRead(t *testing.T) { for _, tt := range readTests { - c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil)) + c, _ := redis.Dial("", "", dialTestConn(tt.reply, nil)) actual, err := c.Receive() if tt.expected == errorSentinel { if err == nil { @@ -447,7 +497,7 @@ var dialErrors = []struct { "localhost", "invalid redis URL scheme", }, - // The error message for invalid hosts is diffferent in different + // The error message for invalid hosts is different in different // versions of Go, so just check that there is an error message. { "redis://weird url", @@ -502,41 +552,85 @@ func TestDialURLHost(t *testing.T) { } } -func TestDialURLPassword(t *testing.T) { - var buf bytes.Buffer - _, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf)) - if err != nil { - t.Error("dial error:", err) +var dialURLTests = []struct { + description string + url string + r string + w string +}{ + {"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"}, + {"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"}, + {"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"}, + {"no database", "redis://localhost/", "+OK\r\n", ""}, +} + +func TestDialURL(t *testing.T) { + for _, tt := range dialURLTests { + var buf bytes.Buffer + // UseTLS should be ignored in all of these tests. + _, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true)) + if err != nil { + t.Errorf("%s dial error: %v", tt.description, err) + continue + } + if w := buf.String(); w != tt.w { + t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w) + } } - expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n" +} + +func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) { + resp, err := c.Do("PING") + if err != nil { + t.Fatal("ping error:", err) + } + // Close connection to ensure that writes to buf are complete. + c.Close() + expected := "*1\r\n$4\r\nPING\r\n" actual := buf.String() if actual != expected { t.Errorf("commands = %q, want %q", actual, expected) } + if resp != "PONG" { + t.Errorf("resp = %v, want %v", resp, "PONG") + } } -func TestDialURLDatabase(t *testing.T) { - var buf3 bytes.Buffer - _, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3)) - if err3 != nil { - t.Error("dial error:", err3) +const pingResponse = "+PONG\r\n" + +func TestDialURLTLS(t *testing.T) { + var buf bytes.Buffer + c, err := redis.DialURL("rediss://example.com/", + redis.DialTLSConfig(&clientTLSConfig), + dialTestConnTLS(pingResponse, &buf)) + if err != nil { + t.Fatal("dial error:", err) } - expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n" - actual3 := buf3.String() - if actual3 != expected3 { - t.Errorf("commands = %q, want %q", actual3, expected3) + checkPingPong(t, &buf, c) +} + +func TestDialUseTLS(t *testing.T) { + var buf bytes.Buffer + c, err := redis.Dial("tcp", "example.com:6379", + redis.DialTLSConfig(&clientTLSConfig), + dialTestConnTLS(pingResponse, &buf), + redis.DialUseTLS(true)) + if err != nil { + t.Fatal("dial error:", err) } - // empty DB means 0 - var buf0 bytes.Buffer - _, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0)) - if err0 != nil { - t.Error("dial error:", err0) - } - expected0 := "" - actual0 := buf0.String() - if actual0 != expected0 { - t.Errorf("commands = %q, want %q", actual0, expected0) + checkPingPong(t, &buf, c) +} + +func TestDialTLSSKipVerify(t *testing.T) { + var buf bytes.Buffer + c, err := redis.Dial("tcp", "example.com:6379", + dialTestConnTLS(pingResponse, &buf), + redis.DialTLSSkipVerify(true), + redis.DialUseTLS(true)) + if err != nil { + t.Fatal("dial error:", err) } + checkPingPong(t, &buf, c) } // Connect to local instance of Redis running on the default port. @@ -668,3 +762,106 @@ func BenchmarkDoPing(b *testing.B) { } } } + +var clientTLSConfig, serverTLSConfig tls.Config + +func init() { + // The certificate and key for testing TLS dial options was created + // using the command + // + // go run GOROOT/src/crypto/tls/generate_cert.go \ + // --rsa-bits 1024 \ + // --host 127.0.0.1,::1,example.com --ca \ + // --start-date "Jan 1 00:00:00 1970" \ + // --duration=1000000h + // + // where GOROOT is the value of GOROOT reported by go env. + localhostCert := []byte(` +-----BEGIN CERTIFICATE----- +MIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw +EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 +MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+ +LGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+ +JaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw +DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF +MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA +AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+ +ach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9 +6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt +rrKgNsltzMk= +-----END CERTIFICATE-----`) + + localhostKey := []byte(` +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi +bMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l +SoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB +AoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB +Eq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y +HenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm +KbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw +KjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa +m6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0 +pDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci +Eo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH +diKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc +Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA= +-----END RSA PRIVATE KEY-----`) + + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + panic(fmt.Sprintf("error creating key pair: %v", err)) + } + serverTLSConfig.Certificates = []tls.Certificate{cert} + + certificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0]) + if err != nil { + panic(fmt.Sprintf("error parsing x509 certificate: %v", err)) + } + + clientTLSConfig.RootCAs = x509.NewCertPool() + clientTLSConfig.RootCAs.AddCert(certificate) +} + +func TestWithTimeout(t *testing.T) { + for _, recv := range []bool{true, false} { + for _, defaultTimout := range []time.Duration{0, time.Minute} { + var buf bytes.Buffer + nc := &testConn{Reader: strings.NewReader("+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n+OK\r\n"), Writer: &buf} + c, _ := redis.Dial("", "", redis.DialReadTimeout(defaultTimout), redis.DialNetDial(func(network, addr string) (net.Conn, error) { return nc, nil })) + for i := 0; i < 4; i++ { + var minDeadline, maxDeadline time.Time + + // Alternate between default and specified timeout. + if i%2 == 0 { + if defaultTimout != 0 { + minDeadline = time.Now().Add(defaultTimout) + } + if recv { + c.Receive() + } else { + c.Do("PING") + } + if defaultTimout != 0 { + maxDeadline = time.Now().Add(defaultTimout) + } + } else { + timeout := 10 * time.Minute + minDeadline = time.Now().Add(timeout) + if recv { + redis.ReceiveWithTimeout(c, timeout) + } else { + redis.DoWithTimeout(c, timeout, "PING") + } + maxDeadline = time.Now().Add(timeout) + } + + // Expect set deadline in expected range. + if nc.readDeadline.Before(minDeadline) || nc.readDeadline.After(maxDeadline) { + t.Errorf("recv %v, %d: do deadline error: %v, %v, %v", recv, i, minDeadline, nc.readDeadline, maxDeadline) + } + } + } + } +} diff --git a/vendor/github.com/garyburd/redigo/redis/doc.go b/vendor/github.com/gomodule/redigo/redis/doc.go similarity index 96% rename from vendor/github.com/garyburd/redigo/redis/doc.go rename to vendor/github.com/gomodule/redigo/redis/doc.go index d4f48928..4e3dc438 100644 --- a/vendor/github.com/garyburd/redigo/redis/doc.go +++ b/vendor/github.com/gomodule/redigo/redis/doc.go @@ -14,7 +14,7 @@ // Package redis is a client for the Redis database. // -// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more +// The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more // documentation about this package. // // Connections @@ -38,7 +38,7 @@ // // n, err := conn.Do("APPEND", "key", "value") // -// The Do method converts command arguments to binary strings for transmission +// The Do method converts command arguments to bulk strings for transmission // to the server as follows: // // Go Type Conversion @@ -48,7 +48,7 @@ // float64 strconv.FormatFloat(v, 'g', -1, 64) // bool true -> "1", false -> "0" // nil "" -// all other types fmt.Print(v) +// all other types fmt.Fprint(w, v) // // Redis command reply types are represented using the following Go types: // @@ -174,4 +174,4 @@ // non-recoverable error such as a network error or protocol parsing error. If // Err() returns a non-nil value, then the connection is not usable and should // be closed. -package redis // import "github.com/garyburd/redigo/redis" +package redis diff --git a/vendor/github.com/garyburd/redigo/redis/pre_go17.go b/vendor/github.com/gomodule/redigo/redis/go16.go similarity index 78% rename from vendor/github.com/garyburd/redigo/redis/pre_go17.go rename to vendor/github.com/gomodule/redigo/redis/go16.go index 0212f60f..f6b1a7cc 100644 --- a/vendor/github.com/garyburd/redigo/redis/pre_go17.go +++ b/vendor/github.com/gomodule/redigo/redis/go16.go @@ -4,11 +4,7 @@ package redis import "crypto/tls" -// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case -func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { - if cfg == nil { - return &tls.Config{InsecureSkipVerify: skipVerify} - } +func cloneTLSConfig(cfg *tls.Config) *tls.Config { return &tls.Config{ Rand: cfg.Rand, Time: cfg.Time, diff --git a/vendor/github.com/garyburd/redigo/redis/go17.go b/vendor/github.com/gomodule/redigo/redis/go17.go similarity index 79% rename from vendor/github.com/garyburd/redigo/redis/go17.go rename to vendor/github.com/gomodule/redigo/redis/go17.go index 3f951e5e..5f363791 100644 --- a/vendor/github.com/garyburd/redigo/redis/go17.go +++ b/vendor/github.com/gomodule/redigo/redis/go17.go @@ -1,14 +1,10 @@ -// +build go1.7 +// +build go1.7,!go1.8 package redis import "crypto/tls" -// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case -func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { - if cfg == nil { - return &tls.Config{InsecureSkipVerify: skipVerify} - } +func cloneTLSConfig(cfg *tls.Config) *tls.Config { return &tls.Config{ Rand: cfg.Rand, Time: cfg.Time, diff --git a/vendor/github.com/gomodule/redigo/redis/go18.go b/vendor/github.com/gomodule/redigo/redis/go18.go new file mode 100644 index 00000000..558363be --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/go18.go @@ -0,0 +1,9 @@ +// +build go1.8 + +package redis + +import "crypto/tls" + +func cloneTLSConfig(cfg *tls.Config) *tls.Config { + return cfg.Clone() +} diff --git a/vendor/github.com/gomodule/redigo/redis/list_test.go b/vendor/github.com/gomodule/redigo/redis/list_test.go new file mode 100644 index 00000000..9c34bede --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/list_test.go @@ -0,0 +1,85 @@ +// Copyright 2018 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.9 + +package redis + +import "testing" + +func TestPoolList(t *testing.T) { + var idle idleList + var a, b, c poolConn + + check := func(pcs ...*poolConn) { + if idle.count != len(pcs) { + t.Fatal("idle.count != len(pcs)") + } + if len(pcs) == 0 { + if idle.front != nil { + t.Fatalf("front not nil") + } + if idle.back != nil { + t.Fatalf("back not nil") + } + return + } + if idle.front != pcs[0] { + t.Fatal("front != pcs[0]") + } + if idle.back != pcs[len(pcs)-1] { + t.Fatal("back != pcs[len(pcs)-1]") + } + if idle.front.prev != nil { + t.Fatal("front.prev != nil") + } + if idle.back.next != nil { + t.Fatal("back.next != nil") + } + for i := 1; i < len(pcs)-1; i++ { + if pcs[i-1].next != pcs[i] { + t.Fatal("pcs[i-1].next != pcs[i]") + } + if pcs[i+1].prev != pcs[i] { + t.Fatal("pcs[i+1].prev != pcs[i]") + } + } + } + + idle.pushFront(&c) + check(&c) + idle.pushFront(&b) + check(&b, &c) + idle.pushFront(&a) + check(&a, &b, &c) + idle.popFront() + check(&b, &c) + idle.popFront() + check(&c) + idle.popFront() + check() + + idle.pushFront(&c) + check(&c) + idle.pushFront(&b) + check(&b, &c) + idle.pushFront(&a) + check(&a, &b, &c) + idle.popBack() + check(&a, &b) + idle.popBack() + check(&a) + idle.popBack() + check() +} diff --git a/vendor/github.com/garyburd/redigo/redis/log.go b/vendor/github.com/gomodule/redigo/redis/log.go similarity index 73% rename from vendor/github.com/garyburd/redigo/redis/log.go rename to vendor/github.com/gomodule/redigo/redis/log.go index 129b86d6..a06db9d6 100644 --- a/vendor/github.com/garyburd/redigo/redis/log.go +++ b/vendor/github.com/gomodule/redigo/redis/log.go @@ -18,6 +18,11 @@ import ( "bytes" "fmt" "log" + "time" +) + +var ( + _ ConnWithTimeout = (*loggingConn)(nil) ) // NewLoggingConn returns a logging wrapper around a connection. @@ -25,13 +30,22 @@ func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { if prefix != "" { prefix = prefix + "." } - return &loggingConn{conn, logger, prefix} + return &loggingConn{conn, logger, prefix, nil} +} + +//NewLoggingConnFilter returns a logging wrapper around a connection and a filter function. +func NewLoggingConnFilter(conn Conn, logger *log.Logger, prefix string, skip func(cmdName string) bool) Conn { + if prefix != "" { + prefix = prefix + "." + } + return &loggingConn{conn, logger, prefix, skip} } type loggingConn struct { Conn logger *log.Logger prefix string + skip func(cmdName string) bool } func (c *loggingConn) Close() error { @@ -80,6 +94,9 @@ func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { } func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { + if c.skip != nil && c.skip(commandName) { + return + } var buf bytes.Buffer fmt.Fprintf(&buf, "%s%s(", c.prefix, method) if method != "Receive" { @@ -104,6 +121,12 @@ func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, return reply, err } +func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { + reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) + c.print("DoWithTimeout", commandName, args, reply, err) + return reply, err +} + func (c *loggingConn) Send(commandName string, args ...interface{}) error { err := c.Conn.Send(commandName, args...) c.print("Send", commandName, args, nil, err) @@ -115,3 +138,9 @@ func (c *loggingConn) Receive() (interface{}, error) { c.print("Receive", "", nil, reply, err) return reply, err } + +func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { + reply, err := ReceiveWithTimeout(c.Conn, timeout) + c.print("ReceiveWithTimeout", "", nil, reply, err) + return reply, err +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool.go b/vendor/github.com/gomodule/redigo/redis/pool.go new file mode 100644 index 00000000..a833f85c --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool.go @@ -0,0 +1,560 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "bytes" + "crypto/rand" + "crypto/sha1" + "errors" + "io" + "strconv" + "sync" + "sync/atomic" + "time" +) + +var ( + _ ConnWithTimeout = (*activeConn)(nil) + _ ConnWithTimeout = (*errorConn)(nil) +) + +var nowFunc = time.Now // for testing + +// ErrPoolExhausted is returned from a pool connection method (Do, Send, +// Receive, Flush, Err) when the maximum number of database connections in the +// pool has been reached. +var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") + +var ( + errPoolClosed = errors.New("redigo: connection pool closed") + errConnClosed = errors.New("redigo: connection closed") +) + +// Pool maintains a pool of connections. The application calls the Get method +// to get a connection from the pool and the connection's Close method to +// return the connection's resources to the pool. +// +// The following example shows how to use a pool in a web application. The +// application creates a pool at application startup and makes it available to +// request handlers using a package level variable. The pool configuration used +// here is an example, not a recommendation. +// +// func newPool(addr string) *redis.Pool { +// return &redis.Pool{ +// MaxIdle: 3, +// IdleTimeout: 240 * time.Second, +// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, +// } +// } +// +// var ( +// pool *redis.Pool +// redisServer = flag.String("redisServer", ":6379", "") +// ) +// +// func main() { +// flag.Parse() +// pool = newPool(*redisServer) +// ... +// } +// +// A request handler gets a connection from the pool and closes the connection +// when the handler is done: +// +// func serveHome(w http.ResponseWriter, r *http.Request) { +// conn := pool.Get() +// defer conn.Close() +// ... +// } +// +// Use the Dial function to authenticate connections with the AUTH command or +// select a database with the SELECT command: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// Dial: func () (redis.Conn, error) { +// c, err := redis.Dial("tcp", server) +// if err != nil { +// return nil, err +// } +// if _, err := c.Do("AUTH", password); err != nil { +// c.Close() +// return nil, err +// } +// if _, err := c.Do("SELECT", db); err != nil { +// c.Close() +// return nil, err +// } +// return c, nil +// }, +// } +// +// Use the TestOnBorrow function to check the health of an idle connection +// before the connection is returned to the application. This example PINGs +// connections that have been idle more than a minute: +// +// pool := &redis.Pool{ +// // Other pool configuration not shown in this example. +// TestOnBorrow: func(c redis.Conn, t time.Time) error { +// if time.Since(t) < time.Minute { +// return nil +// } +// _, err := c.Do("PING") +// return err +// }, +// } +// +type Pool struct { + // Dial is an application supplied function for creating and configuring a + // connection. + // + // The connection returned from Dial must not be in a special state + // (subscribed to pubsub channel, transaction started, ...). + Dial func() (Conn, error) + + // TestOnBorrow is an optional application supplied function for checking + // the health of an idle connection before the connection is used again by + // the application. Argument t is the time that the connection was returned + // to the pool. If the function returns an error, then the connection is + // closed. + TestOnBorrow func(c Conn, t time.Time) error + + // Maximum number of idle connections in the pool. + MaxIdle int + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + + // Close connections older than this duration. If the value is zero, then + // the pool does not close connections based on age. + MaxConnLifetime time.Duration + + chInitialized uint32 // set to 1 when field ch is initialized + + mu sync.Mutex // mu protects the following fields + closed bool // set to true when the pool is closed. + active int // the number of open connections in the pool + ch chan struct{} // limits open connections when p.Wait is true + idle idleList // idle connections +} + +// NewPool creates a new pool. +// +// Deprecated: Initialize the Pool directory as shown in the example. +func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { + return &Pool{Dial: newFn, MaxIdle: maxIdle} +} + +// Get gets a connection. The application must close the returned connection. +// This method always returns a valid connection so that applications can defer +// error handling to the first use of the connection. If there is an error +// getting an underlying connection, then the connection Err, Do, Send, Flush +// and Receive methods return that error. +func (p *Pool) Get() Conn { + pc, err := p.get(nil) + if err != nil { + return errorConn{err} + } + return &activeConn{p: p, pc: pc} +} + +// PoolStats contains pool statistics. +type PoolStats struct { + // ActiveCount is the number of connections in the pool. The count includes + // idle connections and connections in use. + ActiveCount int + // IdleCount is the number of idle connections in the pool. + IdleCount int +} + +// Stats returns pool's statistics. +func (p *Pool) Stats() PoolStats { + p.mu.Lock() + stats := PoolStats{ + ActiveCount: p.active, + IdleCount: p.idle.count, + } + p.mu.Unlock() + + return stats +} + +// ActiveCount returns the number of connections in the pool. The count +// includes idle connections and connections in use. +func (p *Pool) ActiveCount() int { + p.mu.Lock() + active := p.active + p.mu.Unlock() + return active +} + +// IdleCount returns the number of idle connections in the pool. +func (p *Pool) IdleCount() int { + p.mu.Lock() + idle := p.idle.count + p.mu.Unlock() + return idle +} + +// Close releases the resources used by the pool. +func (p *Pool) Close() error { + p.mu.Lock() + if p.closed { + p.mu.Unlock() + return nil + } + p.closed = true + p.active -= p.idle.count + pc := p.idle.front + p.idle.count = 0 + p.idle.front, p.idle.back = nil, nil + if p.ch != nil { + close(p.ch) + } + p.mu.Unlock() + for ; pc != nil; pc = pc.next { + pc.c.Close() + } + return nil +} + +func (p *Pool) lazyInit() { + // Fast path. + if atomic.LoadUint32(&p.chInitialized) == 1 { + return + } + // Slow path. + p.mu.Lock() + if p.chInitialized == 0 { + p.ch = make(chan struct{}, p.MaxActive) + if p.closed { + close(p.ch) + } else { + for i := 0; i < p.MaxActive; i++ { + p.ch <- struct{}{} + } + } + atomic.StoreUint32(&p.chInitialized, 1) + } + p.mu.Unlock() +} + +// get prunes stale connections and returns a connection from the idle list or +// creates a new connection. +func (p *Pool) get(ctx interface { + Done() <-chan struct{} + Err() error +}) (*poolConn, error) { + + // Handle limit for p.Wait == true. + if p.Wait && p.MaxActive > 0 { + p.lazyInit() + if ctx == nil { + <-p.ch + } else { + select { + case <-p.ch: + case <-ctx.Done(): + return nil, ctx.Err() + } + } + } + + p.mu.Lock() + + // Prune stale connections at the back of the idle list. + if p.IdleTimeout > 0 { + n := p.idle.count + for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { + pc := p.idle.back + p.idle.popBack() + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + } + + // Get idle connection from the front of idle list. + for p.idle.front != nil { + pc := p.idle.front + p.idle.popFront() + p.mu.Unlock() + if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && + (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { + return pc, nil + } + pc.c.Close() + p.mu.Lock() + p.active-- + } + + // Check for pool closed before dialing a new connection. + if p.closed { + p.mu.Unlock() + return nil, errors.New("redigo: get on closed pool") + } + + // Handle limit for p.Wait == false. + if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { + p.mu.Unlock() + return nil, ErrPoolExhausted + } + + p.active++ + p.mu.Unlock() + c, err := p.Dial() + if err != nil { + c = nil + p.mu.Lock() + p.active-- + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + } + return &poolConn{c: c, created: nowFunc()}, err +} + +func (p *Pool) put(pc *poolConn, forceClose bool) error { + p.mu.Lock() + if !p.closed && !forceClose { + pc.t = nowFunc() + p.idle.pushFront(pc) + if p.idle.count > p.MaxIdle { + pc = p.idle.back + p.idle.popBack() + } else { + pc = nil + } + } + + if pc != nil { + p.mu.Unlock() + pc.c.Close() + p.mu.Lock() + p.active-- + } + + if p.ch != nil && !p.closed { + p.ch <- struct{}{} + } + p.mu.Unlock() + return nil +} + +type activeConn struct { + p *Pool + pc *poolConn + state int +} + +var ( + sentinel []byte + sentinelOnce sync.Once +) + +func initSentinel() { + p := make([]byte, 64) + if _, err := rand.Read(p); err == nil { + sentinel = p + } else { + h := sha1.New() + io.WriteString(h, "Oops, rand failed. Use time instead.") + io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) + sentinel = h.Sum(nil) + } +} + +func (ac *activeConn) Close() error { + pc := ac.pc + if pc == nil { + return nil + } + ac.pc = nil + + if ac.state&connectionMultiState != 0 { + pc.c.Send("DISCARD") + ac.state &^= (connectionMultiState | connectionWatchState) + } else if ac.state&connectionWatchState != 0 { + pc.c.Send("UNWATCH") + ac.state &^= connectionWatchState + } + if ac.state&connectionSubscribeState != 0 { + pc.c.Send("UNSUBSCRIBE") + pc.c.Send("PUNSUBSCRIBE") + // To detect the end of the message stream, ask the server to echo + // a sentinel value and read until we see that value. + sentinelOnce.Do(initSentinel) + pc.c.Send("ECHO", sentinel) + pc.c.Flush() + for { + p, err := pc.c.Receive() + if err != nil { + break + } + if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { + ac.state &^= connectionSubscribeState + break + } + } + } + pc.c.Do("") + ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil) + return nil +} + +func (ac *activeConn) Err() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Err() +} + +func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + ci := lookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Do(commandName, args...) +} + +func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + ci := lookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return cwt.DoWithTimeout(timeout, commandName, args...) +} + +func (ac *activeConn) Send(commandName string, args ...interface{}) error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + ci := lookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return pc.c.Send(commandName, args...) +} + +func (ac *activeConn) Flush() error { + pc := ac.pc + if pc == nil { + return errConnClosed + } + return pc.c.Flush() +} + +func (ac *activeConn) Receive() (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + return pc.c.Receive() +} + +func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} + +type errorConn struct{ err error } + +func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } +func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { + return nil, ec.err +} +func (ec errorConn) Send(string, ...interface{}) error { return ec.err } +func (ec errorConn) Err() error { return ec.err } +func (ec errorConn) Close() error { return nil } +func (ec errorConn) Flush() error { return ec.err } +func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } +func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } + +type idleList struct { + count int + front, back *poolConn +} + +type poolConn struct { + c Conn + t time.Time + created time.Time + next, prev *poolConn +} + +func (l *idleList) pushFront(pc *poolConn) { + pc.next = l.front + pc.prev = nil + if l.count == 0 { + l.back = pc + } else { + l.front.prev = pc + } + l.front = pc + l.count++ + return +} + +func (l *idleList) popFront() { + pc := l.front + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.next.prev = nil + l.front = pc.next + } + pc.next, pc.prev = nil, nil +} + +func (l *idleList) popBack() { + pc := l.back + l.count-- + if l.count == 0 { + l.front, l.back = nil, nil + } else { + pc.prev.next = nil + l.back = pc.prev + } + pc.next, pc.prev = nil, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool17.go b/vendor/github.com/gomodule/redigo/redis/pool17.go new file mode 100644 index 00000000..c1ea18ee --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool17.go @@ -0,0 +1,35 @@ +// Copyright 2018 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.7 + +package redis + +import "context" + +// GetContext gets a connection using the provided context. +// +// The provided Context must be non-nil. If the context expires before the +// connection is complete, an error is returned. Any expiration on the context +// will not affect the returned connection. +// +// If the function completes without error, then the application must close the +// returned connection. +func (p *Pool) GetContext(ctx context.Context) (Conn, error) { + pc, err := p.get(ctx) + if err != nil { + return errorConn{err}, err + } + return &activeConn{p: p, pc: pc}, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/pool17_test.go b/vendor/github.com/gomodule/redigo/redis/pool17_test.go new file mode 100644 index 00000000..d97bf7a6 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pool17_test.go @@ -0,0 +1,74 @@ +// Copyright 2018 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.7 + +package redis_test + +import ( + "context" + "testing" + + "github.com/gomodule/redigo/redis" +) + +func TestWaitPoolGetContext(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 1, + MaxActive: 1, + Dial: d.dial, + Wait: true, + } + defer p.Close() + c, err := p.GetContext(context.Background()) + if err != nil { + t.Fatalf("GetContext returned %v", err) + } + defer c.Close() +} + +func TestWaitPoolGetAfterClose(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 1, + MaxActive: 1, + Dial: d.dial, + Wait: true, + } + p.Close() + _, err := p.GetContext(context.Background()) + if err == nil { + t.Fatal("expected error") + } +} + +func TestWaitPoolGetCanceledContext(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 1, + MaxActive: 1, + Dial: d.dial, + Wait: true, + } + defer p.Close() + ctx, f := context.WithCancel(context.Background()) + f() + c := p.Get() + defer c.Close() + _, err := p.GetContext(ctx) + if err != context.Canceled { + t.Fatalf("got error %v, want %v", err, context.Canceled) + } +} diff --git a/vendor/github.com/garyburd/redigo/redis/pool_test.go b/vendor/github.com/gomodule/redigo/redis/pool_test.go similarity index 84% rename from vendor/github.com/garyburd/redigo/redis/pool_test.go rename to vendor/github.com/gomodule/redigo/redis/pool_test.go index 26d2747a..3509dd4e 100644 --- a/vendor/github.com/garyburd/redigo/redis/pool_test.go +++ b/vendor/github.com/gomodule/redigo/redis/pool_test.go @@ -22,7 +22,7 @@ import ( "testing" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) type poolTestConn struct { @@ -83,7 +83,7 @@ func (d *poolDialer) dial() (redis.Conn, error) { return &poolTestConn{d: d, Conn: c}, nil } -func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { +func (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse int) { d.mu.Lock() if d.dialed != dialed { d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) @@ -91,9 +91,16 @@ func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { if d.open != open { d.t.Errorf("%s: open=%d, want %d", message, d.open, open) } - if active := p.ActiveCount(); active != open { - d.t.Errorf("%s: active=%d, want %d", message, active, open) + + stats := p.Stats() + + if stats.ActiveCount != open { + d.t.Errorf("%s: active=%d, want %d", message, stats.ActiveCount, open) } + if stats.IdleCount != open-inuse { + d.t.Errorf("%s: idle=%d, want %d", message, stats.IdleCount, open-inuse) + } + d.mu.Unlock() } @@ -113,9 +120,9 @@ func TestPoolReuse(t *testing.T) { c2.Close() } - d.check("before close", p, 2, 2) + d.check("before close", p, 2, 2, 0) p.Close() - d.check("after close", p, 2, 0) + d.check("after close", p, 2, 0, 0) } func TestPoolMaxIdle(t *testing.T) { @@ -137,9 +144,9 @@ func TestPoolMaxIdle(t *testing.T) { c2.Close() c3.Close() } - d.check("before close", p, 12, 2) + d.check("before close", p, 12, 2, 0) p.Close() - d.check("after close", p, 12, 0) + d.check("after close", p, 12, 0, 0) } func TestPoolError(t *testing.T) { @@ -161,7 +168,7 @@ func TestPoolError(t *testing.T) { c.Do("ERR", io.EOF) c.Close() - d.check(".", p, 2, 0) + d.check(".", p, 2, 0, 0) } func TestPoolClose(t *testing.T) { @@ -189,7 +196,7 @@ func TestPoolClose(t *testing.T) { p.Close() - d.check("after pool close", p, 3, 1) + d.check("after pool close", p, 3, 1, 1) if _, err := c1.Do("PING"); err == nil { t.Errorf("expected error after connection and pool closed") @@ -197,7 +204,7 @@ func TestPoolClose(t *testing.T) { c3.Close() - d.check("after conn close", p, 3, 0) + d.check("after conn close", p, 3, 0, 0) c1 = p.Get() if _, err := c1.Do("PING"); err == nil { @@ -205,7 +212,37 @@ func TestPoolClose(t *testing.T) { } } -func TestPoolTimeout(t *testing.T) { +func TestPoolClosedConn(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + IdleTimeout: 300 * time.Second, + Dial: d.dial, + } + defer p.Close() + c := p.Get() + if c.Err() != nil { + t.Fatal("get failed") + } + c.Close() + if err := c.Err(); err == nil { + t.Fatal("Err on closed connection did not return error") + } + if _, err := c.Do("PING"); err == nil { + t.Fatal("Do on closed connection did not return error") + } + if err := c.Send("PING"); err == nil { + t.Fatal("Send on closed connection did not return error") + } + if err := c.Flush(); err == nil { + t.Fatal("Flush on closed connection did not return error") + } + if _, err := c.Receive(); err == nil { + t.Fatal("Receive on closed connection did not return error") + } +} + +func TestPoolIdleTimeout(t *testing.T) { d := poolDialer{t: t} p := &redis.Pool{ MaxIdle: 2, @@ -222,15 +259,43 @@ func TestPoolTimeout(t *testing.T) { c.Do("PING") c.Close() - d.check("1", p, 1, 1) + d.check("1", p, 1, 1, 0) - now = now.Add(p.IdleTimeout) + now = now.Add(p.IdleTimeout + 1) c = p.Get() c.Do("PING") c.Close() - d.check("2", p, 2, 1) + d.check("2", p, 2, 1, 0) +} + +func TestPoolMaxLifetime(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + MaxConnLifetime: 300 * time.Second, + Dial: d.dial, + } + defer p.Close() + + now := time.Now() + redis.SetNowFunc(func() time.Time { return now }) + defer redis.SetNowFunc(time.Now) + + c := p.Get() + c.Do("PING") + c.Close() + + d.check("1", p, 1, 1, 0) + + now = now.Add(p.MaxConnLifetime + 1) + + c = p.Get() + c.Do("PING") + c.Close() + + d.check("2", p, 2, 1, 0) } func TestPoolConcurrenSendReceive(t *testing.T) { @@ -272,7 +337,7 @@ func TestPoolBorrowCheck(t *testing.T) { c.Do("PING") c.Close() } - d.check("1", p, 10, 1) + d.check("1", p, 10, 1, 0) } func TestPoolMaxActive(t *testing.T) { @@ -289,7 +354,7 @@ func TestPoolMaxActive(t *testing.T) { c2 := p.Get() c2.Do("PING") - d.check("1", p, 2, 2) + d.check("1", p, 2, 2, 2) c3 := p.Get() if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted { @@ -297,9 +362,9 @@ func TestPoolMaxActive(t *testing.T) { } c3.Close() - d.check("2", p, 2, 2) + d.check("2", p, 2, 2, 2) c2.Close() - d.check("3", p, 2, 2) + d.check("3", p, 2, 2, 1) c3 = p.Get() if _, err := c3.Do("PING"); err != nil { @@ -307,7 +372,7 @@ func TestPoolMaxActive(t *testing.T) { } c3.Close() - d.check("4", p, 2, 2) + d.check("4", p, 2, 2, 1) } func TestPoolMonitorCleanup(t *testing.T) { @@ -323,7 +388,7 @@ func TestPoolMonitorCleanup(t *testing.T) { c.Send("MONITOR") c.Close() - d.check("", p, 1, 0) + d.check("", p, 1, 0, 0) } func TestPoolPubSubCleanup(t *testing.T) { @@ -433,14 +498,11 @@ func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error go func() { c := p.Get() _, err := c.Do(cmd, args...) - errs <- err c.Close() + errs <- err }() } - // Wait for goroutines to block. - time.Sleep(time.Second / 4) - return errs } @@ -456,7 +518,7 @@ func TestWaitPool(t *testing.T) { c := p.Get() errs := startGoroutines(p, "PING") - d.check("before close", p, 1, 1) + d.check("before close", p, 1, 1, 1) c.Close() timeout := time.After(2 * time.Second) for i := 0; i < cap(errs); i++ { @@ -469,7 +531,7 @@ func TestWaitPool(t *testing.T) { t.Fatalf("timeout waiting for blocked goroutine %d", i) } } - d.check("done", p, 1, 1) + d.check("done", p, 1, 1, 0) } func TestWaitPoolClose(t *testing.T) { @@ -487,7 +549,7 @@ func TestWaitPoolClose(t *testing.T) { t.Fatal(err) } errs := startGoroutines(p, "PING") - d.check("before close", p, 1, 1) + d.check("before close", p, 1, 1, 1) p.Close() timeout := time.After(2 * time.Second) for i := 0; i < cap(errs); i++ { @@ -504,7 +566,7 @@ func TestWaitPoolClose(t *testing.T) { } } c.Close() - d.check("done", p, 1, 0) + d.check("done", p, 1, 0, 0) } func TestWaitPoolCommandError(t *testing.T) { @@ -520,7 +582,7 @@ func TestWaitPoolCommandError(t *testing.T) { c := p.Get() errs := startGoroutines(p, "ERR", testErr) - d.check("before close", p, 1, 1) + d.check("before close", p, 1, 1, 1) c.Close() timeout := time.After(2 * time.Second) for i := 0; i < cap(errs); i++ { @@ -533,7 +595,7 @@ func TestWaitPoolCommandError(t *testing.T) { t.Fatalf("timeout waiting for blocked goroutine %d", i) } } - d.check("done", p, cap(errs), 0) + d.check("done", p, cap(errs), 0, 0) } func TestWaitPoolDialError(t *testing.T) { @@ -549,7 +611,7 @@ func TestWaitPoolDialError(t *testing.T) { c := p.Get() errs := startGoroutines(p, "ERR", testErr) - d.check("before close", p, 1, 1) + d.check("before close", p, 1, 1, 1) d.dialErr = errors.New("dial") c.Close() @@ -578,7 +640,7 @@ func TestWaitPoolDialError(t *testing.T) { if errCount != cap(errs)-1 { t.Errorf("expected %d dial errors, got %d", cap(errs)-1, errCount) } - d.check("done", p, cap(errs), 0) + d.check("done", p, cap(errs), 0, 0) } // Borrowing requires us to iterate over the idle connections, unlock the pool, diff --git a/vendor/github.com/garyburd/redigo/redis/pubsub.go b/vendor/github.com/gomodule/redigo/redis/pubsub.go similarity index 79% rename from vendor/github.com/garyburd/redigo/redis/pubsub.go rename to vendor/github.com/gomodule/redigo/redis/pubsub.go index c0ecce82..2da60211 100644 --- a/vendor/github.com/garyburd/redigo/redis/pubsub.go +++ b/vendor/github.com/gomodule/redigo/redis/pubsub.go @@ -14,11 +14,13 @@ package redis -import "errors" +import ( + "errors" + "time" +) // Subscription represents a subscribe or unsubscribe notification. type Subscription struct { - // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" Kind string @@ -31,23 +33,12 @@ type Subscription struct { // Message represents a message notification. type Message struct { - // The originating channel. Channel string - // The message data. - Data []byte -} - -// PMessage represents a pmessage notification. -type PMessage struct { - - // The matched pattern. + // The matched pattern, if any Pattern string - // The originating channel. - Channel string - // The message data. Data []byte } @@ -94,16 +85,29 @@ func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { } // Ping sends a PING to the server with the specified data. +// +// The connection must be subscribed to at least one channel or pattern when +// calling this method. func (c PubSubConn) Ping(data string) error { c.Conn.Send("PING", data) return c.Conn.Flush() } -// Receive returns a pushed message as a Subscription, Message, PMessage, Pong -// or error. The return value is intended to be used directly in a type switch -// as illustrated in the PubSubConn example. +// Receive returns a pushed message as a Subscription, Message, Pong or error. +// The return value is intended to be used directly in a type switch as +// illustrated in the PubSubConn example. func (c PubSubConn) Receive() interface{} { - reply, err := Values(c.Conn.Receive()) + return c.receiveInternal(c.Conn.Receive()) +} + +// ReceiveWithTimeout is like Receive, but it allows the application to +// override the connection's default timeout. +func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { + return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) +} + +func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { + reply, err := Values(replyArg, errArg) if err != nil { return err } @@ -122,11 +126,11 @@ func (c PubSubConn) Receive() interface{} { } return m case "pmessage": - var pm PMessage - if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { + var m Message + if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil { return err } - return pm + return m case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": s := Subscription{Kind: kind} if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { diff --git a/vendor/github.com/gomodule/redigo/redis/pubsub_example_test.go b/vendor/github.com/gomodule/redigo/redis/pubsub_example_test.go new file mode 100644 index 00000000..39d0abf8 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/pubsub_example_test.go @@ -0,0 +1,165 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// +build go1.7 + +package redis_test + +import ( + "context" + "fmt" + "time" + + "github.com/gomodule/redigo/redis" +) + +// listenPubSubChannels listens for messages on Redis pubsub channels. The +// onStart function is called after the channels are subscribed. The onMessage +// function is called for each message. +func listenPubSubChannels(ctx context.Context, redisServerAddr string, + onStart func() error, + onMessage func(channel string, data []byte) error, + channels ...string) error { + // A ping is set to the server with this period to test for the health of + // the connection and server. + const healthCheckPeriod = time.Minute + + c, err := redis.Dial("tcp", redisServerAddr, + // Read timeout on server should be greater than ping period. + redis.DialReadTimeout(healthCheckPeriod+10*time.Second), + redis.DialWriteTimeout(10*time.Second)) + if err != nil { + return err + } + defer c.Close() + + psc := redis.PubSubConn{Conn: c} + + if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil { + return err + } + + done := make(chan error, 1) + + // Start a goroutine to receive notifications from the server. + go func() { + for { + switch n := psc.Receive().(type) { + case error: + done <- n + return + case redis.Message: + if err := onMessage(n.Channel, n.Data); err != nil { + done <- err + return + } + case redis.Subscription: + switch n.Count { + case len(channels): + // Notify application when all channels are subscribed. + if err := onStart(); err != nil { + done <- err + return + } + case 0: + // Return from the goroutine when all channels are unsubscribed. + done <- nil + return + } + } + } + }() + + ticker := time.NewTicker(healthCheckPeriod) + defer ticker.Stop() +loop: + for err == nil { + select { + case <-ticker.C: + // Send ping to test health of connection and server. If + // corresponding pong is not received, then receive on the + // connection will timeout and the receive goroutine will exit. + if err = psc.Ping(""); err != nil { + break loop + } + case <-ctx.Done(): + break loop + case err := <-done: + // Return error from the receive goroutine. + return err + } + } + + // Signal the receiving goroutine to exit by unsubscribing from all channels. + psc.Unsubscribe() + + // Wait for goroutine to complete. + return <-done +} + +func publish() { + c, err := dial() + if err != nil { + fmt.Println(err) + return + } + defer c.Close() + + c.Do("PUBLISH", "c1", "hello") + c.Do("PUBLISH", "c2", "world") + c.Do("PUBLISH", "c1", "goodbye") +} + +// This example shows how receive pubsub notifications with cancelation and +// health checks. +func ExamplePubSubConn() { + redisServerAddr, err := serverAddr() + if err != nil { + fmt.Println(err) + return + } + + ctx, cancel := context.WithCancel(context.Background()) + + err = listenPubSubChannels(ctx, + redisServerAddr, + func() error { + // The start callback is a good place to backfill missed + // notifications. For the purpose of this example, a goroutine is + // started to send notifications. + go publish() + return nil + }, + func(channel string, message []byte) error { + fmt.Printf("channel: %s, message: %s\n", channel, message) + + // For the purpose of this example, cancel the listener's context + // after receiving last message sent by publish(). + if string(message) == "goodbye" { + cancel() + } + return nil + }, + "c1", "c2") + + if err != nil { + fmt.Println(err) + return + } + + // Output: + // channel: c1, message: hello + // channel: c2, message: world + // channel: c1, message: goodbye +} diff --git a/vendor/github.com/garyburd/redigo/redis/pubsub_test.go b/vendor/github.com/gomodule/redigo/redis/pubsub_test.go similarity index 53% rename from vendor/github.com/garyburd/redigo/redis/pubsub_test.go rename to vendor/github.com/gomodule/redigo/redis/pubsub_test.go index b9551315..13f3f797 100644 --- a/vendor/github.com/garyburd/redigo/redis/pubsub_test.go +++ b/vendor/github.com/gomodule/redigo/redis/pubsub_test.go @@ -15,93 +15,13 @@ package redis_test import ( - "fmt" "reflect" - "sync" "testing" + "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) -func publish(channel, value interface{}) { - c, err := dial() - if err != nil { - fmt.Println(err) - return - } - defer c.Close() - c.Do("PUBLISH", channel, value) -} - -// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine. -func ExamplePubSubConn() { - c, err := dial() - if err != nil { - fmt.Println(err) - return - } - defer c.Close() - var wg sync.WaitGroup - wg.Add(2) - - psc := redis.PubSubConn{Conn: c} - - // This goroutine receives and prints pushed notifications from the server. - // The goroutine exits when the connection is unsubscribed from all - // channels or there is an error. - go func() { - defer wg.Done() - for { - switch n := psc.Receive().(type) { - case redis.Message: - fmt.Printf("Message: %s %s\n", n.Channel, n.Data) - case redis.PMessage: - fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data) - case redis.Subscription: - fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count) - if n.Count == 0 { - return - } - case error: - fmt.Printf("error: %v\n", n) - return - } - } - }() - - // This goroutine manages subscriptions for the connection. - go func() { - defer wg.Done() - - psc.Subscribe("example") - psc.PSubscribe("p*") - - // The following function calls publish a message using another - // connection to the Redis server. - publish("example", "hello") - publish("example", "world") - publish("pexample", "foo") - publish("pexample", "bar") - - // Unsubscribe from all connections. This will cause the receiving - // goroutine to exit. - psc.Unsubscribe() - psc.PUnsubscribe() - }() - - wg.Wait() - - // Output: - // Subscription: subscribe example 1 - // Subscription: psubscribe p* 2 - // Message: example hello - // Message: example world - // PMessage: p* pexample foo - // PMessage: p* pexample bar - // Subscription: unsubscribe example 1 - // Subscription: punsubscribe p* 0 -} - func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) { actual := c.Receive() if !reflect.DeepEqual(actual, expected) { @@ -145,4 +65,10 @@ func TestPushed(t *testing.T) { c.Conn.Send("PING") c.Conn.Flush() expectPushed(t, c, `Send("PING")`, redis.Pong{}) + + c.Ping("timeout") + got := c.ReceiveWithTimeout(time.Minute) + if want := (redis.Pong{Data: "timeout"}); want != got { + t.Errorf("recv /w timeout got %v, want %v", got, want) + } } diff --git a/vendor/github.com/gomodule/redigo/redis/redis.go b/vendor/github.com/gomodule/redigo/redis/redis.go new file mode 100644 index 00000000..141fa4a9 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/redis.go @@ -0,0 +1,117 @@ +// Copyright 2012 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis + +import ( + "errors" + "time" +) + +// Error represents an error returned in a command reply. +type Error string + +func (err Error) Error() string { return string(err) } + +// Conn represents a connection to a Redis server. +type Conn interface { + // Close closes the connection. + Close() error + + // Err returns a non-nil value when the connection is not usable. + Err() error + + // Do sends a command to the server and returns the received reply. + Do(commandName string, args ...interface{}) (reply interface{}, err error) + + // Send writes the command to the client's output buffer. + Send(commandName string, args ...interface{}) error + + // Flush flushes the output buffer to the Redis server. + Flush() error + + // Receive receives a single reply from the Redis server + Receive() (reply interface{}, err error) +} + +// Argument is the interface implemented by an object which wants to control how +// the object is converted to Redis bulk strings. +type Argument interface { + // RedisArg returns a value to be encoded as a bulk string per the + // conversions listed in the section 'Executing Commands'. + // Implementations should typically return a []byte or string. + RedisArg() interface{} +} + +// Scanner is implemented by an object which wants to control its value is +// interpreted when read from Redis. +type Scanner interface { + // RedisScan assigns a value from a Redis value. The argument src is one of + // the reply types listed in the section `Executing Commands`. + // + // An error should be returned if the value cannot be stored without + // loss of information. + RedisScan(src interface{}) error +} + +// ConnWithTimeout is an optional interface that allows the caller to override +// a connection's default read timeout. This interface is useful for executing +// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the +// server. +// +// A connection's default read timeout is set with the DialReadTimeout dial +// option. Applications should rely on the default timeout for commands that do +// not block at the server. +// +// All of the Conn implementations in this package satisfy the ConnWithTimeout +// interface. +// +// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify +// use of this interface. +type ConnWithTimeout interface { + Conn + + // Do sends a command to the server and returns the received reply. + // The timeout overrides the read timeout set when dialing the + // connection. + DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) + + // Receive receives a single reply from the Redis server. The timeout + // overrides the read timeout set when dialing the connection. + ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) +} + +var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") + +// DoWithTimeout executes a Redis command with the specified read timeout. If +// the connection does not satisfy the ConnWithTimeout interface, then an error +// is returned. +func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.DoWithTimeout(timeout, cmd, args...) +} + +// ReceiveWithTimeout receives a reply with the specified read timeout. If the +// connection does not satisfy the ConnWithTimeout interface, then an error is +// returned. +func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { + cwt, ok := c.(ConnWithTimeout) + if !ok { + return nil, errTimeoutNotSupported + } + return cwt.ReceiveWithTimeout(timeout) +} diff --git a/vendor/github.com/gomodule/redigo/redis/redis_test.go b/vendor/github.com/gomodule/redigo/redis/redis_test.go new file mode 100644 index 00000000..5a98f535 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/redis_test.go @@ -0,0 +1,71 @@ +// Copyright 2017 Gary Burd +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package redis_test + +import ( + "testing" + "time" + + "github.com/gomodule/redigo/redis" +) + +type timeoutTestConn int + +func (tc timeoutTestConn) Do(string, ...interface{}) (interface{}, error) { + return time.Duration(-1), nil +} +func (tc timeoutTestConn) DoWithTimeout(timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { + return timeout, nil +} + +func (tc timeoutTestConn) Receive() (interface{}, error) { + return time.Duration(-1), nil +} +func (tc timeoutTestConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { + return timeout, nil +} + +func (tc timeoutTestConn) Send(string, ...interface{}) error { return nil } +func (tc timeoutTestConn) Err() error { return nil } +func (tc timeoutTestConn) Close() error { return nil } +func (tc timeoutTestConn) Flush() error { return nil } + +func testTimeout(t *testing.T, c redis.Conn) { + r, err := c.Do("PING") + if r != time.Duration(-1) || err != nil { + t.Errorf("Do() = %v, %v, want %v, %v", r, err, time.Duration(-1), nil) + } + r, err = redis.DoWithTimeout(c, time.Minute, "PING") + if r != time.Minute || err != nil { + t.Errorf("DoWithTimeout() = %v, %v, want %v, %v", r, err, time.Minute, nil) + } + r, err = c.Receive() + if r != time.Duration(-1) || err != nil { + t.Errorf("Receive() = %v, %v, want %v, %v", r, err, time.Duration(-1), nil) + } + r, err = redis.ReceiveWithTimeout(c, time.Minute) + if r != time.Minute || err != nil { + t.Errorf("ReceiveWithTimeout() = %v, %v, want %v, %v", r, err, time.Minute, nil) + } +} + +func TestConnTimeout(t *testing.T) { + testTimeout(t, timeoutTestConn(0)) +} + +func TestPoolConnTimeout(t *testing.T) { + p := &redis.Pool{Dial: func() (redis.Conn, error) { return timeoutTestConn(0), nil }} + testTimeout(t, p.Get()) +} diff --git a/vendor/github.com/garyburd/redigo/redis/reply.go b/vendor/github.com/gomodule/redigo/redis/reply.go similarity index 76% rename from vendor/github.com/garyburd/redigo/redis/reply.go rename to vendor/github.com/gomodule/redigo/redis/reply.go index 3d25dbae..c2b3b2b6 100644 --- a/vendor/github.com/garyburd/redigo/redis/reply.go +++ b/vendor/github.com/gomodule/redigo/redis/reply.go @@ -243,34 +243,67 @@ func Values(reply interface{}, err error) ([]interface{}, error) { return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) } +func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { + if err != nil { + return err + } + switch reply := reply.(type) { + case []interface{}: + makeSlice(len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + if err := assign(i, reply[i]); err != nil { + return err + } + } + return nil + case nil: + return ErrNil + case Error: + return reply + } + return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) +} + +// Float64s is a helper that converts an array command reply to a []float64. If +// err is not equal to nil, then Float64s returns nil, err. Nil array items are +// converted to 0 in the output slice. Floats64 returns an error if an array +// item is not a bulk string or nil. +func Float64s(reply interface{}, err error) ([]float64, error) { + var result []float64 + err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) + } + f, err := strconv.ParseFloat(string(p), 64) + result[i] = f + return err + }) + return result, err +} + // Strings is a helper that converts an array command reply to a []string. If // err is not equal to nil, then Strings returns nil, err. Nil array items are // converted to "" in the output slice. Strings returns an error if an array // item is not a bulk string or nil. func Strings(reply interface{}, err error) ([]string, error) { - if err != nil { - return nil, err - } - switch reply := reply.(type) { - case []interface{}: - result := make([]string, len(reply)) - for i := range reply { - if reply[i] == nil { - continue - } - p, ok := reply[i].([]byte) - if !ok { - return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) - } - result[i] = string(p) + var result []string + err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case string: + result[i] = v + return nil + case []byte: + result[i] = string(v) + return nil + default: + return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) } - return result, nil - case nil: - return nil, ErrNil - case Error: - return nil, reply - } - return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) + }) + return result, err } // ByteSlices is a helper that converts an array command reply to a [][]byte. @@ -278,43 +311,64 @@ func Strings(reply interface{}, err error) ([]string, error) { // items are stay nil. ByteSlices returns an error if an array item is not a // bulk string or nil. func ByteSlices(reply interface{}, err error) ([][]byte, error) { - if err != nil { - return nil, err - } - switch reply := reply.(type) { - case []interface{}: - result := make([][]byte, len(reply)) - for i := range reply { - if reply[i] == nil { - continue - } - p, ok := reply[i].([]byte) - if !ok { - return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i]) - } - result[i] = p + var result [][]byte + err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { + p, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) } - return result, nil - case nil: - return nil, ErrNil - case Error: - return nil, reply - } - return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply) + result[i] = p + return nil + }) + return result, err } -// Ints is a helper that converts an array command reply to a []int. If -// err is not equal to nil, then Ints returns nil, err. +// Int64s is a helper that converts an array command reply to a []int64. +// If err is not equal to nil, then Int64s returns nil, err. Nil array +// items are stay nil. Int64s returns an error if an array item is not a +// bulk string or nil. +func Int64s(reply interface{}, err error) ([]int64, error) { + var result []int64 + err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + result[i] = v + return nil + case []byte: + n, err := strconv.ParseInt(string(v), 10, 64) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) + } + }) + return result, err +} + +// Ints is a helper that converts an array command reply to a []in. +// If err is not equal to nil, then Ints returns nil, err. Nil array +// items are stay nil. Ints returns an error if an array item is not a +// bulk string or nil. func Ints(reply interface{}, err error) ([]int, error) { - var ints []int - values, err := Values(reply, err) - if err != nil { - return ints, err - } - if err := ScanSlice(values, &ints); err != nil { - return ints, err - } - return ints, nil + var result []int + err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { + switch v := v.(type) { + case int64: + n := int(v) + if int64(n) != v { + return strconv.ErrRange + } + result[i] = n + return nil + case []byte: + n, err := strconv.Atoi(string(v)) + result[i] = n + return err + default: + return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) + } + }) + return result, err } // StringMap is a helper that converts an array of strings (alternating key, value) @@ -333,7 +387,7 @@ func StringMap(result interface{}, err error) (map[string]string, error) { key, okKey := values[i].([]byte) value, okValue := values[i+1].([]byte) if !okKey || !okValue { - return nil, errors.New("redigo: ScanMap key not a bulk string value") + return nil, errors.New("redigo: StringMap key not a bulk string value") } m[string(key)] = string(value) } @@ -355,7 +409,7 @@ func IntMap(result interface{}, err error) (map[string]int, error) { for i := 0; i < len(values); i += 2 { key, ok := values[i].([]byte) if !ok { - return nil, errors.New("redigo: ScanMap key not a bulk string value") + return nil, errors.New("redigo: IntMap key not a bulk string value") } value, err := Int(values[i+1], nil) if err != nil { @@ -381,7 +435,7 @@ func Int64Map(result interface{}, err error) (map[string]int64, error) { for i := 0; i < len(values); i += 2 { key, ok := values[i].([]byte) if !ok { - return nil, errors.New("redigo: ScanMap key not a bulk string value") + return nil, errors.New("redigo: Int64Map key not a bulk string value") } value, err := Int64(values[i+1], nil) if err != nil { diff --git a/vendor/github.com/garyburd/redigo/redis/reply_test.go b/vendor/github.com/gomodule/redigo/redis/reply_test.go similarity index 78% rename from vendor/github.com/garyburd/redigo/redis/reply_test.go rename to vendor/github.com/gomodule/redigo/redis/reply_test.go index 81a25a97..c7f9b282 100644 --- a/vendor/github.com/garyburd/redigo/redis/reply_test.go +++ b/vendor/github.com/gomodule/redigo/redis/reply_test.go @@ -19,7 +19,7 @@ import ( "reflect" "testing" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) type valueError struct { @@ -37,24 +37,44 @@ var replyTests = []struct { expected valueError }{ { - "ints([v1, v2])", + "ints([[]byte, []byte])", ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)), ve([]int{4, 5}, nil), }, + { + "ints([nt64, int64])", + ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)), + ve([]int{4, 5}, nil), + }, + { + "ints([[]byte, nil, []byte])", + ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)), + ve([]int{4, 0, 5}, nil), + }, { "ints(nil)", ve(redis.Ints(nil, nil)), ve([]int(nil), redis.ErrNil), }, { - "strings([v1, v2])", + "int64s([[]byte, []byte])", + ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)), + ve([]int64{4, 5}, nil), + }, + { + "int64s([int64, int64])", + ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)), + ve([]int64{4, 5}, nil), + }, + { + "strings([[]byte, []bytev2])", ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)), ve([]string{"v1", "v2"}, nil), }, { - "strings(nil)", - ve(redis.Strings(nil, nil)), - ve([]string(nil), redis.ErrNil), + "strings([string, string])", + ve(redis.Strings([]interface{}{"v1", "v2"}, nil)), + ve([]string{"v1", "v2"}, nil), }, { "byteslices([v1, v2])", @@ -62,9 +82,9 @@ var replyTests = []struct { ve([][]byte{[]byte("v1"), []byte("v2")}, nil), }, { - "byteslices(nil)", - ve(redis.ByteSlices(nil, nil)), - ve([][]byte(nil), redis.ErrNil), + "float64s([v1, v2])", + ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)), + ve([]float64{1.234, 5.678}, nil), }, { "values([v1, v2])", @@ -120,6 +140,11 @@ func dial() (redis.Conn, error) { return redis.DialDefaultServer() } +// serverAddr wraps DefaultServerAddr() with a more suitable function name for examples. +func serverAddr() (string, error) { + return redis.DefaultServerAddr() +} + func ExampleBool() { c, err := dial() if err != nil { diff --git a/vendor/github.com/garyburd/redigo/redis/scan.go b/vendor/github.com/gomodule/redigo/redis/scan.go similarity index 95% rename from vendor/github.com/garyburd/redigo/redis/scan.go rename to vendor/github.com/gomodule/redigo/redis/scan.go index 1e8f9220..ef9551bd 100644 --- a/vendor/github.com/garyburd/redigo/redis/scan.go +++ b/vendor/github.com/gomodule/redigo/redis/scan.go @@ -110,6 +110,25 @@ func convertAssignInt(d reflect.Value, s int64) (err error) { } func convertAssignValue(d reflect.Value, s interface{}) (err error) { + if d.Kind() != reflect.Ptr { + if d.CanAddr() { + d2 := d.Addr() + if d2.CanInterface() { + if scanner, ok := d2.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + } + } else if d.CanInterface() { + // Already a reflect.Ptr + if d.IsNil() { + d.Set(reflect.New(d.Type().Elem())) + } + if scanner, ok := d.Interface().(Scanner); ok { + return scanner.RedisScan(s) + } + } + switch s := s.(type) { case []byte: err = convertAssignBulkString(d, s) @@ -135,11 +154,15 @@ func convertAssignArray(d reflect.Value, s []interface{}) error { } func convertAssign(d interface{}, s interface{}) (err error) { + if scanner, ok := d.(Scanner); ok { + return scanner.RedisScan(s) + } + // Handle the most common destination types using type switches and // fall back to reflection for all other types. switch s := s.(type) { case nil: - // ingore + // ignore case []byte: switch d := d.(type) { case *string: @@ -219,6 +242,8 @@ func convertAssign(d interface{}, s interface{}) (err error) { // Scan copies from src to the values pointed at by dest. // +// Scan uses RedisScan if available otherwise: +// // The values pointed at by dest must be an integer, float, boolean, string, // []byte, interface{} or slices of these types. Scan uses the standard strconv // package to convert bulk strings to numeric and boolean types. @@ -359,6 +384,7 @@ var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil po // // Fields with the tag redis:"-" are ignored. // +// Each field uses RedisScan if available otherwise: // Integer, float, boolean, string and []byte fields are supported. Scan uses the // standard strconv package to convert bulk string values to numeric and // boolean types. diff --git a/vendor/github.com/garyburd/redigo/redis/scan_test.go b/vendor/github.com/gomodule/redigo/redis/scan_test.go similarity index 85% rename from vendor/github.com/garyburd/redigo/redis/scan_test.go rename to vendor/github.com/gomodule/redigo/redis/scan_test.go index d364dff4..a68cd8ee 100644 --- a/vendor/github.com/garyburd/redigo/redis/scan_test.go +++ b/vendor/github.com/gomodule/redigo/redis/scan_test.go @@ -19,10 +19,32 @@ import ( "math" "reflect" "testing" + "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) +type durationScan struct { + time.Duration `redis:"sd"` +} + +func (t *durationScan) RedisScan(src interface{}) (err error) { + if t == nil { + return fmt.Errorf("nil pointer") + } + switch src := src.(type) { + case string: + t.Duration, err = time.ParseDuration(src) + case []byte: + t.Duration, err = time.ParseDuration(string(src)) + case int64: + t.Duration = time.Duration(src) + default: + err = fmt.Errorf("cannot convert from %T to %T", src, t) + } + return err +} + var scanConversionTests = []struct { src interface{} dest interface{} @@ -59,6 +81,11 @@ var scanConversionTests = []struct { {[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}}, {[]interface{}{[]byte("1")}, []byte{1}}, {[]interface{}{[]byte("1")}, []bool{true}}, + {"1m", durationScan{Duration: time.Minute}}, + {[]byte("1m"), durationScan{Duration: time.Minute}}, + {time.Minute.Nanoseconds(), durationScan{Duration: time.Minute}}, + {[]interface{}{[]byte("1m")}, []durationScan{{Duration: time.Minute}}}, + {[]interface{}{[]byte("1m")}, []*durationScan{{Duration: time.Minute}}}, } func TestScanConversion(t *testing.T) { @@ -86,6 +113,8 @@ var scanConversionErrorTests = []struct { {int64(-1), byte(0)}, {[]byte("junk"), false}, {redis.Error("blah"), false}, + {redis.Error("blah"), durationScan{Duration: time.Minute}}, + {"invalid", durationScan{Duration: time.Minute}}, } func TestScanConversionError(t *testing.T) { @@ -158,6 +187,8 @@ type s1 struct { Bt bool Bf bool s0 + Sd durationScan `redis:"sd"` + Sdp *durationScan `redis:"sdp"` } var scanStructTests = []struct { @@ -166,8 +197,35 @@ var scanStructTests = []struct { value interface{} }{ {"basic", - []string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"}, - &s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}}, + []string{ + "i", "-1234", + "u", "5678", + "s", "hello", + "p", "world", + "b", "t", + "Bt", "1", + "Bf", "0", + "X", "123", + "y", "456", + "sd", "1m", + "sdp", "1m", + }, + &s1{ + I: -1234, + U: 5678, + S: "hello", + P: []byte("world"), + B: true, + Bt: true, + Bf: false, + s0: s0{X: 123, Y: 456}, + Sd: durationScan{Duration: time.Minute}, + Sdp: &durationScan{Duration: time.Minute}, + }, + }, + {"absent values", + []string{}, + &s1{}, }, } @@ -264,7 +322,7 @@ var scanSliceTests = []struct { []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, nil, true, - []*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, + []*struct{ A, B string }{{A: "a1", B: "b1"}, {A: "a2", B: "b2"}}, }, { []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, @@ -362,17 +420,11 @@ var argsTests = []struct { }, {"struct omitempty", redis.Args{}.AddFlat(&struct { - I int `redis:"i,omitempty"` - U uint `redis:"u,omitempty"` - S string `redis:"s,omitempty"` - P []byte `redis:"p,omitempty"` - M map[string]string `redis:"m,omitempty"` - Bt bool `redis:"Bt,omitempty"` - Bf bool `redis:"Bf,omitempty"` + Sdp *durationArg `redis:"Sdp,omitempty"` }{ - 0, 0, "", []byte{}, map[string]string{}, true, false, + nil, }), - redis.Args{"Bt", true}, + redis.Args{}, }, } diff --git a/vendor/github.com/garyburd/redigo/redis/script.go b/vendor/github.com/gomodule/redigo/redis/script.go similarity index 97% rename from vendor/github.com/garyburd/redigo/redis/script.go rename to vendor/github.com/gomodule/redigo/redis/script.go index 78605a90..0ef1c821 100644 --- a/vendor/github.com/garyburd/redigo/redis/script.go +++ b/vendor/github.com/gomodule/redigo/redis/script.go @@ -55,6 +55,11 @@ func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { return args } +// Hash returns the script hash. +func (s *Script) Hash() string { + return s.hash +} + // Do evaluates the script. Under the covers, Do optimistically evaluates the // script using the EVALSHA command. If the command fails because the script is // not loaded, then Do evaluates the script using the EVAL command (thus diff --git a/vendor/github.com/garyburd/redigo/redis/script_test.go b/vendor/github.com/gomodule/redigo/redis/script_test.go similarity index 98% rename from vendor/github.com/garyburd/redigo/redis/script_test.go rename to vendor/github.com/gomodule/redigo/redis/script_test.go index af282415..388e167f 100644 --- a/vendor/github.com/garyburd/redigo/redis/script_test.go +++ b/vendor/github.com/gomodule/redigo/redis/script_test.go @@ -20,7 +20,7 @@ import ( "testing" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) var ( diff --git a/vendor/github.com/garyburd/redigo/redis/test_test.go b/vendor/github.com/gomodule/redigo/redis/test_test.go similarity index 83% rename from vendor/github.com/garyburd/redigo/redis/test_test.go rename to vendor/github.com/gomodule/redigo/redis/test_test.go index 7240fa1f..5c758486 100644 --- a/vendor/github.com/garyburd/redigo/redis/test_test.go +++ b/vendor/github.com/gomodule/redigo/redis/test_test.go @@ -38,6 +38,7 @@ var ( ErrNegativeInt = errNegativeInt serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary") + serverAddress = flag.String("redis-address", "127.0.0.1", "The address of the server") serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers") serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`") serverLog = ioutil.Discard @@ -96,7 +97,8 @@ func (s *Server) watch(r io.Reader, ready chan error) { text = scn.Text() fmt.Fprintf(serverLog, "%s\n", text) if !listening { - if strings.Contains(text, "The server is now ready to accept connections on port") { + if strings.Contains(text, " * Ready to accept connections") || + strings.Contains(text, " * The server is now ready to accept connections on port") { listening = true ready <- nil } @@ -125,28 +127,32 @@ func stopDefaultServer() { } } -// startDefaultServer starts the default server if not already running. -func startDefaultServer() error { +// DefaultServerAddr starts the test server if not already started and returns +// the address of that server. +func DefaultServerAddr() (string, error) { defaultServerMu.Lock() defer defaultServerMu.Unlock() + addr := fmt.Sprintf("%v:%d", *serverAddress, *serverBasePort) if defaultServer != nil || defaultServerErr != nil { - return defaultServerErr + return addr, defaultServerErr } defaultServer, defaultServerErr = NewServer( "default", "--port", strconv.Itoa(*serverBasePort), + "--bind", *serverAddress, "--save", "", "--appendonly", "no") - return defaultServerErr + return addr, defaultServerErr } // DialDefaultServer starts the test server if not already started and dials a // connection to the server. func DialDefaultServer() (Conn, error) { - if err := startDefaultServer(); err != nil { + addr, err := DefaultServerAddr() + if err != nil { return nil, err } - c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second)) + c, err := Dial("tcp", addr, DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second)) if err != nil { return nil, err } diff --git a/vendor/github.com/garyburd/redigo/redis/zpop_example_test.go b/vendor/github.com/gomodule/redigo/redis/zpop_example_test.go similarity index 98% rename from vendor/github.com/garyburd/redigo/redis/zpop_example_test.go rename to vendor/github.com/gomodule/redigo/redis/zpop_example_test.go index 1d86ee6c..f7702e03 100644 --- a/vendor/github.com/garyburd/redigo/redis/zpop_example_test.go +++ b/vendor/github.com/gomodule/redigo/redis/zpop_example_test.go @@ -16,7 +16,8 @@ package redis_test import ( "fmt" - "github.com/garyburd/redigo/redis" + + "github.com/gomodule/redigo/redis" ) // zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. diff --git a/vendor/github.com/garyburd/redigo/internal/commandinfo.go b/vendor/github.com/gomodule/redigo/redisx/commandinfo.go similarity index 58% rename from vendor/github.com/garyburd/redigo/internal/commandinfo.go rename to vendor/github.com/gomodule/redigo/redisx/commandinfo.go index 11e58425..b911cc4a 100644 --- a/vendor/github.com/garyburd/redigo/internal/commandinfo.go +++ b/vendor/github.com/gomodule/redigo/redisx/commandinfo.go @@ -12,32 +12,32 @@ // License for the specific language governing permissions and limitations // under the License. -package internal // import "github.com/garyburd/redigo/internal" +package redisx import ( "strings" ) const ( - WatchState = 1 << iota - MultiState - SubscribeState - MonitorState + connectionWatchState = 1 << iota + connectionMultiState + connectionSubscribeState + connectionMonitorState ) -type CommandInfo struct { - Set, Clear int +type commandInfo struct { + notMuxable bool } -var commandInfos = map[string]CommandInfo{ - "WATCH": {Set: WatchState}, - "UNWATCH": {Clear: WatchState}, - "MULTI": {Set: MultiState}, - "EXEC": {Clear: WatchState | MultiState}, - "DISCARD": {Clear: WatchState | MultiState}, - "PSUBSCRIBE": {Set: SubscribeState}, - "SUBSCRIBE": {Set: SubscribeState}, - "MONITOR": {Set: MonitorState}, +var commandInfos = map[string]commandInfo{ + "WATCH": {notMuxable: true}, + "UNWATCH": {notMuxable: true}, + "MULTI": {notMuxable: true}, + "EXEC": {notMuxable: true}, + "DISCARD": {notMuxable: true}, + "PSUBSCRIBE": {notMuxable: true}, + "SUBSCRIBE": {notMuxable: true}, + "MONITOR": {notMuxable: true}, } func init() { @@ -46,7 +46,7 @@ func init() { } } -func LookupCommandInfo(commandName string) CommandInfo { +func lookupCommandInfo(commandName string) commandInfo { if ci, ok := commandInfos[commandName]; ok { return ci } diff --git a/vendor/github.com/gomodule/redigo/redisx/commandinfo_test.go b/vendor/github.com/gomodule/redigo/redisx/commandinfo_test.go new file mode 100644 index 00000000..e7e869b1 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redisx/commandinfo_test.go @@ -0,0 +1,11 @@ +package redisx + +import "testing" + +func TestLookupCommandInfo(t *testing.T) { + for _, n := range []string{"watch", "WATCH", "wAtch"} { + if lookupCommandInfo(n) == (commandInfo{}) { + t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n) + } + } +} diff --git a/vendor/github.com/garyburd/redigo/redisx/connmux.go b/vendor/github.com/gomodule/redigo/redisx/connmux.go similarity index 96% rename from vendor/github.com/garyburd/redigo/redisx/connmux.go rename to vendor/github.com/gomodule/redigo/redisx/connmux.go index af2cced3..6ae1f9d1 100644 --- a/vendor/github.com/garyburd/redigo/redisx/connmux.go +++ b/vendor/github.com/gomodule/redigo/redisx/connmux.go @@ -18,8 +18,7 @@ import ( "errors" "sync" - "github.com/garyburd/redigo/internal" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) // ConnMux multiplexes one or more connections to a single underlying @@ -60,7 +59,7 @@ type muxConn struct { } func (c *muxConn) send(flush bool, cmd string, args ...interface{}) error { - if internal.LookupCommandInfo(cmd).Set != 0 { + if lookupCommandInfo(cmd).notMuxable { return errors.New("command not supported by mux pool") } p := c.p diff --git a/vendor/github.com/garyburd/redigo/redisx/connmux_test.go b/vendor/github.com/gomodule/redigo/redisx/connmux_test.go similarity index 91% rename from vendor/github.com/garyburd/redigo/redisx/connmux_test.go rename to vendor/github.com/gomodule/redigo/redisx/connmux_test.go index 9c3c8b16..e035a59f 100644 --- a/vendor/github.com/garyburd/redigo/redisx/connmux_test.go +++ b/vendor/github.com/gomodule/redigo/redisx/connmux_test.go @@ -19,13 +19,12 @@ import ( "sync" "testing" - "github.com/garyburd/redigo/internal/redistest" - "github.com/garyburd/redigo/redis" - "github.com/garyburd/redigo/redisx" + "github.com/gomodule/redigo/redis" + "github.com/gomodule/redigo/redisx" ) func TestConnMux(t *testing.T) { - c, err := redistest.Dial() + c, err := redisx.DialTest() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -57,7 +56,7 @@ func TestConnMux(t *testing.T) { } func TestConnMuxClose(t *testing.T) { - c, err := redistest.Dial() + c, err := redisx.DialTest() if err != nil { t.Fatalf("error connection to database, %v", err) } @@ -93,7 +92,7 @@ func TestConnMuxClose(t *testing.T) { func BenchmarkConn(b *testing.B) { b.StopTimer() - c, err := redistest.Dial() + c, err := redisx.DialTest() if err != nil { b.Fatalf("error connection to database, %v", err) } @@ -109,7 +108,7 @@ func BenchmarkConn(b *testing.B) { func BenchmarkConnMux(b *testing.B) { b.StopTimer() - c, err := redistest.Dial() + c, err := redisx.DialTest() if err != nil { b.Fatalf("error connection to database, %v", err) } @@ -130,7 +129,7 @@ func BenchmarkConnMux(b *testing.B) { func BenchmarkPool(b *testing.B) { b.StopTimer() - p := redis.Pool{Dial: redistest.Dial, MaxIdle: 1} + p := redis.Pool{Dial: redisx.DialTest, MaxIdle: 1} defer p.Close() // Fill the pool. @@ -155,7 +154,7 @@ const numConcurrent = 10 func BenchmarkConnMuxConcurrent(b *testing.B) { b.StopTimer() - c, err := redistest.Dial() + c, err := redisx.DialTest() if err != nil { b.Fatalf("error connection to database, %v", err) } @@ -186,7 +185,7 @@ func BenchmarkConnMuxConcurrent(b *testing.B) { func BenchmarkPoolConcurrent(b *testing.B) { b.StopTimer() - p := redis.Pool{Dial: redistest.Dial, MaxIdle: numConcurrent} + p := redis.Pool{Dial: redisx.DialTest, MaxIdle: numConcurrent} defer p.Close() // Fill the pool. @@ -224,7 +223,7 @@ func BenchmarkPoolConcurrent(b *testing.B) { func BenchmarkPipelineConcurrency(b *testing.B) { b.StopTimer() - c, err := redistest.Dial() + c, err := redisx.DialTest() if err != nil { b.Fatalf("error connection to database, %v", err) } diff --git a/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go b/vendor/github.com/gomodule/redigo/redisx/db_test.go similarity index 94% rename from vendor/github.com/garyburd/redigo/internal/redistest/testdb.go rename to vendor/github.com/gomodule/redigo/redisx/db_test.go index b6f205b7..ead64744 100644 --- a/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go +++ b/vendor/github.com/gomodule/redigo/redisx/db_test.go @@ -13,13 +13,13 @@ // under the License. // Package redistest contains utilities for writing Redigo tests. -package redistest +package redisx import ( "errors" "time" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" ) type testConn struct { @@ -41,7 +41,7 @@ func (t testConn) Close() error { // Dial dials the local Redis server and selects database 9. To prevent // stomping on real data, DialTestDB fails if database 9 contains data. The // returned connection flushes database 9 on close. -func Dial() (redis.Conn, error) { +func DialTest() (redis.Conn, error) { c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second) if err != nil { return nil, err diff --git a/vendor/github.com/garyburd/redigo/redisx/doc.go b/vendor/github.com/gomodule/redigo/redisx/doc.go similarity index 92% rename from vendor/github.com/garyburd/redigo/redisx/doc.go rename to vendor/github.com/gomodule/redigo/redisx/doc.go index 91653dbe..7c0023b7 100644 --- a/vendor/github.com/garyburd/redigo/redisx/doc.go +++ b/vendor/github.com/gomodule/redigo/redisx/doc.go @@ -14,4 +14,4 @@ // Package redisx contains experimental features for Redigo. Features in this // package may be modified or deleted at any time. -package redisx // import "github.com/garyburd/redigo/redisx" +package redisx