├── .travis.yml ├── Godeps └── Godeps.json ├── Makefile ├── README.md ├── main └── main.go ├── proxy ├── backendsession.go ├── connpool.go ├── crc16.go ├── crc16_test.go ├── dispatcher.go ├── filter.go ├── multirequest.go ├── proxy.go ├── request.go ├── session.go ├── session_test.go ├── slottable.go ├── slottable_test.go └── util.go ├── rcproxy.png ├── start_proxy.sh ├── tests ├── bench │ └── redis-benchmark.sh ├── loadrunner.go ├── random_migration.py ├── redis.conf.tmpl ├── redistrib │ ├── __init__.py │ ├── clusternode.py │ ├── command.py │ ├── console.py │ └── exceptions.py └── start_cluster.py └── vendor ├── github.com ├── artyom │ └── autoflags │ │ ├── LICENSE │ │ ├── README.md │ │ └── autoflags.go ├── collinmsn │ └── resp │ │ ├── LICENSE │ │ ├── README.md │ │ ├── README_en.md │ │ └── resp.go ├── davecgh │ └── go-spew │ │ ├── LICENSE │ │ └── spew │ │ ├── bypass.go │ │ ├── bypasssafe.go │ │ ├── common.go │ │ ├── config.go │ │ ├── doc.go │ │ ├── dump.go │ │ ├── format.go │ │ └── spew.go ├── garyburd │ └── redigo │ │ ├── internal │ │ └── commandinfo.go │ │ └── redis │ │ ├── conn.go │ │ ├── doc.go │ │ ├── log.go │ │ ├── pool.go │ │ ├── pubsub.go │ │ ├── redis.go │ │ ├── reply.go │ │ ├── scan.go │ │ └── script.go ├── ngaut │ └── logging │ │ ├── LICENSE │ │ ├── README.md │ │ └── log.go ├── pmezard │ └── go-difflib │ │ ├── LICENSE │ │ └── difflib │ │ └── difflib.go └── stretchr │ └── testify │ ├── LICENSE │ └── assert │ ├── assertion_forward.go │ ├── assertion_forward.go.tmpl │ ├── assertions.go │ ├── doc.go │ ├── errors.go │ ├── forward_assertions.go │ └── http_assertions.go ├── gopkg.in └── fatih │ └── pool.v2 │ ├── LICENSE │ ├── README.md │ ├── channel.go │ ├── conn.go │ └── pool.go └── vendor.json /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5.1 5 | 6 | script: 7 | - go get -u golang.org/x/tools/cmd/goimports 8 | - make 9 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/collinmsn/rcproxy", 3 | "GoVersion": "go1.5", 4 | "GodepVersion": "v60", 5 | "Packages": [ 6 | "./..." 7 | ], 8 | "Deps": [ 9 | { 10 | "ImportPath": "github.com/artyom/autoflags", 11 | "Rev": "8d4acb53349c5f11e663d75ad9d3e54f5ce17d3c" 12 | }, 13 | { 14 | "ImportPath": "github.com/collinmsn/resp", 15 | "Rev": "9485de4066e6f7eec81e47545b3d4522274cb13c" 16 | }, 17 | { 18 | "ImportPath": "github.com/davecgh/go-spew/spew", 19 | "Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" 20 | }, 21 | { 22 | "ImportPath": "gopkg.in/fatih/pool.v2", 23 | "Rev": "cba550ebf9bce999a02e963296d4bc7a486cb715" 24 | }, 25 | { 26 | "ImportPath": "github.com/garyburd/redigo/internal", 27 | "Rev": "785e0ca9319196b94fd61abd4c9eab5c11777377" 28 | }, 29 | { 30 | "ImportPath": "github.com/garyburd/redigo/redis", 31 | "Rev": "785e0ca9319196b94fd61abd4c9eab5c11777377" 32 | }, 33 | { 34 | "ImportPath": "github.com/ngaut/logging", 35 | "Rev": "f98f5f4cd523647678af6f1726c5b5a14c9193d0" 36 | }, 37 | { 38 | "ImportPath": "github.com/pmezard/go-difflib/difflib", 39 | "Rev": "792786c7400a136282c1664665ae0a8db921c6c2" 40 | }, 41 | { 42 | "ImportPath": "github.com/stretchr/testify/assert", 43 | "Comment": "v1.1.3-12-gc5d7a69", 44 | "Rev": "c5d7a69bf8a2c9c374798160849c071093e41dd1" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: server 3 | 4 | server: 5 | find ./ -name "*.go" | xargs goimports -w 6 | find ./ -name "*.go" | xargs gofmt -w 7 | @mkdir -p bin 8 | go build -v -o bin/rcproxy ./main 9 | 10 | clean: 11 | @rm -rf bin 12 | 13 | test: 14 | go test ./proxy/... -v 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rcproxy 2 | [![Build Status](https://travis-ci.org/collinmsn/rcproxy.svg)](https://travis-ci.org/collinmsn/rcproxy) 3 | ## Introduction 4 | 5 | RCProxy, short for Redis Cluster Proxy, is a redis proxy built on redis 3.0(see [redis cluster spec](http://redis.io/topics/cluster-spec "悬停显示")). It aims to be simple, robust and high performance. It caches the cluster topology and implements the MOVED and ASK redirection. Cluster topology is updated on backend server failure or MOVED redirection received. Client is totally transpanrent about the backend data migration. 6 | 7 | ## Getting started 8 | * Make sure Redis 3.0.0 RC or above has been installed in your PATH. Run 'redis-server --version' to check it. 9 | * Run 'python start_cluster.py' in tests directory to start the redis cluster 10 | * Run 'sh bootstrap.sh' in rcproxy to build rcproxy. Note that [godep](https://github.com/tools/godep) is required to solved dependencies. Otherwise, you can use go get to fetch missing packages. 11 | * Run 'sh start_proxy.sh' to run rcproxy. 12 | * Allmost done! Now you can play with it with redis-cli, or run tests/loadrunner.go and tests/random_migration.py to see how it handles slots migration properly, or run tests/bench/redis-benchmark.sh to benchmark it. 13 | 14 | ## Architecture 15 | ![](https://github.com/collinmsn/rcproxy/blob/master/rcproxy.png) 16 | 17 | Each client connection is wrapped with a session, which spawns two goroutines to read request from and write response to the client. Each session appends it's request to dispatcher's request queue, then dispatcher route request to the right task runner according key hash and slot table. Task runner sends requests to its backend server and read responses from it. 18 | Upon cluster topology changed, backend server will response MOVED or ASK error. These error is handled by session, by sending request to destination server directly. Session will trigger dispatcher to update slot info on MOVED error. When connection error is returned by task runner, session will trigger dispather to reload topology. 19 | 20 | ## Performance 21 | Benchmark it with tests/bench/redis-benchmark.sh 22 | 23 | ## Status 24 | Product ready 25 | 26 | ## Feedback 27 | Feedbacks and bug reports are highly appreciated. Contact me by 4130944@qq.com 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "math/rand" 6 | "net/http" 7 | _ "net/http/pprof" 8 | "os" 9 | "os/signal" 10 | "strings" 11 | "time" 12 | 13 | "github.com/artyom/autoflags" 14 | "github.com/collinmsn/rcproxy/proxy" 15 | log "github.com/ngaut/logging" 16 | ) 17 | 18 | var config = struct { 19 | //flag:"flagName,usage string" 20 | Addr string `flag:"addr, proxy serving addr"` 21 | DebugAddr string `flag:"debug-addr, proxy debug listen address for pprof and set log level, default not enabled"` 22 | StartupNodes string `flag:"startup-nodes, startup nodes used to query cluster topology"` 23 | ConnectTimeout time.Duration `flag:"connect-timeout, connect to backend timeout"` 24 | SlotsReloadInterval time.Duration `flag:"slots-reload-interval, slots reload interval"` 25 | LogLevel string `flag:"log-level, log level eg. debug, info, warn, error, fatal and panic"` 26 | LogFile string `flag:"log-file, log file path"` 27 | LogEveryN uint64 `flag:"log-every-n, output an access log for every N commands"` 28 | BackendIdleConnections int `flag:"backend-idle-connections, max number of idle connections for each backend server"` 29 | ReadPrefer int `flag:"read-prefer, where read command to send to, valid options are: 0: READ_PREFER_MASTER, 1: READ_PREFER_SLAVE, 2: READ_PREFER_SLAVE_IDC"` 30 | }{ 31 | Addr: "0.0.0.0:8088", 32 | DebugAddr: "", 33 | StartupNodes: "127.0.0.1:7001", 34 | ConnectTimeout: 250 * time.Millisecond, 35 | SlotsReloadInterval: 3 * time.Second, 36 | LogLevel: "info", 37 | LogFile: "rcproxy.log", 38 | LogEveryN: 100, 39 | BackendIdleConnections: 5, 40 | ReadPrefer: proxy.READ_PREFER_MASTER, 41 | } 42 | 43 | func handleSetLogLevel(w http.ResponseWriter, r *http.Request) { 44 | r.ParseForm() 45 | level := r.Form.Get("level") 46 | log.SetLevelByString(level) 47 | log.Info("set log level to ", level) 48 | w.Header().Set("Content-Type", "text/html") 49 | w.Write([]byte("OK")) 50 | } 51 | 52 | func initRandSeed() { 53 | rand.Seed(time.Now().UnixNano()) 54 | } 55 | 56 | func main() { 57 | initRandSeed() 58 | autoflags.Define(&config) 59 | flag.Parse() 60 | log.SetLevelByString(config.LogLevel) 61 | log.SetFlags(log.Ldate | log.Lshortfile | log.Lmicroseconds) 62 | if len(config.LogFile) != 0 { 63 | log.SetOutputByName(config.LogFile) 64 | log.SetHighlighting(false) 65 | log.SetRotateByDay() 66 | } 67 | if config.LogEveryN <= 0 { 68 | proxy.LogEveryN = 1 69 | } else { 70 | proxy.LogEveryN = config.LogEveryN 71 | } 72 | log.Info(config) 73 | sigChan := make(chan os.Signal) 74 | signal.Notify(sigChan, os.Interrupt, os.Kill) 75 | 76 | log.Infof("pid %d", os.Getpid()) 77 | if len(config.DebugAddr) != 0 { 78 | http.HandleFunc("/setloglevel", handleSetLogLevel) 79 | go func() { 80 | log.Fatal(http.ListenAndServe(config.DebugAddr, nil)) 81 | }() 82 | log.Infof("debug service listens on %s", config.DebugAddr) 83 | } 84 | 85 | // shuffle startup nodes 86 | startupNodes := strings.Split(config.StartupNodes, ",") 87 | for i := len(startupNodes) - 1; i > 0; i-- { 88 | j := rand.Intn(i + 1) 89 | startupNodes[i], startupNodes[j] = startupNodes[j], startupNodes[i] 90 | } 91 | 92 | connPool := proxy.NewConnPool(config.BackendIdleConnections, config.ConnectTimeout, config.ReadPrefer != proxy.READ_PREFER_MASTER) 93 | dispatcher := proxy.NewDefaultDispatcher(startupNodes, config.SlotsReloadInterval, connPool, config.ReadPrefer) 94 | if err := dispatcher.InitSlotTable(); err != nil { 95 | log.Fatal(err) 96 | } 97 | proxy := proxy.NewProxy(config.Addr, dispatcher, connPool) 98 | go proxy.Run() 99 | sig := <-sigChan 100 | log.Infof("terminated by %#v", sig) 101 | proxy.Exit() 102 | } 103 | -------------------------------------------------------------------------------- /proxy/backendsession.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bufio" 5 | "container/list" 6 | "errors" 7 | "net" 8 | "time" 9 | 10 | "fmt" 11 | 12 | "github.com/collinmsn/resp" 13 | log "github.com/ngaut/logging" 14 | "gopkg.in/fatih/pool.v2" 15 | ) 16 | 17 | const ( 18 | REQUEST_QUEUE_SIZE = 50000 19 | RESPONSE_QUEUE_SIZE = 20000 20 | ) 21 | 22 | var ( 23 | ERR_BACKEND_SESSION_OVERFLOW = errors.New("Backend session overflow") 24 | ERR_BACKEND_SESSION_EXITING = errors.New("Backend session exiting") 25 | ERR_BACKEND_SESSION_READER_EXITING = errors.New("Backend session reader exiting") 26 | ) 27 | 28 | type BackendSession interface { 29 | Schedule(req *Request) 30 | Start() 31 | Exit() 32 | } 33 | 34 | type DefaultBackendSession struct { 35 | addr string 36 | conn net.Conn 37 | requestQueue chan *Request 38 | responseQueue chan *resp.Object 39 | connPool *ConnPool 40 | r *bufio.Reader 41 | w *bufio.Writer 42 | inflight *list.List 43 | closed bool 44 | exitChan chan struct{} 45 | } 46 | 47 | func NewDefaultBackendSession(addr string, connPool *ConnPool) *DefaultBackendSession { 48 | return &DefaultBackendSession{ 49 | addr: addr, 50 | requestQueue: make(chan *Request, REQUEST_QUEUE_SIZE), 51 | responseQueue: make(chan *resp.Object, REQUEST_QUEUE_SIZE), 52 | connPool: connPool, 53 | inflight: list.New(), 54 | exitChan: make(chan struct{}), 55 | } 56 | } 57 | 58 | // Schedule adds request to backend-session's request queue 59 | // it will never block even if request queue is overflow 60 | func (s *DefaultBackendSession) Schedule(req *Request) { 61 | select { 62 | case s.requestQueue <- req: 63 | log.Debug("Backend session schedule") 64 | default: 65 | req.err = ERR_BACKEND_SESSION_OVERFLOW 66 | req.Done() 67 | } 68 | } 69 | 70 | func (s *DefaultBackendSession) Start() { 71 | if conn, err := s.connPool.GetConn(s.addr); err != nil { 72 | log.Error("failed to connect to backend", s.addr, err) 73 | } else { 74 | s.initRWConn(conn) 75 | } 76 | 77 | go s.writingLoop() 78 | go s.readingLoop(s.responseQueue) 79 | 80 | } 81 | 82 | // readingLoop 一直从连接中读取response object,如果出错则退出,通过关闭responseQueue通知writtingLoop 83 | func (s *DefaultBackendSession) readingLoop(responseQueue chan *resp.Object) { 84 | var err error 85 | defer func() { 86 | log.Error("exit reading loop", s.addr, err) 87 | close(responseQueue) 88 | }() 89 | 90 | if s.r == nil { 91 | return 92 | } 93 | // reading loop 94 | for { 95 | obj := resp.NewObject() 96 | if err = resp.ReadDataBytes(s.r, obj); err != nil { 97 | log.Error("Failed to read response object", s.addr, err) 98 | return 99 | } else { 100 | responseQueue <- obj 101 | } 102 | } 103 | } 104 | 105 | // writtingLoop只有在被要求退出时才会退出 106 | func (s *DefaultBackendSession) writingLoop() { 107 | var err error 108 | defer func() { 109 | // 退出前将inflight和request queue处理完 110 | s.cleanupInflight(ERR_BACKEND_SESSION_EXITING) 111 | s.cleanupRequestQueue(ERR_BACKEND_SESSION_EXITING) 112 | s.conn.Close() 113 | }() 114 | for { 115 | select { 116 | case <-s.exitChan: 117 | log.Info("exiting writing loop") 118 | return 119 | default: 120 | } 121 | 122 | if err != nil { 123 | err = s.tryRecover(err) 124 | if err != nil { 125 | continue 126 | } 127 | } 128 | 129 | select { 130 | case req := <-s.requestQueue: 131 | err = s.handleRequest(req) 132 | case obj, ok := <-s.responseQueue: 133 | if ok { 134 | s.handleResponse(obj) 135 | } else { 136 | err = ERR_BACKEND_SESSION_READER_EXITING 137 | } 138 | } 139 | } 140 | } 141 | 142 | func (s *DefaultBackendSession) initRWConn(conn net.Conn) { 143 | if s.conn != nil { 144 | s.conn.(*pool.PoolConn).MarkUnusable() 145 | s.conn.Close() 146 | } 147 | s.conn = conn 148 | s.r = bufio.NewReader(s.conn) 149 | s.w = bufio.NewWriter(s.conn) 150 | } 151 | 152 | // tryRecover首先将inflight request都ack一下,然后尝试重连后端,如果失败,则当request queue也进行清理 153 | // 如果重连成功, 重置responseQueue并重新启动reading goroutine 154 | // 因为重连成功时会关掉旧的连接,所以原来的reading loop会自然退出 155 | func (s *DefaultBackendSession) tryRecover(err error) error { 156 | s.cleanupInflight(err) 157 | if conn, err := s.connPool.GetConn(s.addr); err != nil { 158 | s.cleanupRequestQueue(err) 159 | log.Error(err) 160 | time.Sleep(100 * time.Millisecond) 161 | return err 162 | } else { 163 | log.Info("recover success", s.addr) 164 | s.initRWConn(conn) 165 | s.resetOutChannel() 166 | go s.readingLoop(s.responseQueue) 167 | } 168 | 169 | return nil 170 | } 171 | 172 | func (s *DefaultBackendSession) handleRequest(req *Request) error { 173 | return s.writeToBackend(req) 174 | } 175 | 176 | func (s *DefaultBackendSession) writeToBackend(req *Request) error { 177 | var err error 178 | // always put req into inflight list first 179 | s.inflight.PushBack(req) 180 | 181 | if s.w == nil { 182 | err = fmt.Errorf("Faild to connect to: %s", s.addr) 183 | log.Error(err) 184 | return err 185 | } 186 | buf := req.cmd.Format() 187 | if _, err = s.w.Write(buf); err != nil { 188 | log.Error(err) 189 | return err 190 | } 191 | if len(s.requestQueue) == 0 { 192 | err = s.w.Flush() 193 | if err != nil { 194 | log.Error("flush error", err) 195 | } 196 | } 197 | return err 198 | } 199 | 200 | func (s *DefaultBackendSession) cleanupInflight(err error) { 201 | for e := s.inflight.Front(); e != nil; e = e.Next() { 202 | req := e.Value.(*Request) 203 | req.err = err 204 | req.Done() 205 | } 206 | s.inflight.Init() 207 | } 208 | 209 | func (s *DefaultBackendSession) cleanupRequestQueue(err error) { 210 | for { 211 | select { 212 | case req, ok := <-s.requestQueue: 213 | if ok { 214 | req.err = err 215 | req.Done() 216 | } else { 217 | return 218 | } 219 | default: 220 | return 221 | } 222 | } 223 | } 224 | 225 | func (s *DefaultBackendSession) resetOutChannel() { 226 | s.responseQueue = make(chan *resp.Object, RESPONSE_QUEUE_SIZE) 227 | } 228 | 229 | func (s *DefaultBackendSession) Exit() { 230 | close(s.exitChan) 231 | } 232 | 233 | func (s *DefaultBackendSession) handleResponse(obj *resp.Object) { 234 | req := s.inflight.Remove(s.inflight.Front()).(*Request) 235 | req.obj = obj 236 | req.Done() 237 | } 238 | -------------------------------------------------------------------------------- /proxy/connpool.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "net" 7 | "sync" 8 | "time" 9 | 10 | "github.com/collinmsn/resp" 11 | log "github.com/ngaut/logging" 12 | "gopkg.in/fatih/pool.v2" 13 | ) 14 | 15 | type ConnPool struct { 16 | pools map[string]pool.Pool 17 | maxIdle int 18 | connTimeout time.Duration 19 | mu sync.Mutex 20 | sendReadOnly bool 21 | } 22 | 23 | func NewConnPool(maxIdle int, connTimeout time.Duration, sendReadOnly bool) *ConnPool { 24 | p := &ConnPool{ 25 | pools: make(map[string]pool.Pool), 26 | maxIdle: maxIdle, 27 | connTimeout: connTimeout, 28 | sendReadOnly: sendReadOnly, 29 | } 30 | return p 31 | } 32 | 33 | func (cp *ConnPool) GetConn(server string) (net.Conn, error) { 34 | var err error 35 | cp.mu.Lock() 36 | p := cp.pools[server] 37 | // create a pool is quite cheap and will not triggered many times 38 | if p == nil { 39 | p, err = pool.NewChannelPool(0, cp.maxIdle, func() (net.Conn, error) { 40 | return cp.postConnect(net.DialTimeout("tcp", server, cp.connTimeout)) 41 | }) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | cp.pools[server] = p 46 | } 47 | cp.mu.Unlock() 48 | return p.Get() 49 | } 50 | 51 | func (cp *ConnPool) postConnect(conn net.Conn, err error) (net.Conn, error) { 52 | if err != nil || !cp.sendReadOnly { 53 | return conn, err 54 | } 55 | defer func() { 56 | if err != nil { 57 | conn.Close() 58 | conn = nil 59 | } 60 | }() 61 | 62 | if _, err = conn.Write(REDIS_CMD_READ_ONLY); err != nil { 63 | log.Error("write READONLY failed", conn.RemoteAddr().String(), err) 64 | return conn, err 65 | } 66 | 67 | var data *resp.Data 68 | reader := bufio.NewReader(conn) 69 | data, err = resp.ReadData(reader) 70 | if err != nil { 71 | log.Error("read READONLY resp failed", conn.RemoteAddr().String(), err) 72 | return conn, err 73 | } 74 | 75 | if data.T == resp.T_Error { 76 | log.Error("READONLY resp is not OK", conn.RemoteAddr().String()) 77 | err = errors.New("post connect error: READONLY resp is not OK") 78 | } 79 | return conn, err 80 | } 81 | 82 | func (cp *ConnPool) Remove(server string) { 83 | cp.mu.Lock() 84 | defer cp.mu.Unlock() 85 | p := cp.pools[server] 86 | if p != nil { 87 | p.Close() 88 | delete(cp.pools, server) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /proxy/crc16.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | var table = [256]uint16{ 4 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 5 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 6 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 7 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 8 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 9 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 10 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 11 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 12 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 13 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 14 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 15 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 16 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 17 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 18 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 19 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 20 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 21 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 22 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 23 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 24 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 25 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 26 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 27 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 28 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 29 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 30 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 31 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 32 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 33 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 34 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 35 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, 36 | } 37 | 38 | // CRC16 returns checksum for a given set of bytes based on the crc algorithm 39 | // defined for hashing redis keys in a cluster setup 40 | func CRC16(buf []byte) uint16 { 41 | crc := uint16(0) 42 | for _, b := range buf { 43 | index := byte(crc>>8) ^ b 44 | crc = (crc << 8) ^ table[index] 45 | } 46 | return crc 47 | } 48 | -------------------------------------------------------------------------------- /proxy/crc16_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "crypto/rand" 5 | "testing" 6 | ) 7 | 8 | func benchmarkCRC16ForNBytes(b *testing.B, numBytes int) { 9 | buf := make([]byte, numBytes) 10 | _, err := rand.Read(buf) 11 | if err != nil { 12 | b.Fatal(err) 13 | } 14 | b.ResetTimer() 15 | for i := 0; i < b.N; i++ { 16 | CRC16(buf) 17 | } 18 | } 19 | 20 | func BenchmarkCRC16For16Bytes(b *testing.B) { 21 | benchmarkCRC16ForNBytes(b, 16) 22 | } 23 | 24 | func BenchmarkCRC16For32Bytes(b *testing.B) { 25 | benchmarkCRC16ForNBytes(b, 32) 26 | } 27 | 28 | func BenchmarkCRC16For64Bytes(b *testing.B) { 29 | benchmarkCRC16ForNBytes(b, 64) 30 | } 31 | 32 | func BenchmarkCRC16For128Bytes(b *testing.B) { 33 | benchmarkCRC16ForNBytes(b, 128) 34 | } 35 | -------------------------------------------------------------------------------- /proxy/dispatcher.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "bufio" 8 | 9 | "math/rand" 10 | "strings" 11 | 12 | "errors" 13 | 14 | "github.com/collinmsn/resp" 15 | log "github.com/ngaut/logging" 16 | "gopkg.in/fatih/pool.v2" 17 | ) 18 | 19 | const ( 20 | // write commands always go to master 21 | // read from master 22 | READ_PREFER_MASTER = iota 23 | // read from slave if possible 24 | READ_PREFER_SLAVE 25 | // read from slave in the same idc if possible 26 | READ_PREFER_SLAVE_IDC 27 | ) 28 | 29 | var ( 30 | REDIS_CMD_CLUSTER_SLOTS []byte 31 | REDIS_CMD_READ_ONLY []byte 32 | errEmptyClusterSlots = errors.New("empty cluster slots") 33 | slotNotCoveredRespObject = resp.NewObjectFromData( 34 | &resp.Data{ 35 | T: resp.T_Error, 36 | String: []byte("slot is not covered")}) 37 | ) 38 | 39 | func init() { 40 | cmd, _ := resp.NewCommand("CLUSTER", "SLOTS") 41 | REDIS_CMD_CLUSTER_SLOTS = cmd.Format() 42 | cmd, _ = resp.NewCommand("READONLY") 43 | REDIS_CMD_READ_ONLY = cmd.Format() 44 | } 45 | 46 | // Dispatcher routes requests from all clients to the right backend 47 | // it also maintains the slot table 48 | type Dispatcher interface { 49 | InitSlotTable() error 50 | Run() 51 | Schedule(req *Request) 52 | TriggerReloadSlots() 53 | } 54 | 55 | type DefaultDispatcher struct { 56 | startupNodes []string 57 | slotReloadInterval time.Duration 58 | connPool *ConnPool 59 | readPrefer int 60 | 61 | slotTable *SlotTable 62 | requestChan chan *Request 63 | backends map[string]BackendSession 64 | // notify slots changed 65 | slotInfoChan chan []*SlotInfo 66 | slotReloadChan chan struct{} 67 | } 68 | 69 | func NewDefaultDispatcher(startupNodes []string, slotReloadInterval time.Duration, connPool *ConnPool, readPrefer int) *DefaultDispatcher { 70 | d := &DefaultDispatcher{ 71 | startupNodes: startupNodes, 72 | slotTable: NewSlotTable(), 73 | slotReloadInterval: slotReloadInterval, 74 | requestChan: make(chan *Request, 10000), 75 | connPool: connPool, 76 | backends: make(map[string]BackendSession), 77 | slotInfoChan: make(chan []*SlotInfo), 78 | slotReloadChan: make(chan struct{}, 1), 79 | readPrefer: readPrefer, 80 | } 81 | return d 82 | } 83 | 84 | func (d *DefaultDispatcher) InitSlotTable() error { 85 | if slotInfos, err := d.reloadTopology(); err != nil { 86 | return err 87 | } else { 88 | for _, si := range slotInfos { 89 | d.slotTable.SetSlotInfo(si) 90 | } 91 | } 92 | return nil 93 | } 94 | 95 | func (d *DefaultDispatcher) Run() { 96 | go d.slotsReloadLoop() 97 | for { 98 | select { 99 | case req, ok := <-d.requestChan: 100 | // dispatch req 101 | if !ok { 102 | log.Info("exit dispatch loop") 103 | return 104 | } 105 | var server string 106 | if req.readOnly { 107 | server = d.slotTable.ReadServer(req.slot) 108 | } else { 109 | server = d.slotTable.WriteServer(req.slot) 110 | } 111 | taskRunner, ok := d.backends[server] 112 | if !ok { 113 | if server == "" { 114 | // slot is not covered 115 | req.obj = slotNotCoveredRespObject 116 | req.Done() 117 | continue 118 | } else { 119 | log.Info("create task runner", server) 120 | taskRunner = NewDefaultBackendSession(server, d.connPool) 121 | taskRunner.Start() 122 | d.backends[server] = taskRunner 123 | } 124 | } 125 | taskRunner.Schedule(req) 126 | case info := <-d.slotInfoChan: 127 | d.handleSlotInfoChanged(info) 128 | } 129 | } 130 | } 131 | 132 | // remove unused task runner 133 | func (d *DefaultDispatcher) handleSlotInfoChanged(slotInfos []*SlotInfo) { 134 | newServers := make(map[string]bool) 135 | for _, si := range slotInfos { 136 | d.slotTable.SetSlotInfo(si) 137 | newServers[si.write] = true 138 | for _, read := range si.read { 139 | newServers[read] = true 140 | } 141 | } 142 | 143 | for server, tr := range d.backends { 144 | if _, ok := newServers[server]; !ok { 145 | log.Info("exit unused task runner", server) 146 | tr.Exit() 147 | delete(d.backends, server) 148 | } 149 | } 150 | } 151 | 152 | func (d *DefaultDispatcher) Schedule(req *Request) { 153 | d.requestChan <- req 154 | log.Debug("add to reqCh") 155 | } 156 | 157 | // wait for the slot reload chan and reload cluster topology 158 | // at most every slotReloadInterval 159 | // it also reload topology at a relative long periodic interval 160 | func (d *DefaultDispatcher) slotsReloadLoop() { 161 | periodicReloadInterval := 300 * time.Second 162 | for { 163 | select { 164 | case <-time.After(d.slotReloadInterval): 165 | select { 166 | case _, ok := <-d.slotReloadChan: 167 | if !ok { 168 | log.Infof("exit reload slot table loop") 169 | return 170 | } 171 | log.Infof("request reload triggered") 172 | if slotInfos, err := d.reloadTopology(); err != nil { 173 | log.Errorf("reload slot table failed") 174 | } else { 175 | d.slotInfoChan <- slotInfos 176 | } 177 | case <-time.After(periodicReloadInterval): 178 | log.Infof("periodic reload triggered") 179 | if slotInfos, err := d.reloadTopology(); err != nil { 180 | log.Errorf("reload slot table failed") 181 | } else { 182 | d.slotInfoChan <- slotInfos 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | // request "CLUSTER SLOTS" to retrieve the cluster topology 190 | // try each start up nodes until the first success one 191 | func (d *DefaultDispatcher) reloadTopology() (slotInfos []*SlotInfo, err error) { 192 | log.Info("reload slot table") 193 | indexes := rand.Perm(len(d.startupNodes)) 194 | for _, index := range indexes { 195 | if slotInfos, err = d.doReload(d.startupNodes[index]); err == nil { 196 | break 197 | } 198 | } 199 | return 200 | } 201 | 202 | /** 203 | 获取cluster slots信息,并利用cluster nodes信息来将failed的slave过滤掉 204 | */ 205 | func (d *DefaultDispatcher) doReload(server string) (slotInfos []*SlotInfo, err error) { 206 | var conn net.Conn 207 | conn, err = d.connPool.GetConn(server) 208 | if err != nil { 209 | log.Error(server, err) 210 | return 211 | } else { 212 | log.Infof("query cluster slots from %s", server) 213 | } 214 | defer func() { 215 | if err != nil { 216 | conn.(*pool.PoolConn).MarkUnusable() 217 | } 218 | conn.Close() 219 | }() 220 | _, err = conn.Write(REDIS_CMD_CLUSTER_SLOTS) 221 | if err != nil { 222 | log.Errorf("write cluster slots error", server, err) 223 | return 224 | } 225 | r := bufio.NewReader(conn) 226 | var data *resp.Data 227 | data, err = resp.ReadData(r) 228 | if err != nil { 229 | log.Error(server, err) 230 | return 231 | } 232 | if data.T == resp.T_Error { 233 | err = errors.New(string(data.Format())) 234 | return 235 | } 236 | if len(data.Array) == 0 { 237 | err = errEmptyClusterSlots 238 | return 239 | } 240 | slotInfos = make([]*SlotInfo, 0, len(data.Array)) 241 | for _, info := range data.Array { 242 | slotInfos = append(slotInfos, NewSlotInfo(info)) 243 | } 244 | 245 | for _, si := range slotInfos { 246 | if d.readPrefer == READ_PREFER_MASTER { 247 | si.read = []string{si.write} 248 | } else if d.readPrefer == READ_PREFER_SLAVE || d.readPrefer == READ_PREFER_SLAVE_IDC { 249 | localIPPrefix := LocalIP() 250 | if len(localIPPrefix) > 0 { 251 | segments := strings.SplitN(localIPPrefix, ".", 3) 252 | localIPPrefix = strings.Join(segments[:2], ".") 253 | localIPPrefix += "." 254 | } 255 | var readNodes []string 256 | for _, node := range si.read { 257 | if d.readPrefer == READ_PREFER_SLAVE_IDC { 258 | // ips are regarded as in the same idc if they have the same first two segments, eg 10.4.x.x 259 | if !strings.HasPrefix(node, localIPPrefix) { 260 | log.Infof("filter %s by read prefer slave idc", node) 261 | continue 262 | } 263 | } 264 | readNodes = append(readNodes, node) 265 | } 266 | if len(readNodes) == 0 { 267 | readNodes = []string{si.write} 268 | } 269 | si.read = readNodes 270 | } 271 | } 272 | return 273 | } 274 | 275 | // schedule a reload task 276 | // this call is inherently throttled, so that multiple clients can call it at 277 | // the same time and it will only actually occur once 278 | func (d *DefaultDispatcher) TriggerReloadSlots() { 279 | select { 280 | case d.slotReloadChan <- struct{}{}: 281 | default: 282 | } 283 | } 284 | func (d *DefaultDispatcher) Exit() { 285 | close(d.requestChan) 286 | } 287 | -------------------------------------------------------------------------------- /proxy/filter.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "github.com/collinmsn/resp" 5 | ) 6 | 7 | const ( 8 | CMD_FLAG_READONLY = 1 << iota 9 | CMD_FLAG_BLACK 10 | ) 11 | 12 | /** 13 | "b" stands for blacklist command 14 | "r" stands for readonly command 15 | */ 16 | var cmdTable = [][2]string{ 17 | {"ASKING", "b"}, 18 | {"AUTH", "b"}, 19 | {"BGREWRITEAOF", "b"}, 20 | {"BGSAVE", "b"}, 21 | {"BITCOUNT", "r"}, 22 | {"BITOP", "b"}, 23 | {"BITPOS", "r"}, 24 | {"BLPOP", "b"}, 25 | {"BRPOP", "b"}, 26 | {"BRPOPLPUSH", "b"}, 27 | {"CLIENT", "b"}, 28 | {"CLUSTER", "b"}, 29 | {"COMMAND", "r"}, 30 | {"CONFIG", "b"}, 31 | {"DBSIZE", "b"}, 32 | {"DEBUG", "b"}, 33 | {"DISCARD", "b"}, 34 | {"DUMP", "r"}, 35 | {"ECHO", "r"}, 36 | {"EXEC", "b"}, 37 | {"EXISTS", "r"}, 38 | {"FLUSHALL", "b"}, 39 | {"FLUSHDB", "b"}, 40 | {"GET", "r"}, 41 | {"GETBIT", "r"}, 42 | {"GETRANGE", "r"}, 43 | {"HEXISTS", "r"}, 44 | {"HGET", "r"}, 45 | {"HGETALL", "r"}, 46 | {"HKEYS", "r"}, 47 | {"HLEN", "r"}, 48 | {"HMGET", "r"}, 49 | {"HSCAN", "r"}, 50 | {"HVALS", "r"}, 51 | {"INFO", "b"}, 52 | {"KEYS", "b"}, 53 | {"LASTSAVE", "b"}, 54 | {"LATENCY", "r"}, 55 | {"LINDEX", "r"}, 56 | {"LLEN", "r"}, 57 | {"LRANGE", "r"}, 58 | {"MGET", "r"}, 59 | {"MIGRATE", "b"}, 60 | {"MONITOR", "b"}, 61 | {"MOVE", "b"}, 62 | {"MSETNX", "b"}, 63 | {"MULTI", "b"}, 64 | {"OBJECT", "b"}, 65 | {"PFCOUNT", "r"}, 66 | {"PFSELFTEST", "r"}, 67 | {"PING", "b"}, 68 | {"PSUBSCRIBE", "b"}, 69 | {"PSYNC", "r"}, 70 | {"PTTL", "r"}, 71 | {"PUBLISH", "b"}, 72 | {"PUBSUB", "r"}, 73 | {"PUNSUBSCRIBE", "b"}, 74 | {"RANDOMKEY", "b"}, 75 | {"READONLY", "r"}, 76 | {"READWRITE", "r"}, 77 | {"RENAME", "b"}, 78 | {"RENAMENX", "b"}, 79 | {"REPLCONF", "r"}, 80 | {"SAVE", "b"}, 81 | {"SCAN", "b"}, 82 | {"SCARD", "r"}, 83 | {"SCRIPT", "b"}, 84 | {"SDIFF", "r"}, 85 | {"SELECT", "b"}, 86 | {"SHUTDOWN", "b"}, 87 | {"SINTER", "r"}, 88 | {"SISMEMBER", "r"}, 89 | {"SLAVEOF", "b"}, 90 | {"SLOWLOG", "b"}, 91 | {"SMEMBERS", "r"}, 92 | {"SRANDMEMBER", "r"}, 93 | {"SSCAN", "r"}, 94 | {"STRLEN", "r"}, 95 | {"SUBSCRIBE", "b"}, 96 | {"SUBSTR", "r"}, 97 | {"SUNION", "r"}, 98 | {"SYNC", "b"}, 99 | {"TIME", "b"}, 100 | {"TTL", "r"}, 101 | {"TYPE", "r"}, 102 | {"UNSUBSCRIBE", "b"}, 103 | {"UNWATCH", "b"}, 104 | {"WAIT", "r"}, 105 | {"WATCH", "b"}, 106 | {"ZCARD", "r"}, 107 | {"ZCOUNT", "r"}, 108 | {"ZLEXCOUNT", "r"}, 109 | {"ZRANGE", "r"}, 110 | {"ZRANGEBYLEX", "r"}, 111 | {"ZRANGEBYSCORE", "r"}, 112 | {"ZRANK", "r"}, 113 | {"ZREVRANGE", "r"}, 114 | {"ZREVRANGEBYLEX", "r"}, 115 | {"ZREVRANGEBYSCORE", "r"}, 116 | {"ZREVRANK", "r"}, 117 | {"ZSCAN", "r"}, 118 | {"ZSCORE", "r"}, 119 | } 120 | 121 | var cmdFlagMap = make(map[string]int) 122 | 123 | func init() { 124 | for _, row := range cmdTable { 125 | if row[1] == "b" { 126 | cmdFlagMap[row[0]] = CMD_FLAG_BLACK 127 | } else if row[1] == "r" { 128 | cmdFlagMap[row[0]] = CMD_FLAG_READONLY 129 | } 130 | } 131 | } 132 | 133 | func CmdFlag(cmd *resp.Command) int { 134 | return cmdFlagMap[cmd.Name()] 135 | } 136 | -------------------------------------------------------------------------------- /proxy/multirequest.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | 7 | "github.com/collinmsn/resp" 8 | log "github.com/ngaut/logging" 9 | ) 10 | 11 | const ( 12 | MGET = iota 13 | MSET 14 | DEL 15 | ) 16 | 17 | var ( 18 | OK_DATA *resp.Data 19 | ) 20 | 21 | func init() { 22 | OK_DATA = &resp.Data{ 23 | T: resp.T_SimpleString, 24 | String: []byte("OK"), 25 | } 26 | } 27 | 28 | /* 29 | multi key cmd被拆分成numKeys个子请求按普通的pipeline request发送,最后在写出response时进行合并 30 | 当最后一个子请求的response到来时,整个multi key cmd完成,拼接最终response并写出 31 | 32 | 只要有一个子请求失败,都认定整个请求失败 33 | 多个子请求共享一个request sequence number 34 | 35 | 请求的失败包含两种类型:1、网络失败,比如读取超时,2,请求错误,比如本来该在A机器上,请求到了B机器上,表现为response type为error 36 | */ 37 | type MultiRequest struct { 38 | cmd *resp.Command 39 | cmdType int 40 | numSubCmds int 41 | numPendingSubCmds int 42 | subCmdRsps []*Request 43 | } 44 | 45 | func NewMultiRequest(cmd *resp.Command, numSubCmds int) *MultiRequest { 46 | mc := &MultiRequest{ 47 | cmd: cmd, 48 | numSubCmds: numSubCmds, 49 | numPendingSubCmds: numSubCmds, 50 | } 51 | switch cmd.Name() { 52 | case "MGET": 53 | mc.cmdType = MGET 54 | case "MSET": 55 | mc.cmdType = MSET 56 | case "DEL": 57 | mc.cmdType = DEL 58 | default: 59 | panic("not multi key command") 60 | } 61 | mc.subCmdRsps = make([]*Request, numSubCmds) 62 | return mc 63 | } 64 | 65 | func (mc *MultiRequest) OnSubCmdFinished(req *Request) { 66 | mc.subCmdRsps[req.subSeq] = req 67 | mc.numPendingSubCmds-- 68 | } 69 | 70 | func (mc *MultiRequest) Finished() bool { 71 | return mc.numPendingSubCmds == 0 72 | } 73 | 74 | func (mc *MultiRequest) CoalesceRsp() *resp.Object { 75 | var rsp *resp.Data 76 | switch mc.CmdType() { 77 | case MGET: 78 | rsp = &resp.Data{T: resp.T_Array, Array: make([]*resp.Data, mc.numSubCmds)} 79 | case MSET: 80 | rsp = OK_DATA 81 | case DEL: 82 | rsp = &resp.Data{T: resp.T_Integer} 83 | default: 84 | panic("invalid multi key cmd name") 85 | } 86 | for i, subReq := range mc.subCmdRsps { 87 | if subReq.err != nil { 88 | rsp = &resp.Data{T: resp.T_Error, String: []byte(subReq.err.Error())} 89 | break 90 | } 91 | reader := bufio.NewReader(bytes.NewReader(subReq.obj.Raw())) 92 | data, err := resp.ReadData(reader) 93 | if err != nil { 94 | log.Errorf("re-parse response err=%s", err) 95 | rsp = &resp.Data{T: resp.T_Error, String: []byte(err.Error())} 96 | break 97 | } 98 | if data.T == resp.T_Error { 99 | rsp = data 100 | break 101 | } 102 | switch mc.CmdType() { 103 | case MGET: 104 | rsp.Array[i] = data 105 | case MSET: 106 | case DEL: 107 | rsp.Integer += data.Integer 108 | default: 109 | panic("invalid multi key cmd name") 110 | } 111 | } 112 | return resp.NewObjectFromData(rsp) 113 | } 114 | 115 | func (mc *MultiRequest) CmdType() int { 116 | return mc.cmdType 117 | } 118 | 119 | func IsMultiCmd(cmd *resp.Command) (multiKey bool, numKeys int) { 120 | multiKey = true 121 | switch cmd.Name() { 122 | case "MGET": 123 | numKeys = len(cmd.Args) - 1 124 | case "MSET": 125 | numKeys = (len(cmd.Args) - 1) / 2 126 | case "DEL": 127 | numKeys = len(cmd.Args) - 1 128 | default: 129 | multiKey = false 130 | } 131 | return 132 | } 133 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | 6 | log "github.com/ngaut/logging" 7 | ) 8 | 9 | var ( 10 | LogEveryN uint64 = 100 11 | accessLogCount uint64 12 | ) 13 | 14 | type Proxy struct { 15 | addr string 16 | dispatcher Dispatcher 17 | connPool *ConnPool 18 | exitChan chan struct{} 19 | } 20 | 21 | func NewProxy(addr string, dispatcher Dispatcher, connPool *ConnPool) *Proxy { 22 | p := &Proxy{ 23 | addr: addr, 24 | dispatcher: dispatcher, 25 | connPool: connPool, 26 | exitChan: make(chan struct{}), 27 | } 28 | return p 29 | } 30 | 31 | func (p *Proxy) Run() { 32 | tcpAddr, err := net.ResolveTCPAddr("tcp", p.addr) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | listener, err := net.ListenTCP("tcp", tcpAddr) 38 | if err != nil { 39 | log.Fatal(err) 40 | } else { 41 | log.Info("proxy listens on", p.addr) 42 | } 43 | defer listener.Close() 44 | 45 | go p.dispatcher.Run() 46 | 47 | for { 48 | conn, err := listener.AcceptTCP() 49 | if err != nil { 50 | log.Error(err) 51 | continue 52 | } 53 | log.Info("accept client", conn.RemoteAddr()) 54 | go p.handleConnection(conn) 55 | } 56 | } 57 | 58 | func (p *Proxy) Exit() { 59 | close(p.exitChan) 60 | } 61 | 62 | func (p *Proxy) handleConnection(conn net.Conn) { 63 | io := NewClientSessionReadWriter(conn) 64 | session := NewSession(io, p.connPool, p.dispatcher) 65 | session.Run() 66 | } 67 | -------------------------------------------------------------------------------- /proxy/request.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/collinmsn/resp" 7 | ) 8 | 9 | type Request struct { 10 | // if request has finished 11 | *sync.WaitGroup 12 | // request command 13 | cmd *resp.Command 14 | // response object 15 | obj *resp.Object 16 | // response error 17 | err error 18 | // if it is readOnly command 19 | readOnly bool 20 | // key slot 21 | slot int 22 | // for multi key command 23 | parentCmd *MultiRequest 24 | // sub sequence number for multi key command 25 | subSeq int 26 | } 27 | -------------------------------------------------------------------------------- /proxy/session.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "sync/atomic" 11 | 12 | "gopkg.in/fatih/pool.v2" 13 | 14 | "fmt" 15 | 16 | "github.com/collinmsn/resp" 17 | log "github.com/ngaut/logging" 18 | ) 19 | 20 | const ( 21 | CMD_NAME_EVAL = "EVAL" 22 | CMD_NAME_EVALSHA = "EVALSHA" 23 | CMD_EVAL_KEY_POS = 3 24 | ) 25 | 26 | var ( 27 | MOVED = []byte("-MOVED") 28 | ASK = []byte("-ASK") 29 | ASK_CMD_BYTES = []byte("ASKING\r\n") 30 | BLACK_CMD_ERR = []byte("unsupported command") 31 | BLACK_CMD_RESP_DATA = &resp.Data{T: resp.T_Error, String: BLACK_CMD_ERR} 32 | BLACK_CMD_RESP_OBJECT = resp.NewObjectFromData(BLACK_CMD_RESP_DATA) 33 | ) 34 | 35 | type ClientReadWriter interface { 36 | ReadCommand() (*resp.Command, error) 37 | WriteObject(*resp.Object) error 38 | Close() error 39 | RemoteAddr() net.Addr 40 | } 41 | 42 | /** 43 | * SessionReadWriter负责client端读写, 44 | * 写出没有使用buffer io 45 | */ 46 | type ClientSessionReadWriter struct { 47 | net.Conn 48 | reader *bufio.Reader 49 | } 50 | 51 | func NewClientSessionReadWriter(conn net.Conn) *ClientSessionReadWriter { 52 | return &ClientSessionReadWriter{ 53 | Conn: conn, 54 | reader: bufio.NewReader(conn), 55 | } 56 | } 57 | 58 | func (s *ClientSessionReadWriter) ReadCommand() (cmd *resp.Command, err error) { 59 | if cmd, err = resp.ReadCommand(s.reader); err != nil { 60 | log.Error("read from client", err, s.RemoteAddr()) 61 | } 62 | return 63 | } 64 | 65 | func (s *ClientSessionReadWriter) WriteObject(obj *resp.Object) (err error) { 66 | if _, err = s.Write(obj.Raw()); err != nil { 67 | log.Error("write to client", err, s.RemoteAddr()) 68 | } 69 | return 70 | } 71 | 72 | // Session represents a connection between client and proxy 73 | // connection reading and writing are executed individually in go routines 74 | // since requests are sent to different backends, response may come back unordered 75 | // a heap is used to reorder the pipeline responses 76 | type Session struct { 77 | io ClientReadWriter 78 | pendingRequests chan *Request 79 | closeWg *WaitGroupWrapper 80 | reqWg *sync.WaitGroup 81 | connPool *ConnPool 82 | dispatcher Dispatcher 83 | } 84 | 85 | func NewSession(io ClientReadWriter, connPool *ConnPool, dispatcher Dispatcher) *Session { 86 | session := &Session{ 87 | io: io, 88 | pendingRequests: make(chan *Request, 5000), 89 | closeWg: &WaitGroupWrapper{}, 90 | reqWg: &sync.WaitGroup{}, 91 | connPool: connPool, 92 | dispatcher: dispatcher, 93 | } 94 | return session 95 | } 96 | 97 | func (s *Session) Run() { 98 | s.closeWg.Wrap(s.WritingLoop) 99 | s.closeWg.Wrap(s.ReadingLoop) 100 | s.closeWg.Wait() 101 | } 102 | 103 | // WritingLoop consumes rspQueue and send response to client 104 | // It close the connection to notify reader on error 105 | // and continue loop until the reader has told it to exit 106 | func (s *Session) WritingLoop() { 107 | defer func() { 108 | s.io.Close() 109 | for { 110 | if _, ok := <-s.pendingRequests; !ok { 111 | break 112 | } 113 | } 114 | log.Info("exit writing loop", s.io.RemoteAddr()) 115 | }() 116 | for { 117 | select { 118 | case req, ok := <-s.pendingRequests: 119 | if !ok { 120 | return 121 | } 122 | req.Wait() 123 | 124 | if err := s.handleRespPipeline(req); err != nil { 125 | return 126 | } 127 | } 128 | } 129 | } 130 | 131 | func (s *Session) ReadingLoop() { 132 | defer func() { 133 | // it's safe to close rspCh only after all requests have done 134 | s.reqWg.Wait() 135 | close(s.pendingRequests) 136 | log.Info("exit reading loop", s.io.RemoteAddr()) 137 | }() 138 | for { 139 | cmd, err := s.io.ReadCommand() 140 | if err != nil { 141 | break 142 | } 143 | 144 | // convert all command name to upper case 145 | cmd.Args[0] = strings.ToUpper(cmd.Args[0]) 146 | 147 | // check if command is supported 148 | cmdFlag := CmdFlag(cmd) 149 | if cmdFlag&CMD_FLAG_BLACK != 0 { 150 | req := &Request{ 151 | cmd: cmd, 152 | obj: BLACK_CMD_RESP_OBJECT, 153 | WaitGroup: &sync.WaitGroup{}, 154 | } 155 | s.pendingRequests <- req 156 | continue 157 | } 158 | 159 | // check if is multi key cmd 160 | if yes, numKeys := IsMultiCmd(cmd); yes && numKeys > 1 { 161 | s.handleMultiKeyCmd(cmd, numKeys) 162 | } else { 163 | if err = s.handleGenericCmd(cmd, cmdFlag); err != nil { 164 | break 165 | } 166 | } 167 | } 168 | 169 | } 170 | 171 | // 将resp写出去。如果是multi key command,只有在全部完成后才汇总输出 172 | func (s *Session) handleRespMulti(req *Request) error { 173 | var obj *resp.Object 174 | if parentCmd := req.parentCmd; parentCmd != nil { 175 | // sub request 176 | parentCmd.OnSubCmdFinished(req) 177 | if !parentCmd.Finished() { 178 | return nil 179 | } 180 | obj = parentCmd.CoalesceRsp() 181 | } else { 182 | obj = req.obj 183 | } 184 | if n := atomic.AddUint64(&accessLogCount, 1); n%LogEveryN == 0 && len(req.cmd.Args) >= 2 { 185 | log.Info("access", s.io.RemoteAddr(), req.cmd.Args[:2]) 186 | } 187 | return s.io.WriteObject(obj) 188 | } 189 | 190 | // redirect send request to backend again to new server told by redis cluster 191 | func (s *Session) redirect(server string, req *Request, ask bool) { 192 | var conn net.Conn 193 | var err error 194 | 195 | req.err = nil 196 | conn, err = s.connPool.GetConn(server) 197 | if err != nil { 198 | log.Error(err) 199 | req.err = err 200 | return 201 | } 202 | defer func() { 203 | if err != nil { 204 | log.Error(err) 205 | conn.(*pool.PoolConn).MarkUnusable() 206 | } 207 | conn.Close() 208 | }() 209 | 210 | reader := bufio.NewReader(conn) 211 | if ask { 212 | if _, err = conn.Write(ASK_CMD_BYTES); err != nil { 213 | req.err = err 214 | return 215 | } 216 | } 217 | if _, err = conn.Write(req.cmd.Format()); err != nil { 218 | req.err = err 219 | return 220 | } 221 | if ask { 222 | if _, err = resp.ReadData(reader); err != nil { 223 | req.err = err 224 | return 225 | } 226 | } 227 | obj := resp.NewObject() 228 | if err = resp.ReadDataBytes(reader, obj); err != nil { 229 | req.err = err 230 | } else { 231 | req.obj = obj 232 | } 233 | } 234 | 235 | // handleResp handles MOVED and ASK redirection and call write response 236 | func (s *Session) handleRespRedirect(req *Request) error { 237 | if req.err != nil { 238 | s.dispatcher.TriggerReloadSlots() 239 | rsp := &resp.Data{T: resp.T_Error, String: []byte(req.err.Error())} 240 | req.obj = resp.NewObjectFromData(rsp) 241 | } else { 242 | raw := req.obj.Raw() 243 | if raw[0] == resp.T_Error { 244 | if bytes.HasPrefix(raw, MOVED) { 245 | _, server := ParseRedirectInfo(string(raw)) 246 | s.dispatcher.TriggerReloadSlots() 247 | s.redirect(server, req, false) 248 | } else if bytes.HasPrefix(raw, ASK) { 249 | _, server := ParseRedirectInfo(string(raw)) 250 | s.redirect(server, req, true) 251 | } 252 | } 253 | } 254 | 255 | if req.err != nil { 256 | return req.err 257 | } 258 | 259 | if err := s.handleRespMulti(req); err != nil { 260 | return err 261 | } 262 | 263 | return nil 264 | } 265 | 266 | // handleRespPipeline handles the response if its sequence number is equal to session's 267 | // response sequence number, otherwise, put it to a heap to keep the response order is same 268 | // to request order 269 | func (s *Session) handleRespPipeline(req *Request) error { 270 | return s.handleRespRedirect(req) 271 | } 272 | 273 | func (s *Session) handleMultiKeyCmd(cmd *resp.Command, numKeys int) { 274 | var subCmd *resp.Command 275 | var err error 276 | mc := NewMultiRequest(cmd, numKeys) 277 | // multi sub cmd share the same seq number, 在处理reorder时有利于提高效率 278 | readOnly := mc.CmdType() == MGET 279 | for i := 0; i < numKeys; i++ { 280 | switch mc.CmdType() { 281 | case MGET: 282 | subCmd, err = resp.NewCommand("GET", cmd.Value(i+1)) 283 | case MSET: 284 | subCmd, err = resp.NewCommand("SET", cmd.Value(2*i+1), cmd.Value((2*i + 2))) 285 | case DEL: 286 | subCmd, err = resp.NewCommand("DEL", cmd.Value(i+1)) 287 | } 288 | if err != nil { 289 | panic(err) 290 | } 291 | key := subCmd.Value(1) 292 | slot := Key2Slot(key) 293 | req := &Request{ 294 | cmd: subCmd, 295 | readOnly: readOnly, 296 | slot: slot, 297 | subSeq: i, 298 | parentCmd: mc, 299 | WaitGroup: &sync.WaitGroup{}, 300 | } 301 | req.Add(1) 302 | s.dispatcher.Schedule(req) 303 | s.pendingRequests <- req 304 | } 305 | } 306 | 307 | func (s *Session) handleGenericCmd(cmd *resp.Command, cmdFlag int) (err error) { 308 | key := cmd.Value(1) 309 | if cmd.Args[0] == CMD_NAME_EVAL || cmd.Args[0] == CMD_NAME_EVALSHA { 310 | if len(cmd.Args) >= CMD_EVAL_KEY_POS+1 { 311 | key = cmd.Value(CMD_EVAL_KEY_POS) 312 | } else { 313 | return fmt.Errorf("not enough parameter for command: %s", cmd.Args[0]) 314 | } 315 | } 316 | slot := Key2Slot(key) 317 | req := &Request{ 318 | cmd: cmd, 319 | readOnly: cmdFlag&CMD_FLAG_READONLY != 0, 320 | slot: slot, 321 | WaitGroup: &sync.WaitGroup{}, 322 | } 323 | 324 | req.Add(1) 325 | s.dispatcher.Schedule(req) 326 | s.pendingRequests <- req 327 | return 328 | } 329 | 330 | // ParseRedirectInfo parse slot redirect information from MOVED and ASK Error 331 | func ParseRedirectInfo(msg string) (slot int, server string) { 332 | var err error 333 | parts := strings.Fields(msg) 334 | if len(parts) != 3 { 335 | log.Fatalf("invalid redirect message: %s", msg) 336 | } 337 | slot, err = strconv.Atoi(parts[1]) 338 | if err != nil { 339 | log.Fatalf("invalid redirect message: %s", msg) 340 | } 341 | server = parts[2] 342 | return 343 | } 344 | -------------------------------------------------------------------------------- /proxy/session_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "testing" 7 | 8 | "fmt" 9 | "math/rand" 10 | "time" 11 | 12 | "bufio" 13 | "bytes" 14 | 15 | "github.com/collinmsn/resp" 16 | log "github.com/ngaut/logging" 17 | "github.com/stretchr/testify/assert" 18 | ) 19 | 20 | type MockSessionReadWriter struct { 21 | cmds chan *resp.Command 22 | objs chan *resp.Object 23 | closeCmds bool 24 | } 25 | 26 | func (rw *MockSessionReadWriter) Close() error { 27 | if rw.closeCmds { 28 | close(rw.cmds) 29 | } 30 | return nil 31 | } 32 | func (rw *MockSessionReadWriter) RemoteAddr() net.Addr { 33 | return &net.TCPAddr{} 34 | } 35 | 36 | /** 37 | * 用chan close模拟client close 38 | */ 39 | func (rw *MockSessionReadWriter) ReadCommand() (*resp.Command, error) { 40 | cmd, ok := <-rw.cmds 41 | if !ok { 42 | return nil, io.EOF 43 | } else { 44 | return cmd, nil 45 | } 46 | } 47 | 48 | func (rw *MockSessionReadWriter) WriteObject(obj *resp.Object) (err error) { 49 | defer func() { 50 | if r := recover(); r != nil { 51 | log.Error(r) 52 | err = io.ErrClosedPipe 53 | } 54 | }() 55 | rw.objs <- obj 56 | return 57 | } 58 | 59 | type EchoDispatcher struct { 60 | } 61 | 62 | func (d *EchoDispatcher) TriggerReloadSlots() { 63 | } 64 | func (d *EchoDispatcher) Schedule(req *PipelineRequest) { 65 | rsp := &PipelineResponse{ 66 | req: req, 67 | obj: resp.NewObjectFromData(&resp.Data{ 68 | T: resp.T_BulkString, 69 | String: []byte(req.cmd.Args[1]), 70 | }), 71 | } 72 | req.backQ <- rsp 73 | } 74 | 75 | func TestSessionClientReadWrite(t *testing.T) { 76 | assert := assert.New(t) 77 | io := &MockSessionReadWriter{ 78 | cmds: make(chan *resp.Command), 79 | objs: make(chan *resp.Object), 80 | } 81 | session := NewSession(io, nil, &EchoDispatcher{}) 82 | go session.Run() 83 | var N int = 10000 84 | keys := make([]string, N) 85 | for i := 0; i < N; i++ { 86 | keys[i] = fmt.Sprintf("key%d", i) 87 | } 88 | reqRsp := make(map[*resp.Command]*resp.Object) 89 | for _, k := range keys { 90 | cmd, _ := resp.NewCommand("echo", k) 91 | reqRsp[cmd] = resp.NewObjectFromData(&resp.Data{ 92 | T: resp.T_BulkString, 93 | String: []byte(k), 94 | }) 95 | } 96 | for k, v := range reqRsp { 97 | io.cmds <- k 98 | rsp := <-io.objs 99 | assert.Equal(string(rsp.Raw()), string(v.Raw())) 100 | } 101 | } 102 | 103 | func TestSessionClientReadError(t *testing.T) { 104 | assert := assert.New(t) 105 | io := &MockSessionReadWriter{ 106 | cmds: make(chan *resp.Command), 107 | objs: make(chan *resp.Object), 108 | } 109 | session := NewSession(io, nil, &EchoDispatcher{}) 110 | go session.Run() 111 | N := 10 112 | 113 | keys := make([]string, N) 114 | for i := 0; i < N; i++ { 115 | keys[i] = fmt.Sprintf("key%d", i) 116 | } 117 | reqRsp := make(map[*resp.Command]*resp.Object) 118 | for _, k := range keys { 119 | cmd, _ := resp.NewCommand("echo", k) 120 | reqRsp[cmd] = resp.NewObjectFromData(&resp.Data{ 121 | T: resp.T_BulkString, 122 | String: []byte(k), 123 | }) 124 | } 125 | for k, v := range reqRsp { 126 | io.cmds <- k 127 | rsp := <-io.objs 128 | assert.Equal(string(rsp.Raw()), string(v.Raw())) 129 | } 130 | closeFunc := func(d time.Duration) { 131 | <-time.After(d) 132 | close(io.cmds) 133 | } 134 | d := 3 * time.Second 135 | go closeFunc(d) 136 | ts := time.Now() 137 | session.closeWg.Wait() 138 | used := time.Since(ts) 139 | assert.InEpsilon(float64(used), float64(d), float64(5*time.Millisecond)) 140 | } 141 | 142 | func TestSessionClientWriteError(t *testing.T) { 143 | assert := assert.New(t) 144 | io := &MockSessionReadWriter{ 145 | cmds: make(chan *resp.Command), 146 | objs: make(chan *resp.Object), 147 | closeCmds: true, 148 | } 149 | session := NewSession(io, nil, &EchoDispatcher{}) 150 | go session.Run() 151 | N := 10 152 | 153 | keys := make([]string, N) 154 | for i := 0; i < N; i++ { 155 | keys[i] = fmt.Sprintf("key%d", i) 156 | } 157 | reqRsp := make(map[*resp.Command]*resp.Object) 158 | cmds := make([]*resp.Command, 0, N) 159 | for _, k := range keys { 160 | cmd, _ := resp.NewCommand("echo", k) 161 | reqRsp[cmd] = resp.NewObjectFromData(&resp.Data{ 162 | T: resp.T_BulkString, 163 | String: []byte(k), 164 | }) 165 | cmds = append(cmds, cmd) 166 | } 167 | for _, cmd := range cmds { 168 | io.cmds <- cmd 169 | } 170 | count := 0 171 | for _, cmd := range cmds { 172 | rsp := <-io.objs 173 | assert.Equal(string(rsp.Raw()), string(reqRsp[cmd].Raw())) 174 | count++ 175 | if count == N/2 { 176 | close(io.objs) 177 | break 178 | } 179 | } 180 | 181 | ts := time.Now() 182 | session.closeWg.Wait() 183 | used := time.Since(ts) 184 | assert.InEpsilon(float64(used), 0, float64(5*time.Millisecond)) 185 | } 186 | 187 | func shuffle(reqs []*PipelineRequest) { 188 | for i := range reqs { 189 | j := rand.Intn(i + 1) 190 | reqs[i], reqs[j] = reqs[j], reqs[i] 191 | } 192 | } 193 | 194 | type OutOfOrderDispatcher struct { 195 | buf []*PipelineRequest 196 | bufLen int 197 | } 198 | 199 | func (d *OutOfOrderDispatcher) TriggerReloadSlots() { 200 | } 201 | func (d *OutOfOrderDispatcher) Schedule(req *PipelineRequest) { 202 | d.buf = append(d.buf, req) 203 | if len(d.buf) < d.bufLen { 204 | return 205 | } 206 | shuffle(d.buf) 207 | for _, req := range d.buf { 208 | rsp := &PipelineResponse{ 209 | req: req, 210 | obj: resp.NewObjectFromData(&resp.Data{ 211 | T: resp.T_BulkString, 212 | String: []byte(req.cmd.Args[1]), 213 | }), 214 | } 215 | req.backQ <- rsp 216 | } 217 | d.buf = d.buf[:0] 218 | } 219 | 220 | func TestSessionPipelineOrder(t *testing.T) { 221 | assert := assert.New(t) 222 | var BUF_LEN int = 110 223 | var ROUND = 100 224 | var N int = BUF_LEN * ROUND 225 | io := &MockSessionReadWriter{ 226 | cmds: make(chan *resp.Command), 227 | objs: make(chan *resp.Object, N), 228 | } 229 | dispatcher := &OutOfOrderDispatcher{ 230 | bufLen: BUF_LEN, 231 | } 232 | session := NewSession(io, nil, dispatcher) 233 | go session.Run() 234 | keys := make([]string, N) 235 | for i := 0; i < N; i++ { 236 | keys[i] = fmt.Sprintf("key%d", i) 237 | } 238 | reqRsp := make(map[*resp.Command]*resp.Object) 239 | cmdOrder := make([]*resp.Command, 0, N) 240 | for _, k := range keys { 241 | cmd, _ := resp.NewCommand("echo", k) 242 | reqRsp[cmd] = resp.NewObjectFromData(&resp.Data{ 243 | T: resp.T_BulkString, 244 | String: []byte(k), 245 | }) 246 | cmdOrder = append(cmdOrder, cmd) 247 | } 248 | for i := 0; i < ROUND; i++ { 249 | for j := 0; j < BUF_LEN; j++ { 250 | io.cmds <- cmdOrder[i*BUF_LEN+j] 251 | } 252 | for j := 0; j < BUF_LEN; j++ { 253 | cmd := cmdOrder[i*BUF_LEN+j] 254 | rsp := <-io.objs 255 | assert.Equal(string(rsp.Raw()), string(reqRsp[cmd].Raw())) 256 | } 257 | } 258 | } 259 | 260 | func TestSessionMultiRequest(t *testing.T) { 261 | assert := assert.New(t) 262 | var BUF_LEN int = 110 263 | var ROUND = 100 264 | var N int = BUF_LEN * ROUND 265 | io := &MockSessionReadWriter{ 266 | cmds: make(chan *resp.Command), 267 | objs: make(chan *resp.Object, BUF_LEN), 268 | } 269 | dispatcher := &OutOfOrderDispatcher{ 270 | bufLen: BUF_LEN, 271 | } 272 | session := NewSession(io, nil, dispatcher) 273 | go session.Run() 274 | keys := make([]string, N) 275 | for i := 0; i < N; i++ { 276 | keys[i] = fmt.Sprintf("key%d", i) 277 | } 278 | // in each round, there are random get command and a mget command 279 | // mget is started at a random pos 280 | for i := 0; i < ROUND; i++ { 281 | reqRsp := make(map[*resp.Command]*resp.Object) 282 | var cmdOrder []*resp.Command 283 | numMgetKeys := rand.Int() % BUF_LEN 284 | if numMgetKeys < 2 { 285 | // 因为mget如果num key为1,会被转成get处理 286 | numMgetKeys = 2 287 | } 288 | pos := rand.Int() % (BUF_LEN - numMgetKeys) 289 | for j := 0; j < pos; j++ { 290 | k := fmt.Sprintf("key%d", i*BUF_LEN+j) 291 | cmd, _ := resp.NewCommand("get", k) 292 | reqRsp[cmd] = resp.NewObjectFromData(&resp.Data{ 293 | T: resp.T_BulkString, 294 | String: []byte(k), 295 | }) 296 | cmdOrder = append(cmdOrder, cmd) 297 | io.cmds <- cmd 298 | } 299 | var mgetKeys []string 300 | for j := pos; j < pos+numMgetKeys; j++ { 301 | k := fmt.Sprintf("key%d", i*BUF_LEN+j) 302 | mgetKeys = append(mgetKeys, k) 303 | cmd, _ := resp.NewCommand("get", k) 304 | reqRsp[cmd] = resp.NewObjectFromData(&resp.Data{ 305 | T: resp.T_BulkString, 306 | String: []byte(k), 307 | }) 308 | cmdOrder = append(cmdOrder, cmd) 309 | } 310 | var args []string 311 | args = append(args, "mget") 312 | args = append(args, mgetKeys...) 313 | cmd, _ := resp.NewCommand(args...) 314 | io.cmds <- cmd 315 | 316 | for j := pos + numMgetKeys; j < BUF_LEN; j++ { 317 | k := fmt.Sprintf("key%d", i*BUF_LEN+j) 318 | cmd, _ := resp.NewCommand("get", k) 319 | reqRsp[cmd] = resp.NewObjectFromData(&resp.Data{ 320 | T: resp.T_BulkString, 321 | String: []byte(k), 322 | }) 323 | cmdOrder = append(cmdOrder, cmd) 324 | io.cmds <- cmd 325 | } 326 | 327 | // rsp 328 | for j := 0; j < pos; j++ { 329 | cmd := cmdOrder[j] 330 | rsp := <-io.objs 331 | assert.Equal(string(rsp.Raw()), string(reqRsp[cmd].Raw())) 332 | } 333 | // multi 334 | rsp := <-io.objs 335 | data, _ := resp.ReadData(bufio.NewReader(bytes.NewReader(rsp.Raw()))) 336 | assert.Equal(len(data.Array), numMgetKeys) 337 | for j := 0; j < len(data.Array); j++ { 338 | cmd := cmdOrder[pos+j] 339 | assert.Equal(string(resp.NewObjectFromData(data.Array[j]).Raw()), string(reqRsp[cmd].Raw())) 340 | } 341 | for j := pos + numMgetKeys; j < BUF_LEN; j++ { 342 | cmd := cmdOrder[j] 343 | rsp := <-io.objs 344 | assert.Equal(string(rsp.Raw()), string(reqRsp[cmd].Raw())) 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /proxy/slottable.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/collinmsn/resp" 8 | ) 9 | 10 | const ( 11 | NumSlots = 16384 12 | CLUSTER_SLOTS_START = 0 13 | CLUSTER_SLOTS_END = 1 14 | CLUSTER_SLOTS_SERVER_START = 2 15 | ) 16 | 17 | // ServerGroup根据cluster slots和ReadPrefer得出 18 | type ServerGroup struct { 19 | write string 20 | read []string 21 | } 22 | 23 | type SlotTable struct { 24 | serverGroups []*ServerGroup 25 | // a cheap way to random select read backend 26 | counter uint32 27 | } 28 | 29 | func NewSlotTable() *SlotTable { 30 | st := &SlotTable{ 31 | serverGroups: make([]*ServerGroup, NumSlots), 32 | } 33 | return st 34 | } 35 | 36 | func (st *SlotTable) WriteServer(slot int) string { 37 | return st.serverGroups[slot].write 38 | } 39 | 40 | func (st *SlotTable) ReadServer(slot int) string { 41 | st.counter += 1 42 | readServers := st.serverGroups[slot].read 43 | return readServers[st.counter%uint32(len(readServers))] 44 | } 45 | 46 | func (st *SlotTable) SetSlotInfo(si *SlotInfo) { 47 | for i := si.start; i <= si.end; i++ { 48 | st.serverGroups[i] = &ServerGroup{ 49 | write: si.write, 50 | read: si.read, 51 | } 52 | } 53 | } 54 | 55 | type SlotInfo struct { 56 | start int 57 | end int 58 | write string 59 | read []string 60 | } 61 | 62 | func NewSlotInfo(data *resp.Data) *SlotInfo { 63 | /* 64 | cluster slots array element example 65 | 1) 1) (integer) 10923 66 | 2) (integer) 16383 67 | 3) 1) "10.4.17.164" 68 | 2) (integer) 7705 69 | 4) 1) "10.4.17.164" 70 | 2) (integer) 7708 71 | */ 72 | si := &SlotInfo{ 73 | start: int(data.Array[CLUSTER_SLOTS_START].Integer), 74 | end: int(data.Array[CLUSTER_SLOTS_END].Integer), 75 | } 76 | for i := CLUSTER_SLOTS_SERVER_START; i < len(data.Array); i++ { 77 | host := string(data.Array[i].Array[0].String) 78 | if len(host) == 0 { 79 | host = "127.0.0.1" 80 | } 81 | port := int(data.Array[i].Array[1].Integer) 82 | node := fmt.Sprintf("%s:%d", host, port) 83 | if i == CLUSTER_SLOTS_SERVER_START { 84 | si.write = node 85 | } else { 86 | si.read = append(si.read, node) 87 | } 88 | } 89 | return si 90 | } 91 | 92 | func Key2Slot(key string) int { 93 | buf := []byte(key) 94 | if pos := bytes.IndexByte(buf, '{'); pos != -1 { 95 | pos += 1 96 | if pos2 := bytes.IndexByte(buf[pos:], '}'); pos2 > 0 { 97 | slot := CRC16(buf[pos:pos+pos2]) % NumSlots 98 | return int(slot) 99 | } 100 | } 101 | slot := CRC16(buf) % NumSlots 102 | return int(slot) 103 | } 104 | -------------------------------------------------------------------------------- /proxy/slottable_test.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import "testing" 4 | 5 | func TestKey2Slot(t *testing.T) { 6 | pairs := map[string]string{ 7 | "{user1000}.following": "user1000", 8 | "{user1000}.followers": "user1000", 9 | "foo{}{bar}": "foo{}{bar}", 10 | "foo{{bar}}zap": "{bar", 11 | "foo{bar}{zap}": "bar", 12 | "{}bar": "{}bar", 13 | } 14 | for k, v := range pairs { 15 | if Key2Slot(k) != int(CRC16([]byte(v))%NumSlots) { 16 | t.Errorf("slot not equal: %s, %s", k, v) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /proxy/util.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | 7 | log "github.com/ngaut/logging" 8 | ) 9 | 10 | var ( 11 | localIP string 12 | localIPLock sync.Mutex 13 | ) 14 | 15 | type WaitGroupWrapper struct { 16 | sync.WaitGroup 17 | } 18 | 19 | func (w *WaitGroupWrapper) Wrap(cb func()) { 20 | w.Add(1) 21 | go func() { 22 | cb() 23 | w.Done() 24 | }() 25 | } 26 | 27 | func LocalIP() string { 28 | if len(localIP) != 0 { 29 | return localIP 30 | } 31 | localIPLock.Lock() 32 | defer localIPLock.Unlock() 33 | if len(localIP) != 0 { 34 | return localIP 35 | } 36 | localIP = getLocalIP() 37 | return localIP 38 | } 39 | 40 | func getLocalIP() string { 41 | var result string 42 | interfaceName := "eth0" 43 | iface, err := net.InterfaceByName(interfaceName) 44 | if err != nil { 45 | log.Error("get net interface err=%v", err) 46 | return result 47 | } 48 | 49 | if iface.Flags&net.FlagUp == 0 { 50 | log.Error("net interface %s is down", interfaceName) 51 | return result 52 | } 53 | 54 | addrs, err := iface.Addrs() 55 | if err != nil { 56 | log.Error("get addrs of interface %s failed err=%v", interfaceName, err) 57 | return result 58 | } 59 | 60 | for _, addr := range addrs { 61 | var ip net.IP 62 | switch v := addr.(type) { 63 | case *net.IPNet: 64 | ip = v.IP 65 | case *net.IPAddr: 66 | ip = v.IP 67 | } 68 | if ip == nil { 69 | continue 70 | } 71 | ip = ip.To4() 72 | if ip != nil { 73 | result = ip.String() 74 | log.Infof("get local ip %s", result) 75 | return result 76 | } 77 | } 78 | // 79 | log.Error("Failed to get local ip") 80 | return result 81 | } 82 | -------------------------------------------------------------------------------- /rcproxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/collinmsn/rcproxy/1c89ff7d474890211fbd4e323c529ae6af253aa3/rcproxy.png -------------------------------------------------------------------------------- /start_proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export GOMAXPROCS=4 4 | killall -9 rcproxy 5 | make clean 6 | make 7 | nohup ./bin/rcproxy --debug-addr=:9876 & 8 | -------------------------------------------------------------------------------- /tests/bench/redis-benchmark.sh: -------------------------------------------------------------------------------- 1 | redis-benchmark -p 8088 -c 500 -n 5000000 -P 100 -r 10000 -t get,set 2 | -------------------------------------------------------------------------------- /tests/loadrunner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/garyburd/redigo/redis" 4 | import "log" 5 | import "fmt" 6 | import "math/rand" 7 | import "runtime" 8 | 9 | var maxKeyNum = 10000000 10 | var numWriter = 100 11 | var numReader = 500 12 | var server = "127.0.0.1:8088" 13 | 14 | func write(server string, exitChan chan struct{}) { 15 | var conn redis.Conn 16 | var err error 17 | defer func() { 18 | if conn != nil { 19 | conn.Close() 20 | } 21 | if err != nil { 22 | log.Print(err) 23 | } 24 | exitChan <- struct{}{} 25 | }() 26 | 27 | conn, err = redis.Dial("tcp", server) 28 | if err != nil { 29 | return 30 | } 31 | for { 32 | i := rand.Int() % maxKeyNum 33 | key := fmt.Sprintf("key:%d", i) 34 | if _, err := conn.Do("SET", key, key); err != nil { 35 | log.Printf("set error %v", err) 36 | break 37 | } 38 | } 39 | } 40 | 41 | func read(server string, exitChan chan struct{}) { 42 | var conn redis.Conn 43 | var err error 44 | defer func() { 45 | if conn != nil { 46 | conn.Close() 47 | } 48 | if err != nil { 49 | log.Print(err) 50 | } 51 | exitChan <- struct{}{} 52 | }() 53 | 54 | conn, err = redis.Dial("tcp", server) 55 | if err != nil { 56 | return 57 | } 58 | for { 59 | i := rand.Int() % maxKeyNum 60 | key := fmt.Sprintf("key:%d", i) 61 | if reply, err := conn.Do("GET", key); err != nil { 62 | log.Printf("get error %v %v", key, err) 63 | break 64 | } else { 65 | log.Printf("key->%s", reply) 66 | } 67 | } 68 | } 69 | 70 | func main() { 71 | runtime.GOMAXPROCS(8) 72 | exitChan := make(chan struct{}) 73 | for i := 0; i < numWriter; i++ { 74 | go write(server, exitChan) 75 | } 76 | for i := 0; i < numReader; i++ { 77 | go read(server, exitChan) 78 | } 79 | for i := 0; i < numReader+numWriter; i++ { 80 | <-exitChan 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/random_migration.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import random 4 | from redistrib import command 5 | while True: 6 | slot = random.randint(0, 16383) 7 | src = random.randint(7001, 7003) 8 | dest = random.randint(7001, 7003) 9 | if src == dest: 10 | continue 11 | s = time.time() 12 | try: 13 | command.migrate_slots('127.0.0.1', src, '127.0.0.1', dest, [slot,]) 14 | except Exception as e: 15 | print e 16 | pass 17 | else: 18 | e = time.time() 19 | print("migrate slot %d 127.0.0.1:%d -> 127.0.0.1:%d time: %f" % (slot, src, dest, e-s)) 20 | 21 | time.sleep(1) 22 | 23 | -------------------------------------------------------------------------------- /tests/redis.conf.tmpl: -------------------------------------------------------------------------------- 1 | port {PORT} 2 | cluster-enabled yes 3 | cluster-config-file nodes.conf 4 | cluster-node-timeout 5000 5 | daemonize yes 6 | save "" -------------------------------------------------------------------------------- /tests/redistrib/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.3.10' 2 | REPO = 'https://github.com/HunanTV/redis-trib.py' 3 | -------------------------------------------------------------------------------- /tests/redistrib/clusternode.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import hiredis 3 | import logging 4 | 5 | SYM_STAR = '*' 6 | SYM_DOLLAR = '$' 7 | SYM_CRLF = '\r\n' 8 | SYM_EMPTY = '' 9 | 10 | 11 | def encode(value, encoding='utf-8'): 12 | if isinstance(value, bytes): 13 | return value 14 | if isinstance(value, (int, long)): 15 | return str(value) 16 | if isinstance(value, float): 17 | return repr(value) 18 | if isinstance(value, unicode): 19 | return value.encode(encoding) 20 | if not isinstance(value, basestring): 21 | return str(value) 22 | return value 23 | 24 | 25 | def squash_commands(commands): 26 | output = [] 27 | buf = '' 28 | 29 | for c in commands: 30 | buf = SYM_EMPTY.join((buf, SYM_STAR, str(len(c)), SYM_CRLF)) 31 | 32 | for arg in map(encode, c): 33 | if len(buf) > 6000 or len(arg) > 6000: 34 | output.append(SYM_EMPTY.join((buf, SYM_DOLLAR, str(len(arg)), 35 | SYM_CRLF))) 36 | output.append(arg) 37 | buf = SYM_CRLF 38 | else: 39 | buf = SYM_EMPTY.join((buf, SYM_DOLLAR, str(len(arg)), 40 | SYM_CRLF, arg, SYM_CRLF)) 41 | output.append(buf) 42 | return output 43 | 44 | 45 | def pack_command(command, *args): 46 | return squash_commands([(command,) + args]) 47 | 48 | CMD_INFO = pack_command('info') 49 | CMD_CLUSTER_NODES = pack_command('cluster', 'nodes') 50 | CMD_CLUSTER_INFO = pack_command('cluster', 'info') 51 | 52 | 53 | class Talker(object): 54 | def __init__(self, host, port, timeout=5): 55 | self.host = host 56 | self.port = port 57 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 58 | self.reader = hiredis.Reader() 59 | self.last_raw_message = '' 60 | 61 | self.sock.settimeout(timeout) 62 | logging.debug('Connect to %s:%d', host, port) 63 | self.sock.connect((host, port)) 64 | 65 | def _recv(self): 66 | while True: 67 | m = self.sock.recv(16384) 68 | self.last_raw_message += m 69 | self.reader.feed(m) 70 | r = self.reader.gets() 71 | if r != False: 72 | return r 73 | 74 | def _recv_multi(self, n): 75 | resp = [] 76 | while len(resp) < n: 77 | m = self.sock.recv(16384) 78 | self.last_raw_message += m 79 | self.reader.feed(m) 80 | 81 | r = self.reader.gets() 82 | while r != False: 83 | resp.append(r) 84 | r = self.reader.gets() 85 | return resp 86 | 87 | def talk_raw(self, command, recv=None): 88 | recv = recv or self._recv 89 | for c in command: 90 | self.sock.send(c) 91 | r = recv() 92 | if r is None: 93 | raise ValueError('No reply') 94 | if isinstance(r, hiredis.ReplyError): 95 | raise r 96 | return r 97 | 98 | def talk(self, *args): 99 | return self.talk_raw(pack_command(*args)) 100 | 101 | def talk_bulk(self, cmd_list): 102 | return self.talk_raw(squash_commands(cmd_list), 103 | recv=lambda: self._recv_multi(len(cmd_list))) 104 | 105 | def close(self): 106 | return self.sock.close() 107 | 108 | 109 | class ClusterNode(object): 110 | def __init__(self, node_id, latest_know_ip_address_and_port, 111 | role_in_cluster, node_id_of_master_if_it_is_a_slave, 112 | last_ping_sent_time, last_pong_received_time, node_index, 113 | link_status, *assigned_slots): 114 | self.node_id = node_id 115 | host, port = latest_know_ip_address_and_port.split(':') 116 | self.host = host 117 | self.port = int(port) 118 | self.role_in_cluster = (role_in_cluster.split(',')[1] 119 | if 'myself' in role_in_cluster 120 | else role_in_cluster) 121 | self.master_id = node_id_of_master_if_it_is_a_slave 122 | self.assigned_slots = [] 123 | self.slots_migrating = False 124 | for slots_range in assigned_slots: 125 | if '[' == slots_range[0] and ']' == slots_range[-1]: 126 | # exclude migrating slot 127 | self.slots_migrating = True 128 | continue 129 | if '-' in slots_range: 130 | begin, end = slots_range.split('-') 131 | self.assigned_slots.extend(range(int(begin), int(end) + 1)) 132 | else: 133 | self.assigned_slots.append(int(slots_range)) 134 | 135 | self._talker = None 136 | 137 | def talker(self): 138 | if self._talker is None: 139 | self._talker = Talker(self.host, self.port) 140 | return self._talker 141 | 142 | def close(self): 143 | if self._talker is not None: 144 | self._talker.close() 145 | self._talker = None 146 | 147 | 148 | class BaseBalancer(object): 149 | def weight(self, clusternode): 150 | return 1 151 | 152 | 153 | def base_balance_plan(nodes, balancer=None): 154 | if balancer is None: 155 | balancer = BaseBalancer() 156 | nodes = [n for n in nodes if 'master' == n.role_in_cluster] 157 | origin_slots = [len(n.assigned_slots) for n in nodes] 158 | total_slots = sum(origin_slots) 159 | weights = [balancer.weight(n) for n in nodes] 160 | total_weight = sum(weights) 161 | 162 | result_slots = [total_slots * w / total_weight for w in weights] 163 | frag_slots = total_slots - sum(result_slots) 164 | 165 | migratings = [[n, r - o] for n, r, o in 166 | zip(nodes, result_slots, origin_slots)] 167 | 168 | for m in migratings: 169 | if frag_slots > -m[1] > 0: 170 | frag_slots += m[1] 171 | m[1] = 0 172 | elif frag_slots <= -m[1]: 173 | m[1] += frag_slots 174 | break 175 | 176 | migrating = sorted([m for m in migratings if m[1] != 0], 177 | key=lambda x: x[1]) 178 | mig_out = 0 179 | mig_in = len(migrating) - 1 180 | 181 | plan = [] 182 | while mig_out < mig_in: 183 | if migrating[mig_in][1] < -migrating[mig_out][1]: 184 | plan.append((migrating[mig_out][0], migrating[mig_in][0], 185 | migrating[mig_in][1])) 186 | migrating[mig_out][1] += migrating[mig_in][1] 187 | mig_in -= 1 188 | elif migrating[mig_in][1] > -migrating[mig_out][1]: 189 | plan.append((migrating[mig_out][0], migrating[mig_in][0], 190 | -migrating[mig_out][1])) 191 | migrating[mig_in][1] += migrating[mig_out][1] 192 | mig_out += 1 193 | else: 194 | plan.append((migrating[mig_out][0], migrating[mig_in][0], 195 | migrating[mig_in][1])) 196 | mig_out += 1 197 | mig_in -= 1 198 | 199 | return plan 200 | -------------------------------------------------------------------------------- /tests/redistrib/console.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | 4 | import command 5 | from . import __version__, REPO 6 | 7 | 8 | def _parse_host_port(addr): 9 | host, port = addr.split(':') 10 | return host, int(port) 11 | 12 | 13 | def start(host_port): 14 | command.start_cluster(*_parse_host_port(host_port)) 15 | 16 | 17 | def start_multi(*host_port_list): 18 | command.start_cluster_on_multi([_parse_host_port(hp) 19 | for hp in host_port_list]) 20 | 21 | 22 | def join(cluster_host_port, newin_host_port): 23 | cluster_host, cluster_port = _parse_host_port(cluster_host_port) 24 | newin_host, newin_port = _parse_host_port(newin_host_port) 25 | command.join_cluster(cluster_host, cluster_port, newin_host, newin_port) 26 | 27 | 28 | def join_no_load(cluster_host_port, newin_host_port): 29 | cluster_host, cluster_port = _parse_host_port(cluster_host_port) 30 | newin_host, newin_port = _parse_host_port(newin_host_port) 31 | command.join_no_load(cluster_host, cluster_port, newin_host, newin_port) 32 | 33 | 34 | def quit(host_port): 35 | command.quit_cluster(*_parse_host_port(host_port)) 36 | 37 | 38 | def shutdown(host_port): 39 | command.shutdown_cluster(*_parse_host_port(host_port)) 40 | 41 | 42 | def fix(host_port): 43 | command.fix_migrating(*_parse_host_port(host_port)) 44 | 45 | 46 | def rescue(host_port, subs_host_port): 47 | host, port = _parse_host_port(host_port) 48 | command.rescue_cluster(host, port, *_parse_host_port(subs_host_port)) 49 | 50 | 51 | def replicate(master_host_port, slave_host_port): 52 | master_host, master_port = _parse_host_port(master_host_port) 53 | slave_host, slave_port = _parse_host_port(slave_host_port) 54 | command.replicate(master_host, master_port, slave_host, slave_port) 55 | 56 | 57 | def migrate_slots(src_host_port, dst_host_port, *slot_ranges): 58 | src_host, src_port = _parse_host_port(src_host_port) 59 | dst_host, dst_port = _parse_host_port(dst_host_port) 60 | 61 | slots = [] 62 | for rg in slot_ranges: 63 | if '-' in rg: 64 | begin, end = rg.split('-') 65 | slots.extend(xrange(int(begin), int(end) + 1)) 66 | else: 67 | slots.append(int(rg)) 68 | 69 | command.migrate_slots(src_host, src_port, dst_host, dst_port, slots) 70 | 71 | 72 | def main(): 73 | print 'Redis-trib', __version__, 74 | print 'Copyright (c) HunanTV Platform developers' 75 | if len(sys.argv) < 2: 76 | print >> sys.stderr, 'Usage:' 77 | print >> sys.stderr, ' redis-trib.py ACTION_NAME [arg0 arg1 ...]' 78 | print >> sys.stderr, 'Take a look at README for more details:', REPO 79 | sys.exit(1) 80 | logging.basicConfig(level=logging.INFO) 81 | getattr(sys.modules[__name__], sys.argv[1])(*sys.argv[2:]) 82 | -------------------------------------------------------------------------------- /tests/redistrib/exceptions.py: -------------------------------------------------------------------------------- 1 | class RedisStatusError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /tests/start_cluster.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | import time 5 | from redistrib import command 6 | logging.getLogger().setLevel(logging.INFO) 7 | 8 | PORTS = [ 9 | 7001, 10 | 7002, 11 | 7003, 12 | ] 13 | 14 | REPLICAS = 3 15 | 16 | cluster = "cluster" 17 | tmpl_file = "redis.conf.tmpl" 18 | 19 | 20 | def mkdir_p(dir): 21 | os.system("mkdir -p %s" % dir) 22 | 23 | 24 | def start_servers(): 25 | logging.info("start servers") 26 | tmpl = open(tmpl_file, 'r').read() 27 | for port in PORTS: 28 | for r in range(REPLICAS): 29 | instance_port = port + r * 100 30 | dir = "%s/%s" % (cluster, instance_port) 31 | mkdir_p(dir) 32 | with open(os.path.join(dir, "redis.conf"), 'w') as fp: 33 | fp.write(tmpl.format(PORT=instance_port)) 34 | 35 | cmd = "cd %s; redis-server redis.conf" % dir 36 | os.system(cmd) 37 | 38 | def start_cluster(): 39 | logging.info("start cluster") 40 | time.sleep(2) 41 | servers = [('127.0.0.1', port) for port in PORTS] 42 | try: 43 | command.start_cluster_on_multi(servers) 44 | except Exception as e: 45 | logging.error(e) 46 | time.sleep(5) 47 | for port in PORTS: 48 | for r in range(1, REPLICAS): 49 | slave_port = port + r * 100 50 | logging.info("replicate: 127.0.0.1:%d <- 127.0.0.1:%d", port, slave_port) 51 | command.replicate("127.0.0.1", port, "127.0.0.1", slave_port) 52 | 53 | if os.path.exists(cluster): 54 | start_servers() 55 | logging.warn("cluster dir has already exists") 56 | sys.exit(0) 57 | else: 58 | start_servers() 59 | start_cluster() 60 | logging.info("done") 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /vendor/github.com/artyom/autoflags/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Artyom Pervukhin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /vendor/github.com/artyom/autoflags/README.md: -------------------------------------------------------------------------------- 1 | autoflags 2 | ========= 3 | 4 | Package autoflags provides a convenient way of exposing struct fields as 5 | command line flags. Exposed fields should have `flag` tag attached: 6 | `flag:"flagName,usage string"`. 7 | 8 | For examples see [documentation][1]. 9 | 10 | [1]: http://godoc.org/github.com/artyom/autoflags 11 | -------------------------------------------------------------------------------- /vendor/github.com/artyom/autoflags/autoflags.go: -------------------------------------------------------------------------------- 1 | // Package autoflags provides a convenient way of exposing struct fields as 2 | // command line flags. Exposed fields should have special tag attached: 3 | // 4 | // var config = struct { 5 | // Name string `flag:"name,name of user"` 6 | // Age uint `flag:"age"` 7 | // Married bool // this won't be exposed 8 | // }{ 9 | // // default values 10 | // Name: "John Doe", 11 | // Age: 34, 12 | // } 13 | // 14 | // After declaring your flags and their default values as above, just register 15 | // flags with flag package and call flag.Parse() as usually: 16 | // 17 | // autoflags.Define(&config) 18 | // flag.Parse() 19 | // 20 | // Now config struct has its fields populated from command line flags. Call the 21 | // program with flags to adjust default values: 22 | // 23 | // progname -name "Jane Roe" -age 29 24 | // 25 | // Package autoflags understands all basic types supported by flag's package 26 | // xxxVar functions: int, int64, uint, uint64, float64, bool, string, 27 | // time.Duration. Types implementing flag.Value interface are also supported. 28 | // Attaching non-empty `flag` tag to field of unsupported type would result in 29 | // panic. 30 | package autoflags // import "github.com/artyom/autoflags" 31 | 32 | import ( 33 | "errors" 34 | "flag" 35 | "fmt" 36 | "reflect" 37 | "strings" 38 | "time" 39 | ) 40 | 41 | var ( 42 | // errPointerWanted is returned when passed argument is not a pointer 43 | errPointerWanted = errors.New("autoflags: pointer expected") 44 | // errInvalidArgument is returned when passed argument is nil pointer or 45 | // pointer to a non-struct value 46 | errInvalidArgument = errors.New("autoflags: non-nil pointer to struct expected") 47 | // errInvalidFlagSet is returned when FlagSet argument passed to 48 | // DefineFlagSet is nil 49 | errInvalidFlagSet = errors.New("autoflags: non-nil FlagSet expected") 50 | errInvalidField = errors.New("autoflags: field is of unsupported type") 51 | ) 52 | 53 | // Define takes pointer to struct and declares flags for its flag-tagged fields. 54 | // Valid tags have the following form: `flag:"flagname"` or 55 | // `flag:"flagname,usage string"`. 56 | // 57 | // Define would panic if given unsupported/invalid argument (anything but 58 | // non-nil pointer to struct) or if any field with `flag` tag attached is of 59 | // unsupported type by flag package (consider implementing flag.Value interface 60 | // for such fields). 61 | func Define(config interface{}) { DefineFlagSet(flag.CommandLine, config) } 62 | 63 | // DefineFlagSet takes pointer to struct and declares flags for its flag-tagged 64 | // fields on given FlagSet. Valid tags have the following form: 65 | // `flag:"flagname"` or `flag:"flagname,usage string"`. 66 | // 67 | // DefineFlagSet would panic if given unsupported/invalid config argument 68 | // (anything but non-nil pointer to struct) or if any config's field with `flag` 69 | // tag attached is of unsupported type by flag package (consider implementing 70 | // flag.Value interface for such fields). 71 | func DefineFlagSet(fs *flag.FlagSet, config interface{}) { 72 | if fs == nil { 73 | panic(errInvalidFlagSet) 74 | } 75 | st := reflect.ValueOf(config) 76 | if st.Kind() != reflect.Ptr { 77 | panic(errPointerWanted) 78 | } 79 | st = reflect.Indirect(st) 80 | if !st.IsValid() || st.Type().Kind() != reflect.Struct { 81 | panic(errInvalidArgument) 82 | } 83 | flagValueType := reflect.TypeOf((*flag.Value)(nil)).Elem() 84 | for i := 0; i < st.NumField(); i++ { 85 | typ := st.Type().Field(i) 86 | var name, usage string 87 | tag := typ.Tag.Get("flag") 88 | if tag == "" { 89 | continue 90 | } 91 | val := st.Field(i) 92 | if !val.CanAddr() { 93 | panic(errInvalidField) 94 | } 95 | flagData := strings.SplitN(tag, ",", 2) 96 | switch len(flagData) { 97 | case 1: 98 | name = flagData[0] 99 | case 2: 100 | name, usage = flagData[0], flagData[1] 101 | } 102 | addr := val.Addr() 103 | if addr.Type().Implements(flagValueType) { 104 | fs.Var(addr.Interface().(flag.Value), name, usage) 105 | continue 106 | } 107 | switch d := val.Interface().(type) { 108 | case int: 109 | fs.IntVar(addr.Interface().(*int), name, d, usage) 110 | case int64: 111 | fs.Int64Var(addr.Interface().(*int64), name, d, usage) 112 | case uint: 113 | fs.UintVar(addr.Interface().(*uint), name, d, usage) 114 | case uint64: 115 | fs.Uint64Var(addr.Interface().(*uint64), name, d, usage) 116 | case float64: 117 | fs.Float64Var(addr.Interface().(*float64), name, d, usage) 118 | case bool: 119 | fs.BoolVar(addr.Interface().(*bool), name, d, usage) 120 | case string: 121 | fs.StringVar(addr.Interface().(*string), name, d, usage) 122 | case time.Duration: 123 | fs.DurationVar(addr.Interface().(*time.Duration), name, d, usage) 124 | default: 125 | panic(fmt.Sprintf("autoflags: field with flag tag value %q is of unsupported type", name)) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /vendor/github.com/collinmsn/resp/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) <2014> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/collinmsn/resp/README.md: -------------------------------------------------------------------------------- 1 | 2 | > [Document In English]() 3 | 4 | # RESP 5 | 6 | resp是一个高性能、易用的redis协议解析类库,支持redis协议数据与redis inline command。 7 | 8 | * 地址:https://github.com/walu/resp 9 | 10 | ## Parse/Encode Redis command 11 | 12 | redis的command分为两种,inline command与bulkString。 13 | 14 | 在io.Reader中读取一个命令: 15 | 16 | ```go 17 | //ex: parse command 18 | body := []byte("setex name 10 walu\r\n") //其实inline command对最后的\r\n没有要求 19 | r := bytes.NewReader(body) 20 | 21 | cmd, err := ReadCommand(r) 22 | 23 | //so: 24 | //cmd.Name() == "setex" 25 | //cmd.Value(1) == "name" 26 | //cmd.Integer(2) == 10 (int64) 27 | //cmd.Value(3) == "walu" 28 | 29 | //---------------------------- 30 | //encode command 31 | cmd := NewCommand("setex", "name", "10", "walu") 32 | body := cmd.Format() 33 | fmt.Println(body) 34 | ``` 35 | 36 | ## Parse/Encode Redis Data 37 | 38 | redis的通讯协议resp支持五种数据类型:SimpleString、Error、BulkString、Interger、Array 39 | 40 | 41 | resp使用了Data结构体来表示这五种数据 42 | 43 | ```go 44 | type Data struct { 45 | T byte //表示类型 46 | String []byte //SimpleString、Error、BulkString 使用这个属性取值 47 | Integer int64 //Interger使用这个属性取值 48 | Array []*Data //Array使用这个属性取值 49 | IsNil bool // 50 | } 51 | ``` 52 | 53 | 在io.Reader中读取一个Data: 54 | 55 | ``` 56 | //ex: parse command 57 | body := []byte("+I'm simple string\r\n") 58 | r := bytes.NewReader(body) 59 | 60 | data, err := ReadCommand(r) 61 | ``` 62 | -------------------------------------------------------------------------------- /vendor/github.com/collinmsn/resp/README_en.md: -------------------------------------------------------------------------------- 1 | > [中文文档]() 2 | 3 | # RESP 4 | 5 | a simple and high-performance library for parsing and encoding redis protocal and redis inline command 6 | 7 | * Repo: https://github.com/walu/resp 8 | 9 | ## Parse/Encode Redis command 10 | 11 | there are two form of redis command, inline command and redis bulk string. 12 | 13 | read a command from io.Reader 14 | 15 | ```go 16 | //ex: parse command 17 | body := []byte("setex name 10 walu\r\n") //Trailing "\r\n" can be omitted 18 | r := bytes.NewReader(body) 19 | 20 | cmd, err := ReadCommand(r) 21 | 22 | //so: 23 | //cmd.Name() == "setex" 24 | //cmd.Value(1) == "name" 25 | //cmd.Integer(2) == 10 (int64) 26 | //cmd.Value(3) == "walu" 27 | 28 | //---------------------------- 29 | //encode command 30 | cmd := NewCommand("setex", "name", "10", "walu") 31 | body := cmd.Format() 32 | fmt.Println(body) 33 | ``` 34 | 35 | ## Parse/Encode Redis Data 36 | 37 | redis protocal has five kinds of data: SimpleString、Error、BulkString、Interger、Array 38 | 39 | resp use a struct named Data representing the five kinds of data. 40 | 41 | ```go 42 | type Data struct { 43 | T byte //the type 44 | String []byte //valid when type is SimpleString、Error or BulkString 45 | Integer int64 //valid when type is Interger 46 | Array []*Data //valid when type is Array 47 | IsNil bool // 48 | } 49 | ``` 50 | 51 | Read a Data from io.Reader 52 | 53 | ``` 54 | //ex: parse command 55 | body := []byte("+I'm simple string\r\n") 56 | r := bytes.NewReader(body) 57 | 58 | data, err := ReadCommand(r) 59 | ``` 60 | -------------------------------------------------------------------------------- /vendor/github.com/collinmsn/resp/resp.go: -------------------------------------------------------------------------------- 1 | //a simple and high-performance library for parsing and encoding redis protocal and redis inline command 2 | package resp 3 | 4 | import ( 5 | "bufio" 6 | "bytes" 7 | "errors" 8 | "io" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | const ( 14 | T_SimpleString = '+' 15 | T_Error = '-' 16 | T_Integer = ':' 17 | T_BulkString = '$' 18 | T_Array = '*' 19 | ) 20 | 21 | var ( 22 | CRLF = []byte{'\r', '\n'} 23 | PROTOCOL_ERR = errors.New("Protocol error") 24 | ) 25 | 26 | /* 27 | Command 28 | 29 | redis supports two kinds of Command: (Inline Command) and (Array With BulkString) 30 | */ 31 | type Command struct { 32 | Args []string //Args[0] is the command name 33 | } 34 | 35 | //get the command name 36 | func (c Command) Name() string { 37 | if len(c.Args) == 0 { 38 | return "" 39 | } else { 40 | return c.Args[0] 41 | } 42 | } 43 | 44 | //get command.Args[index] in string 45 | // 46 | //I must change the method name from String to Value, because method named String has specical meaning when working with fmt.Sprintf. 47 | func (c Command) Value(index int) (ret string) { 48 | if len(c.Args) > index { 49 | ret = c.Args[index] 50 | } 51 | return ret 52 | } 53 | 54 | //get command.Args[index] in int64. 55 | //return 0 if it isn't numberic string. 56 | func (c Command) Integer(index int) (ret int64) { 57 | if len(c.Args) > index { 58 | ret, _ = strconv.ParseInt(c.Args[index], 10, 64) 59 | } 60 | return ret 61 | } 62 | 63 | //Foramat a command into ArrayWithBulkString 64 | func (c Command) Format() []byte { 65 | var ret *bytes.Buffer 66 | ret = new(bytes.Buffer) 67 | 68 | ret.WriteByte(T_Array) 69 | ret.WriteString(strconv.Itoa(len(c.Args))) 70 | ret.Write(CRLF) 71 | for index := range c.Args { 72 | ret.WriteByte(T_BulkString) 73 | ret.WriteString(strconv.Itoa(len(c.Args[index]))) 74 | ret.Write(CRLF) 75 | ret.WriteString(c.Args[index]) 76 | ret.Write(CRLF) 77 | } 78 | return ret.Bytes() 79 | } 80 | 81 | /* 82 | make a new command like terminal 83 | 84 | cmd, err := NewCommand("get", "username") 85 | */ 86 | func NewCommand(args ...string) (*Command, error) { 87 | if len(args) == 0 { 88 | return nil, errors.New("err_new_cmd") 89 | } 90 | return &Command{Args: args}, nil 91 | } 92 | 93 | //read a command from bufio.Reader 94 | func ReadCommand(r *bufio.Reader) (*Command, error) { 95 | buf, err := readRespCommandLine(r) 96 | if nil != err && !(io.EOF == err && len(buf) > 1) { 97 | return nil, err 98 | } 99 | if len(buf) == 0 { 100 | return nil, PROTOCOL_ERR 101 | } 102 | if T_Array != buf[0] { 103 | return NewCommand(strings.Fields(strings.TrimSpace(string(buf)))...) 104 | } 105 | 106 | //Command: BulkString 107 | var ret *Data 108 | ret = new(Data) 109 | 110 | ret, err = readDataForSpecType(r, buf) 111 | if nil != err { 112 | return nil, err 113 | } 114 | 115 | commandArgs := make([]string, len(ret.Array)) 116 | for index := range ret.Array { 117 | if ret.Array[index].T != T_BulkString { 118 | return nil, errors.New("Unexpected Command Type") 119 | } 120 | commandArgs[index] = string(ret.Array[index].String) 121 | } 122 | 123 | return NewCommand(commandArgs...) 124 | } 125 | 126 | //a resp package 127 | type Data struct { 128 | T byte 129 | String []byte 130 | Integer int64 131 | Array []*Data 132 | IsNil bool 133 | } 134 | 135 | //format Data into resp string 136 | func (d Data) Format() []byte { 137 | var ret *bytes.Buffer 138 | ret = new(bytes.Buffer) 139 | 140 | ret.WriteByte(d.T) 141 | if d.IsNil { 142 | ret.WriteString("-1") 143 | ret.Write(CRLF) 144 | return ret.Bytes() 145 | } 146 | 147 | switch d.T { 148 | case T_SimpleString, T_Error: 149 | ret.Write(d.String) 150 | ret.Write(CRLF) 151 | case T_BulkString: 152 | ret.WriteString(strconv.Itoa(len(d.String))) 153 | ret.Write(CRLF) 154 | ret.Write(d.String) 155 | ret.Write(CRLF) 156 | case T_Integer: 157 | ret.WriteString(strconv.FormatInt(d.Integer, 10)) 158 | ret.Write(CRLF) 159 | case T_Array: 160 | ret.WriteString(strconv.Itoa(len(d.Array))) 161 | ret.Write(CRLF) 162 | for index := range d.Array { 163 | ret.Write(d.Array[index].Format()) 164 | } 165 | } 166 | return ret.Bytes() 167 | } 168 | 169 | //get a data from bufio.Reader 170 | func ReadData(r *bufio.Reader) (*Data, error) { 171 | buf, err := readRespLine(r) 172 | if nil != err { 173 | return nil, err 174 | } 175 | 176 | if len(buf) < 2 { 177 | return nil, errors.New("invalid Data Source: " + string(buf)) 178 | } 179 | 180 | return readDataForSpecType(r, buf) 181 | } 182 | 183 | func readDataForSpecType(r *bufio.Reader, line []byte) (*Data, error) { 184 | 185 | var err error 186 | var ret *Data 187 | 188 | ret = new(Data) 189 | switch line[0] { 190 | case T_SimpleString: 191 | ret.T = T_SimpleString 192 | ret.String = line[1:] 193 | 194 | case T_Error: 195 | ret.T = T_Error 196 | ret.String = line[1:] 197 | 198 | case T_Integer: 199 | ret.T = T_Integer 200 | ret.Integer, err = strconv.ParseInt(string(line[1:]), 10, 64) 201 | 202 | case T_BulkString: 203 | var lenBulkString int64 204 | lenBulkString, err = strconv.ParseInt(string(line[1:]), 10, 64) 205 | 206 | ret.T = T_BulkString 207 | if -1 != lenBulkString { 208 | ret.String, err = readRespN(r, lenBulkString) 209 | _, err = readRespN(r, 2) 210 | } else { 211 | ret.IsNil = true 212 | } 213 | 214 | case T_Array: 215 | var lenArray int64 216 | var i int64 217 | lenArray, err = strconv.ParseInt(string(line[1:]), 10, 64) 218 | 219 | ret.T = T_Array 220 | if nil == err { 221 | if -1 != lenArray { 222 | ret.Array = make([]*Data, lenArray) 223 | for i = 0; i < lenArray && nil == err; i++ { 224 | ret.Array[i], err = ReadData(r) 225 | } 226 | } else { 227 | ret.IsNil = true 228 | } 229 | } 230 | 231 | default: //Maybe you are Inline Command 232 | err = errors.New("Unexpected type ") 233 | 234 | } 235 | return ret, err 236 | } 237 | 238 | // 239 | func readDataBytesForSpecType(r *bufio.Reader, line []byte, obj *Object) error { 240 | switch line[0] { 241 | case T_SimpleString, T_Error, T_Integer: 242 | return nil 243 | case T_BulkString: 244 | lenBulkString, err := strconv.ParseInt(string(line[1:]), 10, 64) 245 | if err != nil { 246 | return err 247 | } 248 | if lenBulkString != -1 { 249 | buf, err := readRespN(r, lenBulkString+2) 250 | if err != nil { 251 | return err 252 | } else { 253 | obj.Append(buf) 254 | } 255 | } 256 | // else if nil 257 | 258 | case T_Array: 259 | lenArray, err := strconv.ParseInt(string(line[1:]), 10, 64) 260 | if err != nil { 261 | return err 262 | } 263 | var i int64 264 | if lenArray != -1 { 265 | for i = 0; i < lenArray; i++ { 266 | if err := ReadDataBytes(r, obj); err != nil { 267 | return err 268 | } 269 | } 270 | } 271 | // else is nil 272 | 273 | default: 274 | return errors.New("Unexpected type ") 275 | 276 | } 277 | return nil 278 | } 279 | 280 | //read a resp line and trim the last \r\n 281 | func readRespLine(r *bufio.Reader) ([]byte, error) { 282 | line, err := r.ReadBytes('\n') 283 | if err != nil { 284 | return nil, err 285 | } 286 | if n := len(line); n < 2 { 287 | return nil, PROTOCOL_ERR 288 | } else { 289 | return line[:n-2], nil 290 | } 291 | } 292 | 293 | // 294 | func readRespLineBytes(r *bufio.Reader, obj *Object) ([]byte, error) { 295 | line, err := r.ReadBytes('\n') 296 | if err != nil { 297 | return nil, err 298 | } 299 | if n := len(line); n < 2 { 300 | return nil, PROTOCOL_ERR 301 | } else { 302 | obj.Append(line) 303 | return line[:n-2], nil 304 | } 305 | } 306 | 307 | //read a redis InlineCommand 308 | func readRespCommandLine(r *bufio.Reader) ([]byte, error) { 309 | line, err := r.ReadBytes('\n') 310 | if err != nil { 311 | return nil, err 312 | } 313 | if n := len(line); n < 2 { 314 | return nil, PROTOCOL_ERR 315 | } else { 316 | return line[:n-2], nil 317 | } 318 | } 319 | 320 | //read the next N bytes 321 | func readRespN(r *bufio.Reader, n int64) ([]byte, error) { 322 | buf := make([]byte, n) 323 | if _, err := io.ReadFull(r, buf); err != nil { 324 | return nil, err 325 | } else { 326 | return buf, nil 327 | } 328 | } 329 | 330 | type Object struct { 331 | raw bytes.Buffer 332 | } 333 | 334 | func NewObject() *Object { 335 | o := &Object{} 336 | return o 337 | } 338 | 339 | func NewObjectFromData(data *Data) *Object { 340 | o := &Object{} 341 | o.Append(data.Format()) 342 | return o 343 | } 344 | 345 | func (o *Object) Append(buf []byte) { 346 | o.raw.Write(buf) 347 | } 348 | 349 | func (o *Object) Raw() []byte { 350 | return o.raw.Bytes() 351 | } 352 | 353 | // read data bytes reads a full RESP object bytes 354 | func ReadDataBytes(r *bufio.Reader, obj *Object) error { 355 | buf, err := readRespLineBytes(r, obj) 356 | if err != nil { 357 | return err 358 | } 359 | 360 | if len(buf) < 2 { 361 | return errors.New("invalid Data Source: " + string(buf)) 362 | } 363 | 364 | return readDataBytesForSpecType(r, buf, obj) 365 | } 366 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Dave Collins 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypass.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when the code is not running on Google App Engine and "-tags disableunsafe" 17 | // is not added to the go build command line. 18 | // +build !appengine,!disableunsafe 19 | 20 | package spew 21 | 22 | import ( 23 | "reflect" 24 | "unsafe" 25 | ) 26 | 27 | const ( 28 | // UnsafeDisabled is a build-time constant which specifies whether or 29 | // not access to the unsafe package is available. 30 | UnsafeDisabled = false 31 | 32 | // ptrSize is the size of a pointer on the current arch. 33 | ptrSize = unsafe.Sizeof((*byte)(nil)) 34 | ) 35 | 36 | var ( 37 | // offsetPtr, offsetScalar, and offsetFlag are the offsets for the 38 | // internal reflect.Value fields. These values are valid before golang 39 | // commit ecccf07e7f9d which changed the format. The are also valid 40 | // after commit 82f48826c6c7 which changed the format again to mirror 41 | // the original format. Code in the init function updates these offsets 42 | // as necessary. 43 | offsetPtr = uintptr(ptrSize) 44 | offsetScalar = uintptr(0) 45 | offsetFlag = uintptr(ptrSize * 2) 46 | 47 | // flagKindWidth and flagKindShift indicate various bits that the 48 | // reflect package uses internally to track kind information. 49 | // 50 | // flagRO indicates whether or not the value field of a reflect.Value is 51 | // read-only. 52 | // 53 | // flagIndir indicates whether the value field of a reflect.Value is 54 | // the actual data or a pointer to the data. 55 | // 56 | // These values are valid before golang commit 90a7c3c86944 which 57 | // changed their positions. Code in the init function updates these 58 | // flags as necessary. 59 | flagKindWidth = uintptr(5) 60 | flagKindShift = uintptr(flagKindWidth - 1) 61 | flagRO = uintptr(1 << 0) 62 | flagIndir = uintptr(1 << 1) 63 | ) 64 | 65 | func init() { 66 | // Older versions of reflect.Value stored small integers directly in the 67 | // ptr field (which is named val in the older versions). Versions 68 | // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named 69 | // scalar for this purpose which unfortunately came before the flag 70 | // field, so the offset of the flag field is different for those 71 | // versions. 72 | // 73 | // This code constructs a new reflect.Value from a known small integer 74 | // and checks if the size of the reflect.Value struct indicates it has 75 | // the scalar field. When it does, the offsets are updated accordingly. 76 | vv := reflect.ValueOf(0xf00) 77 | if unsafe.Sizeof(vv) == (ptrSize * 4) { 78 | offsetScalar = ptrSize * 2 79 | offsetFlag = ptrSize * 3 80 | } 81 | 82 | // Commit 90a7c3c86944 changed the flag positions such that the low 83 | // order bits are the kind. This code extracts the kind from the flags 84 | // field and ensures it's the correct type. When it's not, the flag 85 | // order has been changed to the newer format, so the flags are updated 86 | // accordingly. 87 | upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) 88 | upfv := *(*uintptr)(upf) 89 | flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { 91 | flagKindShift = 0 92 | flagRO = 1 << 5 93 | flagIndir = 1 << 6 94 | 95 | // Commit adf9b30e5594 modified the flags to separate the 96 | // flagRO flag into two bits which specifies whether or not the 97 | // field is embedded. This causes flagIndir to move over a bit 98 | // and means that flagRO is the combination of either of the 99 | // original flagRO bit and the new bit. 100 | // 101 | // This code detects the change by extracting what used to be 102 | // the indirect bit to ensure it's set. When it's not, the flag 103 | // order has been changed to the newer format, so the flags are 104 | // updated accordingly. 105 | if upfv&flagIndir == 0 { 106 | flagRO = 3 << 5 107 | flagIndir = 1 << 7 108 | } 109 | } 110 | } 111 | 112 | // unsafeReflectValue converts the passed reflect.Value into a one that bypasses 113 | // the typical safety restrictions preventing access to unaddressable and 114 | // unexported data. It works by digging the raw pointer to the underlying 115 | // value out of the protected value and generating a new unprotected (unsafe) 116 | // reflect.Value to it. 117 | // 118 | // This allows us to check for implementations of the Stringer and error 119 | // interfaces to be used for pretty printing ordinarily unaddressable and 120 | // inaccessible values such as unexported struct fields. 121 | func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { 122 | indirects := 1 123 | vt := v.Type() 124 | upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) 125 | rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) 126 | if rvf&flagIndir != 0 { 127 | vt = reflect.PtrTo(v.Type()) 128 | indirects++ 129 | } else if offsetScalar != 0 { 130 | // The value is in the scalar field when it's not one of the 131 | // reference types. 132 | switch vt.Kind() { 133 | case reflect.Uintptr: 134 | case reflect.Chan: 135 | case reflect.Func: 136 | case reflect.Map: 137 | case reflect.Ptr: 138 | case reflect.UnsafePointer: 139 | default: 140 | upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + 141 | offsetScalar) 142 | } 143 | } 144 | 145 | pv := reflect.NewAt(vt, upv) 146 | rv = pv 147 | for i := 0; i < indirects; i++ { 148 | rv = rv.Elem() 149 | } 150 | return rv 151 | } 152 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypasssafe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Dave Collins 2 | // 3 | // Permission to use, copy, modify, and distribute this software for any 4 | // purpose with or without fee is hereby granted, provided that the above 5 | // copyright notice and this permission notice appear in all copies. 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | // NOTE: Due to the following build constraints, this file will only be compiled 16 | // when either the code is running on Google App Engine or "-tags disableunsafe" 17 | // is added to the go build command line. 18 | // +build appengine disableunsafe 19 | 20 | package spew 21 | 22 | import "reflect" 23 | 24 | const ( 25 | // UnsafeDisabled is a build-time constant which specifies whether or 26 | // not access to the unsafe package is available. 27 | UnsafeDisabled = true 28 | ) 29 | 30 | // unsafeReflectValue typically converts the passed reflect.Value into a one 31 | // that bypasses the typical safety restrictions preventing access to 32 | // unaddressable and unexported data. However, doing this relies on access to 33 | // the unsafe package. This is a stub version which simply returns the passed 34 | // reflect.Value when the unsafe package is not available. 35 | func unsafeReflectValue(v reflect.Value) reflect.Value { 36 | return v 37 | } 38 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spew 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io" 23 | "reflect" 24 | "sort" 25 | "strconv" 26 | ) 27 | 28 | // Some constants in the form of bytes to avoid string overhead. This mirrors 29 | // the technique used in the fmt package. 30 | var ( 31 | panicBytes = []byte("(PANIC=") 32 | plusBytes = []byte("+") 33 | iBytes = []byte("i") 34 | trueBytes = []byte("true") 35 | falseBytes = []byte("false") 36 | interfaceBytes = []byte("(interface {})") 37 | commaNewlineBytes = []byte(",\n") 38 | newlineBytes = []byte("\n") 39 | openBraceBytes = []byte("{") 40 | openBraceNewlineBytes = []byte("{\n") 41 | closeBraceBytes = []byte("}") 42 | asteriskBytes = []byte("*") 43 | colonBytes = []byte(":") 44 | colonSpaceBytes = []byte(": ") 45 | openParenBytes = []byte("(") 46 | closeParenBytes = []byte(")") 47 | spaceBytes = []byte(" ") 48 | pointerChainBytes = []byte("->") 49 | nilAngleBytes = []byte("") 50 | maxNewlineBytes = []byte("\n") 51 | maxShortBytes = []byte("") 52 | circularBytes = []byte("") 53 | circularShortBytes = []byte("") 54 | invalidAngleBytes = []byte("") 55 | openBracketBytes = []byte("[") 56 | closeBracketBytes = []byte("]") 57 | percentBytes = []byte("%") 58 | precisionBytes = []byte(".") 59 | openAngleBytes = []byte("<") 60 | closeAngleBytes = []byte(">") 61 | openMapBytes = []byte("map[") 62 | closeMapBytes = []byte("]") 63 | lenEqualsBytes = []byte("len=") 64 | capEqualsBytes = []byte("cap=") 65 | ) 66 | 67 | // hexDigits is used to map a decimal value to a hex digit. 68 | var hexDigits = "0123456789abcdef" 69 | 70 | // catchPanic handles any panics that might occur during the handleMethods 71 | // calls. 72 | func catchPanic(w io.Writer, v reflect.Value) { 73 | if err := recover(); err != nil { 74 | w.Write(panicBytes) 75 | fmt.Fprintf(w, "%v", err) 76 | w.Write(closeParenBytes) 77 | } 78 | } 79 | 80 | // handleMethods attempts to call the Error and String methods on the underlying 81 | // type the passed reflect.Value represents and outputes the result to Writer w. 82 | // 83 | // It handles panics in any called methods by catching and displaying the error 84 | // as the formatted value. 85 | func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { 86 | // We need an interface to check if the type implements the error or 87 | // Stringer interface. However, the reflect package won't give us an 88 | // interface on certain things like unexported struct fields in order 89 | // to enforce visibility rules. We use unsafe, when it's available, 90 | // to bypass these restrictions since this package does not mutate the 91 | // values. 92 | if !v.CanInterface() { 93 | if UnsafeDisabled { 94 | return false 95 | } 96 | 97 | v = unsafeReflectValue(v) 98 | } 99 | 100 | // Choose whether or not to do error and Stringer interface lookups against 101 | // the base type or a pointer to the base type depending on settings. 102 | // Technically calling one of these methods with a pointer receiver can 103 | // mutate the value, however, types which choose to satisify an error or 104 | // Stringer interface with a pointer receiver should not be mutating their 105 | // state inside these interface methods. 106 | if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { 107 | v = unsafeReflectValue(v) 108 | } 109 | if v.CanAddr() { 110 | v = v.Addr() 111 | } 112 | 113 | // Is it an error or Stringer? 114 | switch iface := v.Interface().(type) { 115 | case error: 116 | defer catchPanic(w, v) 117 | if cs.ContinueOnMethod { 118 | w.Write(openParenBytes) 119 | w.Write([]byte(iface.Error())) 120 | w.Write(closeParenBytes) 121 | w.Write(spaceBytes) 122 | return false 123 | } 124 | 125 | w.Write([]byte(iface.Error())) 126 | return true 127 | 128 | case fmt.Stringer: 129 | defer catchPanic(w, v) 130 | if cs.ContinueOnMethod { 131 | w.Write(openParenBytes) 132 | w.Write([]byte(iface.String())) 133 | w.Write(closeParenBytes) 134 | w.Write(spaceBytes) 135 | return false 136 | } 137 | w.Write([]byte(iface.String())) 138 | return true 139 | } 140 | return false 141 | } 142 | 143 | // printBool outputs a boolean value as true or false to Writer w. 144 | func printBool(w io.Writer, val bool) { 145 | if val { 146 | w.Write(trueBytes) 147 | } else { 148 | w.Write(falseBytes) 149 | } 150 | } 151 | 152 | // printInt outputs a signed integer value to Writer w. 153 | func printInt(w io.Writer, val int64, base int) { 154 | w.Write([]byte(strconv.FormatInt(val, base))) 155 | } 156 | 157 | // printUint outputs an unsigned integer value to Writer w. 158 | func printUint(w io.Writer, val uint64, base int) { 159 | w.Write([]byte(strconv.FormatUint(val, base))) 160 | } 161 | 162 | // printFloat outputs a floating point value using the specified precision, 163 | // which is expected to be 32 or 64bit, to Writer w. 164 | func printFloat(w io.Writer, val float64, precision int) { 165 | w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) 166 | } 167 | 168 | // printComplex outputs a complex value using the specified float precision 169 | // for the real and imaginary parts to Writer w. 170 | func printComplex(w io.Writer, c complex128, floatPrecision int) { 171 | r := real(c) 172 | w.Write(openParenBytes) 173 | w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) 174 | i := imag(c) 175 | if i >= 0 { 176 | w.Write(plusBytes) 177 | } 178 | w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) 179 | w.Write(iBytes) 180 | w.Write(closeParenBytes) 181 | } 182 | 183 | // printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' 184 | // prefix to Writer w. 185 | func printHexPtr(w io.Writer, p uintptr) { 186 | // Null pointer. 187 | num := uint64(p) 188 | if num == 0 { 189 | w.Write(nilAngleBytes) 190 | return 191 | } 192 | 193 | // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix 194 | buf := make([]byte, 18) 195 | 196 | // It's simpler to construct the hex string right to left. 197 | base := uint64(16) 198 | i := len(buf) - 1 199 | for num >= base { 200 | buf[i] = hexDigits[num%base] 201 | num /= base 202 | i-- 203 | } 204 | buf[i] = hexDigits[num] 205 | 206 | // Add '0x' prefix. 207 | i-- 208 | buf[i] = 'x' 209 | i-- 210 | buf[i] = '0' 211 | 212 | // Strip unused leading bytes. 213 | buf = buf[i:] 214 | w.Write(buf) 215 | } 216 | 217 | // valuesSorter implements sort.Interface to allow a slice of reflect.Value 218 | // elements to be sorted. 219 | type valuesSorter struct { 220 | values []reflect.Value 221 | strings []string // either nil or same len and values 222 | cs *ConfigState 223 | } 224 | 225 | // newValuesSorter initializes a valuesSorter instance, which holds a set of 226 | // surrogate keys on which the data should be sorted. It uses flags in 227 | // ConfigState to decide if and how to populate those surrogate keys. 228 | func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { 229 | vs := &valuesSorter{values: values, cs: cs} 230 | if canSortSimply(vs.values[0].Kind()) { 231 | return vs 232 | } 233 | if !cs.DisableMethods { 234 | vs.strings = make([]string, len(values)) 235 | for i := range vs.values { 236 | b := bytes.Buffer{} 237 | if !handleMethods(cs, &b, vs.values[i]) { 238 | vs.strings = nil 239 | break 240 | } 241 | vs.strings[i] = b.String() 242 | } 243 | } 244 | if vs.strings == nil && cs.SpewKeys { 245 | vs.strings = make([]string, len(values)) 246 | for i := range vs.values { 247 | vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) 248 | } 249 | } 250 | return vs 251 | } 252 | 253 | // canSortSimply tests whether a reflect.Kind is a primitive that can be sorted 254 | // directly, or whether it should be considered for sorting by surrogate keys 255 | // (if the ConfigState allows it). 256 | func canSortSimply(kind reflect.Kind) bool { 257 | // This switch parallels valueSortLess, except for the default case. 258 | switch kind { 259 | case reflect.Bool: 260 | return true 261 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 262 | return true 263 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 264 | return true 265 | case reflect.Float32, reflect.Float64: 266 | return true 267 | case reflect.String: 268 | return true 269 | case reflect.Uintptr: 270 | return true 271 | case reflect.Array: 272 | return true 273 | } 274 | return false 275 | } 276 | 277 | // Len returns the number of values in the slice. It is part of the 278 | // sort.Interface implementation. 279 | func (s *valuesSorter) Len() int { 280 | return len(s.values) 281 | } 282 | 283 | // Swap swaps the values at the passed indices. It is part of the 284 | // sort.Interface implementation. 285 | func (s *valuesSorter) Swap(i, j int) { 286 | s.values[i], s.values[j] = s.values[j], s.values[i] 287 | if s.strings != nil { 288 | s.strings[i], s.strings[j] = s.strings[j], s.strings[i] 289 | } 290 | } 291 | 292 | // valueSortLess returns whether the first value should sort before the second 293 | // value. It is used by valueSorter.Less as part of the sort.Interface 294 | // implementation. 295 | func valueSortLess(a, b reflect.Value) bool { 296 | switch a.Kind() { 297 | case reflect.Bool: 298 | return !a.Bool() && b.Bool() 299 | case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 300 | return a.Int() < b.Int() 301 | case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 302 | return a.Uint() < b.Uint() 303 | case reflect.Float32, reflect.Float64: 304 | return a.Float() < b.Float() 305 | case reflect.String: 306 | return a.String() < b.String() 307 | case reflect.Uintptr: 308 | return a.Uint() < b.Uint() 309 | case reflect.Array: 310 | // Compare the contents of both arrays. 311 | l := a.Len() 312 | for i := 0; i < l; i++ { 313 | av := a.Index(i) 314 | bv := b.Index(i) 315 | if av.Interface() == bv.Interface() { 316 | continue 317 | } 318 | return valueSortLess(av, bv) 319 | } 320 | } 321 | return a.String() < b.String() 322 | } 323 | 324 | // Less returns whether the value at index i should sort before the 325 | // value at index j. It is part of the sort.Interface implementation. 326 | func (s *valuesSorter) Less(i, j int) bool { 327 | if s.strings == nil { 328 | return valueSortLess(s.values[i], s.values[j]) 329 | } 330 | return s.strings[i] < s.strings[j] 331 | } 332 | 333 | // sortValues is a sort function that handles both native types and any type that 334 | // can be converted to error or Stringer. Other inputs are sorted according to 335 | // their Value.String() value to ensure display stability. 336 | func sortValues(values []reflect.Value, cs *ConfigState) { 337 | if len(values) == 0 { 338 | return 339 | } 340 | sort.Sort(newValuesSorter(values, cs)) 341 | } 342 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | Package spew implements a deep pretty printer for Go data structures to aid in 19 | debugging. 20 | 21 | A quick overview of the additional features spew provides over the built-in 22 | printing facilities for Go data types are as follows: 23 | 24 | * Pointers are dereferenced and followed 25 | * Circular data structures are detected and handled properly 26 | * Custom Stringer/error interfaces are optionally invoked, including 27 | on unexported types 28 | * Custom types which only implement the Stringer/error interfaces via 29 | a pointer receiver are optionally invoked when passing non-pointer 30 | variables 31 | * Byte arrays and slices are dumped like the hexdump -C command which 32 | includes offsets, byte values in hex, and ASCII output (only when using 33 | Dump style) 34 | 35 | There are two different approaches spew allows for dumping Go data structures: 36 | 37 | * Dump style which prints with newlines, customizable indentation, 38 | and additional debug information such as types and all pointer addresses 39 | used to indirect to the final value 40 | * A custom Formatter interface that integrates cleanly with the standard fmt 41 | package and replaces %v, %+v, %#v, and %#+v to provide inline printing 42 | similar to the default %v while providing the additional functionality 43 | outlined above and passing unsupported format verbs such as %x and %q 44 | along to fmt 45 | 46 | Quick Start 47 | 48 | This section demonstrates how to quickly get started with spew. See the 49 | sections below for further details on formatting and configuration options. 50 | 51 | To dump a variable with full newlines, indentation, type, and pointer 52 | information use Dump, Fdump, or Sdump: 53 | spew.Dump(myVar1, myVar2, ...) 54 | spew.Fdump(someWriter, myVar1, myVar2, ...) 55 | str := spew.Sdump(myVar1, myVar2, ...) 56 | 57 | Alternatively, if you would prefer to use format strings with a compacted inline 58 | printing style, use the convenience wrappers Printf, Fprintf, etc with 59 | %v (most compact), %+v (adds pointer addresses), %#v (adds types), or 60 | %#+v (adds types and pointer addresses): 61 | spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) 62 | spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 63 | spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) 64 | spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 65 | 66 | Configuration Options 67 | 68 | Configuration of spew is handled by fields in the ConfigState type. For 69 | convenience, all of the top-level functions use a global state available 70 | via the spew.Config global. 71 | 72 | It is also possible to create a ConfigState instance that provides methods 73 | equivalent to the top-level functions. This allows concurrent configuration 74 | options. See the ConfigState documentation for more details. 75 | 76 | The following configuration options are available: 77 | * Indent 78 | String to use for each indentation level for Dump functions. 79 | It is a single space by default. A popular alternative is "\t". 80 | 81 | * MaxDepth 82 | Maximum number of levels to descend into nested data structures. 83 | There is no limit by default. 84 | 85 | * DisableMethods 86 | Disables invocation of error and Stringer interface methods. 87 | Method invocation is enabled by default. 88 | 89 | * DisablePointerMethods 90 | Disables invocation of error and Stringer interface methods on types 91 | which only accept pointer receivers from non-pointer variables. 92 | Pointer method invocation is enabled by default. 93 | 94 | * ContinueOnMethod 95 | Enables recursion into types after invoking error and Stringer interface 96 | methods. Recursion after method invocation is disabled by default. 97 | 98 | * SortKeys 99 | Specifies map keys should be sorted before being printed. Use 100 | this to have a more deterministic, diffable output. Note that 101 | only native types (bool, int, uint, floats, uintptr and string) 102 | and types which implement error or Stringer interfaces are 103 | supported with other types sorted according to the 104 | reflect.Value.String() output which guarantees display 105 | stability. Natural map order is used by default. 106 | 107 | * SpewKeys 108 | Specifies that, as a last resort attempt, map keys should be 109 | spewed to strings and sorted by those strings. This is only 110 | considered if SortKeys is true. 111 | 112 | Dump Usage 113 | 114 | Simply call spew.Dump with a list of variables you want to dump: 115 | 116 | spew.Dump(myVar1, myVar2, ...) 117 | 118 | You may also call spew.Fdump if you would prefer to output to an arbitrary 119 | io.Writer. For example, to dump to standard error: 120 | 121 | spew.Fdump(os.Stderr, myVar1, myVar2, ...) 122 | 123 | A third option is to call spew.Sdump to get the formatted output as a string: 124 | 125 | str := spew.Sdump(myVar1, myVar2, ...) 126 | 127 | Sample Dump Output 128 | 129 | See the Dump example for details on the setup of the types and variables being 130 | shown here. 131 | 132 | (main.Foo) { 133 | unexportedField: (*main.Bar)(0xf84002e210)({ 134 | flag: (main.Flag) flagTwo, 135 | data: (uintptr) 136 | }), 137 | ExportedField: (map[interface {}]interface {}) (len=1) { 138 | (string) (len=3) "one": (bool) true 139 | } 140 | } 141 | 142 | Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C 143 | command as shown. 144 | ([]uint8) (len=32 cap=32) { 145 | 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | 146 | 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| 147 | 00000020 31 32 |12| 148 | } 149 | 150 | Custom Formatter 151 | 152 | Spew provides a custom formatter that implements the fmt.Formatter interface 153 | so that it integrates cleanly with standard fmt package printing functions. The 154 | formatter is useful for inline printing of smaller data types similar to the 155 | standard %v format specifier. 156 | 157 | The custom formatter only responds to the %v (most compact), %+v (adds pointer 158 | addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb 159 | combinations. Any other verbs such as %x and %q will be sent to the the 160 | standard fmt package for formatting. In addition, the custom formatter ignores 161 | the width and precision arguments (however they will still work on the format 162 | specifiers not handled by the custom formatter). 163 | 164 | Custom Formatter Usage 165 | 166 | The simplest way to make use of the spew custom formatter is to call one of the 167 | convenience functions such as spew.Printf, spew.Println, or spew.Printf. The 168 | functions have syntax you are most likely already familiar with: 169 | 170 | spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) 171 | spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 172 | spew.Println(myVar, myVar2) 173 | spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) 174 | spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 175 | 176 | See the Index for the full list convenience functions. 177 | 178 | Sample Formatter Output 179 | 180 | Double pointer to a uint8: 181 | %v: <**>5 182 | %+v: <**>(0xf8400420d0->0xf8400420c8)5 183 | %#v: (**uint8)5 184 | %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 185 | 186 | Pointer to circular struct with a uint8 field and a pointer to itself: 187 | %v: <*>{1 <*>} 188 | %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} 189 | %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} 190 | %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} 191 | 192 | See the Printf example for details on the setup of variables being shown 193 | here. 194 | 195 | Errors 196 | 197 | Since it is possible for custom Stringer/error interfaces to panic, spew 198 | detects them and handles them internally by printing the panic information 199 | inline with the output. Since spew is intended to provide deep pretty printing 200 | capabilities on structures, it intentionally does not return any errors. 201 | */ 202 | package spew 203 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/spew.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 Dave Collins 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | package spew 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | ) 23 | 24 | // Errorf is a wrapper for fmt.Errorf that treats each argument as if it were 25 | // passed with a default Formatter interface returned by NewFormatter. It 26 | // returns the formatted string as a value that satisfies error. See 27 | // NewFormatter for formatting details. 28 | // 29 | // This function is shorthand for the following syntax: 30 | // 31 | // fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) 32 | func Errorf(format string, a ...interface{}) (err error) { 33 | return fmt.Errorf(format, convertArgs(a)...) 34 | } 35 | 36 | // Fprint is a wrapper for fmt.Fprint that treats each argument as if it were 37 | // passed with a default Formatter interface returned by NewFormatter. It 38 | // returns the number of bytes written and any write error encountered. See 39 | // NewFormatter for formatting details. 40 | // 41 | // This function is shorthand for the following syntax: 42 | // 43 | // fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) 44 | func Fprint(w io.Writer, a ...interface{}) (n int, err error) { 45 | return fmt.Fprint(w, convertArgs(a)...) 46 | } 47 | 48 | // Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were 49 | // passed with a default Formatter interface returned by NewFormatter. It 50 | // returns the number of bytes written and any write error encountered. See 51 | // NewFormatter for formatting details. 52 | // 53 | // This function is shorthand for the following syntax: 54 | // 55 | // fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) 56 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { 57 | return fmt.Fprintf(w, format, convertArgs(a)...) 58 | } 59 | 60 | // Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it 61 | // passed with a default Formatter interface returned by NewFormatter. See 62 | // NewFormatter for formatting details. 63 | // 64 | // This function is shorthand for the following syntax: 65 | // 66 | // fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) 67 | func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { 68 | return fmt.Fprintln(w, convertArgs(a)...) 69 | } 70 | 71 | // Print is a wrapper for fmt.Print that treats each argument as if it were 72 | // passed with a default Formatter interface returned by NewFormatter. It 73 | // returns the number of bytes written and any write error encountered. See 74 | // NewFormatter for formatting details. 75 | // 76 | // This function is shorthand for the following syntax: 77 | // 78 | // fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) 79 | func Print(a ...interface{}) (n int, err error) { 80 | return fmt.Print(convertArgs(a)...) 81 | } 82 | 83 | // Printf is a wrapper for fmt.Printf that treats each argument as if it were 84 | // passed with a default Formatter interface returned by NewFormatter. It 85 | // returns the number of bytes written and any write error encountered. See 86 | // NewFormatter for formatting details. 87 | // 88 | // This function is shorthand for the following syntax: 89 | // 90 | // fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) 91 | func Printf(format string, a ...interface{}) (n int, err error) { 92 | return fmt.Printf(format, convertArgs(a)...) 93 | } 94 | 95 | // Println is a wrapper for fmt.Println that treats each argument as if it were 96 | // passed with a default Formatter interface returned by NewFormatter. It 97 | // returns the number of bytes written and any write error encountered. See 98 | // NewFormatter for formatting details. 99 | // 100 | // This function is shorthand for the following syntax: 101 | // 102 | // fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) 103 | func Println(a ...interface{}) (n int, err error) { 104 | return fmt.Println(convertArgs(a)...) 105 | } 106 | 107 | // Sprint is a wrapper for fmt.Sprint that treats each argument as if it were 108 | // passed with a default Formatter interface returned by NewFormatter. It 109 | // returns the resulting string. See NewFormatter for formatting details. 110 | // 111 | // This function is shorthand for the following syntax: 112 | // 113 | // fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) 114 | func Sprint(a ...interface{}) string { 115 | return fmt.Sprint(convertArgs(a)...) 116 | } 117 | 118 | // Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were 119 | // passed with a default Formatter interface returned by NewFormatter. It 120 | // returns the resulting string. See NewFormatter for formatting details. 121 | // 122 | // This function is shorthand for the following syntax: 123 | // 124 | // fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) 125 | func Sprintf(format string, a ...interface{}) string { 126 | return fmt.Sprintf(format, convertArgs(a)...) 127 | } 128 | 129 | // Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it 130 | // were passed with a default Formatter interface returned by NewFormatter. It 131 | // returns the resulting string. See NewFormatter for formatting details. 132 | // 133 | // This function is shorthand for the following syntax: 134 | // 135 | // fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) 136 | func Sprintln(a ...interface{}) string { 137 | return fmt.Sprintln(convertArgs(a)...) 138 | } 139 | 140 | // convertArgs accepts a slice of arguments and returns a slice of the same 141 | // length with each argument converted to a default spew Formatter interface. 142 | func convertArgs(args []interface{}) (formatters []interface{}) { 143 | formatters = make([]interface{}, len(args)) 144 | for index, arg := range args { 145 | formatters[index] = NewFormatter(arg) 146 | } 147 | return formatters 148 | } 149 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/internal/commandinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package internal // import "github.com/garyburd/redigo/internal" 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | const ( 22 | WatchState = 1 << iota 23 | MultiState 24 | SubscribeState 25 | MonitorState 26 | ) 27 | 28 | type CommandInfo struct { 29 | Set, Clear int 30 | } 31 | 32 | var commandInfos = map[string]CommandInfo{ 33 | "WATCH": {Set: WatchState}, 34 | "UNWATCH": {Clear: WatchState}, 35 | "MULTI": {Set: MultiState}, 36 | "EXEC": {Clear: WatchState | MultiState}, 37 | "DISCARD": {Clear: WatchState | MultiState}, 38 | "PSUBSCRIBE": {Set: SubscribeState}, 39 | "SUBSCRIBE": {Set: SubscribeState}, 40 | "MONITOR": {Set: MonitorState}, 41 | } 42 | 43 | func LookupCommandInfo(commandName string) CommandInfo { 44 | return commandInfos[strings.ToUpper(commandName)] 45 | } 46 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "errors" 21 | "fmt" 22 | "io" 23 | "net" 24 | "strconv" 25 | "sync" 26 | "time" 27 | ) 28 | 29 | // conn is the low-level implementation of Conn 30 | type conn struct { 31 | 32 | // Shared 33 | mu sync.Mutex 34 | pending int 35 | err error 36 | conn net.Conn 37 | 38 | // Read 39 | readTimeout time.Duration 40 | br *bufio.Reader 41 | 42 | // Write 43 | writeTimeout time.Duration 44 | bw *bufio.Writer 45 | 46 | // Scratch space for formatting argument length. 47 | // '*' or '$', length, "\r\n" 48 | lenScratch [32]byte 49 | 50 | // Scratch space for formatting integers and floats. 51 | numScratch [40]byte 52 | } 53 | 54 | // Dial connects to the Redis server at the given network and address. 55 | func Dial(network, address string) (Conn, error) { 56 | dialer := xDialer{} 57 | return dialer.Dial(network, address) 58 | } 59 | 60 | // DialTimeout acts like Dial but takes timeouts for establishing the 61 | // connection to the server, writing a command and reading a reply. 62 | func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { 63 | netDialer := net.Dialer{Timeout: connectTimeout} 64 | dialer := xDialer{ 65 | NetDial: netDialer.Dial, 66 | ReadTimeout: readTimeout, 67 | WriteTimeout: writeTimeout, 68 | } 69 | return dialer.Dial(network, address) 70 | } 71 | 72 | // A Dialer specifies options for connecting to a Redis server. 73 | type xDialer struct { 74 | // NetDial specifies the dial function for creating TCP connections. If 75 | // NetDial is nil, then net.Dial is used. 76 | NetDial func(network, addr string) (net.Conn, error) 77 | 78 | // ReadTimeout specifies the timeout for reading a single command 79 | // reply. If ReadTimeout is zero, then no timeout is used. 80 | ReadTimeout time.Duration 81 | 82 | // WriteTimeout specifies the timeout for writing a single command. If 83 | // WriteTimeout is zero, then no timeout is used. 84 | WriteTimeout time.Duration 85 | } 86 | 87 | // Dial connects to the Redis server at address on the named network. 88 | func (d *xDialer) Dial(network, address string) (Conn, error) { 89 | dial := d.NetDial 90 | if dial == nil { 91 | dial = net.Dial 92 | } 93 | netConn, err := dial(network, address) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return &conn{ 98 | conn: netConn, 99 | bw: bufio.NewWriter(netConn), 100 | br: bufio.NewReader(netConn), 101 | readTimeout: d.ReadTimeout, 102 | writeTimeout: d.WriteTimeout, 103 | }, nil 104 | } 105 | 106 | // NewConn returns a new Redigo connection for the given net connection. 107 | func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { 108 | return &conn{ 109 | conn: netConn, 110 | bw: bufio.NewWriter(netConn), 111 | br: bufio.NewReader(netConn), 112 | readTimeout: readTimeout, 113 | writeTimeout: writeTimeout, 114 | } 115 | } 116 | 117 | func (c *conn) Close() error { 118 | c.mu.Lock() 119 | err := c.err 120 | if c.err == nil { 121 | c.err = errors.New("redigo: closed") 122 | err = c.conn.Close() 123 | } 124 | c.mu.Unlock() 125 | return err 126 | } 127 | 128 | func (c *conn) fatal(err error) error { 129 | c.mu.Lock() 130 | if c.err == nil { 131 | c.err = err 132 | // Close connection to force errors on subsequent calls and to unblock 133 | // other reader or writer. 134 | c.conn.Close() 135 | } 136 | c.mu.Unlock() 137 | return err 138 | } 139 | 140 | func (c *conn) Err() error { 141 | c.mu.Lock() 142 | err := c.err 143 | c.mu.Unlock() 144 | return err 145 | } 146 | 147 | func (c *conn) writeLen(prefix byte, n int) error { 148 | c.lenScratch[len(c.lenScratch)-1] = '\n' 149 | c.lenScratch[len(c.lenScratch)-2] = '\r' 150 | i := len(c.lenScratch) - 3 151 | for { 152 | c.lenScratch[i] = byte('0' + n%10) 153 | i -= 1 154 | n = n / 10 155 | if n == 0 { 156 | break 157 | } 158 | } 159 | c.lenScratch[i] = prefix 160 | _, err := c.bw.Write(c.lenScratch[i:]) 161 | return err 162 | } 163 | 164 | func (c *conn) writeString(s string) error { 165 | c.writeLen('$', len(s)) 166 | c.bw.WriteString(s) 167 | _, err := c.bw.WriteString("\r\n") 168 | return err 169 | } 170 | 171 | func (c *conn) writeBytes(p []byte) error { 172 | c.writeLen('$', len(p)) 173 | c.bw.Write(p) 174 | _, err := c.bw.WriteString("\r\n") 175 | return err 176 | } 177 | 178 | func (c *conn) writeInt64(n int64) error { 179 | return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) 180 | } 181 | 182 | func (c *conn) writeFloat64(n float64) error { 183 | return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) 184 | } 185 | 186 | func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { 187 | c.writeLen('*', 1+len(args)) 188 | err = c.writeString(cmd) 189 | for _, arg := range args { 190 | if err != nil { 191 | break 192 | } 193 | switch arg := arg.(type) { 194 | case string: 195 | err = c.writeString(arg) 196 | case []byte: 197 | err = c.writeBytes(arg) 198 | case int: 199 | err = c.writeInt64(int64(arg)) 200 | case int64: 201 | err = c.writeInt64(arg) 202 | case float64: 203 | err = c.writeFloat64(arg) 204 | case bool: 205 | if arg { 206 | err = c.writeString("1") 207 | } else { 208 | err = c.writeString("0") 209 | } 210 | case nil: 211 | err = c.writeString("") 212 | default: 213 | var buf bytes.Buffer 214 | fmt.Fprint(&buf, arg) 215 | err = c.writeBytes(buf.Bytes()) 216 | } 217 | } 218 | return err 219 | } 220 | 221 | type protocolError string 222 | 223 | func (pe protocolError) Error() string { 224 | return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) 225 | } 226 | 227 | func (c *conn) readLine() ([]byte, error) { 228 | p, err := c.br.ReadSlice('\n') 229 | if err == bufio.ErrBufferFull { 230 | return nil, protocolError("long response line") 231 | } 232 | if err != nil { 233 | return nil, err 234 | } 235 | i := len(p) - 2 236 | if i < 0 || p[i] != '\r' { 237 | return nil, protocolError("bad response line terminator") 238 | } 239 | return p[:i], nil 240 | } 241 | 242 | // parseLen parses bulk string and array lengths. 243 | func parseLen(p []byte) (int, error) { 244 | if len(p) == 0 { 245 | return -1, protocolError("malformed length") 246 | } 247 | 248 | if p[0] == '-' && len(p) == 2 && p[1] == '1' { 249 | // handle $-1 and $-1 null replies. 250 | return -1, nil 251 | } 252 | 253 | var n int 254 | for _, b := range p { 255 | n *= 10 256 | if b < '0' || b > '9' { 257 | return -1, protocolError("illegal bytes in length") 258 | } 259 | n += int(b - '0') 260 | } 261 | 262 | return n, nil 263 | } 264 | 265 | // parseInt parses an integer reply. 266 | func parseInt(p []byte) (interface{}, error) { 267 | if len(p) == 0 { 268 | return 0, protocolError("malformed integer") 269 | } 270 | 271 | var negate bool 272 | if p[0] == '-' { 273 | negate = true 274 | p = p[1:] 275 | if len(p) == 0 { 276 | return 0, protocolError("malformed integer") 277 | } 278 | } 279 | 280 | var n int64 281 | for _, b := range p { 282 | n *= 10 283 | if b < '0' || b > '9' { 284 | return 0, protocolError("illegal bytes in length") 285 | } 286 | n += int64(b - '0') 287 | } 288 | 289 | if negate { 290 | n = -n 291 | } 292 | return n, nil 293 | } 294 | 295 | var ( 296 | okReply interface{} = "OK" 297 | pongReply interface{} = "PONG" 298 | ) 299 | 300 | func (c *conn) readReply() (interface{}, error) { 301 | line, err := c.readLine() 302 | if err != nil { 303 | return nil, err 304 | } 305 | if len(line) == 0 { 306 | return nil, protocolError("short response line") 307 | } 308 | switch line[0] { 309 | case '+': 310 | switch { 311 | case len(line) == 3 && line[1] == 'O' && line[2] == 'K': 312 | // Avoid allocation for frequent "+OK" response. 313 | return okReply, nil 314 | case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': 315 | // Avoid allocation in PING command benchmarks :) 316 | return pongReply, nil 317 | default: 318 | return string(line[1:]), nil 319 | } 320 | case '-': 321 | return Error(string(line[1:])), nil 322 | case ':': 323 | return parseInt(line[1:]) 324 | case '$': 325 | n, err := parseLen(line[1:]) 326 | if n < 0 || err != nil { 327 | return nil, err 328 | } 329 | p := make([]byte, n) 330 | _, err = io.ReadFull(c.br, p) 331 | if err != nil { 332 | return nil, err 333 | } 334 | if line, err := c.readLine(); err != nil { 335 | return nil, err 336 | } else if len(line) != 0 { 337 | return nil, protocolError("bad bulk string format") 338 | } 339 | return p, nil 340 | case '*': 341 | n, err := parseLen(line[1:]) 342 | if n < 0 || err != nil { 343 | return nil, err 344 | } 345 | r := make([]interface{}, n) 346 | for i := range r { 347 | r[i], err = c.readReply() 348 | if err != nil { 349 | return nil, err 350 | } 351 | } 352 | return r, nil 353 | } 354 | return nil, protocolError("unexpected response line") 355 | } 356 | 357 | func (c *conn) Send(cmd string, args ...interface{}) error { 358 | c.mu.Lock() 359 | c.pending += 1 360 | c.mu.Unlock() 361 | if c.writeTimeout != 0 { 362 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 363 | } 364 | if err := c.writeCommand(cmd, args); err != nil { 365 | return c.fatal(err) 366 | } 367 | return nil 368 | } 369 | 370 | func (c *conn) Flush() error { 371 | if c.writeTimeout != 0 { 372 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 373 | } 374 | if err := c.bw.Flush(); err != nil { 375 | return c.fatal(err) 376 | } 377 | return nil 378 | } 379 | 380 | func (c *conn) Receive() (reply interface{}, err error) { 381 | if c.readTimeout != 0 { 382 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 383 | } 384 | if reply, err = c.readReply(); err != nil { 385 | return nil, c.fatal(err) 386 | } 387 | // When using pub/sub, the number of receives can be greater than the 388 | // number of sends. To enable normal use of the connection after 389 | // unsubscribing from all channels, we do not decrement pending to a 390 | // negative value. 391 | // 392 | // The pending field is decremented after the reply is read to handle the 393 | // case where Receive is called before Send. 394 | c.mu.Lock() 395 | if c.pending > 0 { 396 | c.pending -= 1 397 | } 398 | c.mu.Unlock() 399 | if err, ok := reply.(Error); ok { 400 | return nil, err 401 | } 402 | return 403 | } 404 | 405 | func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { 406 | c.mu.Lock() 407 | pending := c.pending 408 | c.pending = 0 409 | c.mu.Unlock() 410 | 411 | if cmd == "" && pending == 0 { 412 | return nil, nil 413 | } 414 | 415 | if c.writeTimeout != 0 { 416 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 417 | } 418 | 419 | if cmd != "" { 420 | c.writeCommand(cmd, args) 421 | } 422 | 423 | if err := c.bw.Flush(); err != nil { 424 | return nil, c.fatal(err) 425 | } 426 | 427 | if c.readTimeout != 0 { 428 | c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) 429 | } 430 | 431 | if cmd == "" { 432 | reply := make([]interface{}, pending) 433 | for i := range reply { 434 | r, e := c.readReply() 435 | if e != nil { 436 | return nil, c.fatal(e) 437 | } 438 | reply[i] = r 439 | } 440 | return reply, nil 441 | } 442 | 443 | var err error 444 | var reply interface{} 445 | for i := 0; i <= pending; i++ { 446 | var e error 447 | if reply, e = c.readReply(); e != nil { 448 | return nil, c.fatal(e) 449 | } 450 | if e, ok := reply.(Error); ok && err == nil { 451 | err = e 452 | } 453 | } 454 | return reply, err 455 | } 456 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | // Package redis is a client for the Redis database. 16 | // 17 | // The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more 18 | // documentation about this package. 19 | // 20 | // Connections 21 | // 22 | // The Conn interface is the primary interface for working with Redis. 23 | // Applications create connections by calling the Dial, DialWithTimeout or 24 | // NewConn functions. In the future, functions will be added for creating 25 | // sharded and other types of connections. 26 | // 27 | // The application must call the connection Close method when the application 28 | // is done with the connection. 29 | // 30 | // Executing Commands 31 | // 32 | // The Conn interface has a generic method for executing Redis commands: 33 | // 34 | // Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | // 36 | // The Redis command reference (http://redis.io/commands) lists the available 37 | // commands. An example of using the Redis APPEND command is: 38 | // 39 | // n, err := conn.Do("APPEND", "key", "value") 40 | // 41 | // The Do method converts command arguments to binary strings for transmission 42 | // to the server as follows: 43 | // 44 | // Go Type Conversion 45 | // []byte Sent as is 46 | // string Sent as is 47 | // int, int64 strconv.FormatInt(v) 48 | // float64 strconv.FormatFloat(v, 'g', -1, 64) 49 | // bool true -> "1", false -> "0" 50 | // nil "" 51 | // all other types fmt.Print(v) 52 | // 53 | // Redis command reply types are represented using the following Go types: 54 | // 55 | // Redis type Go type 56 | // error redis.Error 57 | // integer int64 58 | // simple string string 59 | // bulk string []byte or nil if value not present. 60 | // array []interface{} or nil if value not present. 61 | // 62 | // Use type assertions or the reply helper functions to convert from 63 | // interface{} to the specific Go type for the command result. 64 | // 65 | // Pipelining 66 | // 67 | // Connections support pipelining using the Send, Flush and Receive methods. 68 | // 69 | // Send(commandName string, args ...interface{}) error 70 | // Flush() error 71 | // Receive() (reply interface{}, err error) 72 | // 73 | // Send writes the command to the connection's output buffer. Flush flushes the 74 | // connection's output buffer to the server. Receive reads a single reply from 75 | // the server. The following example shows a simple pipeline. 76 | // 77 | // c.Send("SET", "foo", "bar") 78 | // c.Send("GET", "foo") 79 | // c.Flush() 80 | // c.Receive() // reply from SET 81 | // v, err = c.Receive() // reply from GET 82 | // 83 | // The Do method combines the functionality of the Send, Flush and Receive 84 | // methods. The Do method starts by writing the command and flushing the output 85 | // buffer. Next, the Do method receives all pending replies including the reply 86 | // for the command just sent by Do. If any of the received replies is an error, 87 | // then Do returns the error. If there are no errors, then Do returns the last 88 | // reply. If the command argument to the Do method is "", then the Do method 89 | // will flush the output buffer and receive pending replies without sending a 90 | // command. 91 | // 92 | // Use the Send and Do methods to implement pipelined transactions. 93 | // 94 | // c.Send("MULTI") 95 | // c.Send("INCR", "foo") 96 | // c.Send("INCR", "bar") 97 | // r, err := c.Do("EXEC") 98 | // fmt.Println(r) // prints [1, 1] 99 | // 100 | // Concurrency 101 | // 102 | // Connections do not support concurrent calls to the write methods (Send, 103 | // Flush) or concurrent calls to the read method (Receive). Connections do 104 | // allow a concurrent reader and writer. 105 | // 106 | // Because the Do method combines the functionality of Send, Flush and Receive, 107 | // the Do method cannot be called concurrently with the other methods. 108 | // 109 | // For full concurrent access to Redis, use the thread-safe Pool to get and 110 | // release connections from within a goroutine. 111 | // 112 | // Publish and Subscribe 113 | // 114 | // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. 115 | // 116 | // c.Send("SUBSCRIBE", "example") 117 | // c.Flush() 118 | // for { 119 | // reply, err := c.Receive() 120 | // if err != nil { 121 | // return err 122 | // } 123 | // // process pushed message 124 | // } 125 | // 126 | // The PubSubConn type wraps a Conn with convenience methods for implementing 127 | // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods 128 | // send and flush a subscription management command. The receive method 129 | // converts a pushed message to convenient types for use in a type switch. 130 | // 131 | // psc := redis.PubSubConn{c} 132 | // psc.Subscribe("example") 133 | // for { 134 | // switch v := psc.Receive().(type) { 135 | // case redis.Message: 136 | // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) 137 | // case redis.Subscription: 138 | // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 139 | // case error: 140 | // return v 141 | // } 142 | // } 143 | // 144 | // Reply Helpers 145 | // 146 | // The Bool, Int, Bytes, String, Strings and Values functions convert a reply 147 | // to a value of a specific type. To allow convenient wrapping of calls to the 148 | // connection Do and Receive methods, the functions take a second argument of 149 | // type error. If the error is non-nil, then the helper function returns the 150 | // error. If the error is nil, the function converts the reply to the specified 151 | // type: 152 | // 153 | // exists, err := redis.Bool(c.Do("EXISTS", "foo")) 154 | // if err != nil { 155 | // // handle error return from c.Do or type conversion error. 156 | // } 157 | // 158 | // The Scan function converts elements of a array reply to Go types: 159 | // 160 | // var value1 int 161 | // var value2 string 162 | // reply, err := redis.Values(c.Do("MGET", "key1", "key2")) 163 | // if err != nil { 164 | // // handle error 165 | // } 166 | // if _, err := redis.Scan(reply, &value1, &value2); err != nil { 167 | // // handle error 168 | // } 169 | package redis // import "github.com/garyburd/redigo/redis" 170 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "log" 21 | ) 22 | 23 | // NewLoggingConn returns a logging wrapper around a connection. 24 | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { 25 | if prefix != "" { 26 | prefix = prefix + "." 27 | } 28 | return &loggingConn{conn, logger, prefix} 29 | } 30 | 31 | type loggingConn struct { 32 | Conn 33 | logger *log.Logger 34 | prefix string 35 | } 36 | 37 | func (c *loggingConn) Close() error { 38 | err := c.Conn.Close() 39 | var buf bytes.Buffer 40 | fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) 41 | c.logger.Output(2, buf.String()) 42 | return err 43 | } 44 | 45 | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { 46 | const chop = 32 47 | switch v := v.(type) { 48 | case []byte: 49 | if len(v) > chop { 50 | fmt.Fprintf(buf, "%q...", v[:chop]) 51 | } else { 52 | fmt.Fprintf(buf, "%q", v) 53 | } 54 | case string: 55 | if len(v) > chop { 56 | fmt.Fprintf(buf, "%q...", v[:chop]) 57 | } else { 58 | fmt.Fprintf(buf, "%q", v) 59 | } 60 | case []interface{}: 61 | if len(v) == 0 { 62 | buf.WriteString("[]") 63 | } else { 64 | sep := "[" 65 | fin := "]" 66 | if len(v) > chop { 67 | v = v[:chop] 68 | fin = "...]" 69 | } 70 | for _, vv := range v { 71 | buf.WriteString(sep) 72 | c.printValue(buf, vv) 73 | sep = ", " 74 | } 75 | buf.WriteString(fin) 76 | } 77 | default: 78 | fmt.Fprint(buf, v) 79 | } 80 | } 81 | 82 | func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { 83 | var buf bytes.Buffer 84 | fmt.Fprintf(&buf, "%s%s(", c.prefix, method) 85 | if method != "Receive" { 86 | buf.WriteString(commandName) 87 | for _, arg := range args { 88 | buf.WriteString(", ") 89 | c.printValue(&buf, arg) 90 | } 91 | } 92 | buf.WriteString(") -> (") 93 | if method != "Send" { 94 | c.printValue(&buf, reply) 95 | buf.WriteString(", ") 96 | } 97 | fmt.Fprintf(&buf, "%v)", err) 98 | c.logger.Output(3, buf.String()) 99 | } 100 | 101 | func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { 102 | reply, err := c.Conn.Do(commandName, args...) 103 | c.print("Do", commandName, args, reply, err) 104 | return reply, err 105 | } 106 | 107 | func (c *loggingConn) Send(commandName string, args ...interface{}) error { 108 | err := c.Conn.Send(commandName, args...) 109 | c.print("Send", commandName, args, nil, err) 110 | return err 111 | } 112 | 113 | func (c *loggingConn) Receive() (interface{}, error) { 114 | reply, err := c.Conn.Receive() 115 | c.print("Receive", "", nil, reply, err) 116 | return reply, err 117 | } 118 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "bytes" 19 | "container/list" 20 | "crypto/rand" 21 | "crypto/sha1" 22 | "errors" 23 | "io" 24 | "strconv" 25 | "sync" 26 | "time" 27 | 28 | "github.com/garyburd/redigo/internal" 29 | ) 30 | 31 | var nowFunc = time.Now // for testing 32 | 33 | // ErrPoolExhausted is returned from a pool connection method (Do, Send, 34 | // Receive, Flush, Err) when the maximum number of database connections in the 35 | // pool has been reached. 36 | var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") 37 | 38 | var ( 39 | errPoolClosed = errors.New("redigo: connection pool closed") 40 | errConnClosed = errors.New("redigo: connection closed") 41 | ) 42 | 43 | // Pool maintains a pool of connections. The application calls the Get method 44 | // to get a connection from the pool and the connection's Close method to 45 | // return the connection's resources to the pool. 46 | // 47 | // The following example shows how to use a pool in a web application. The 48 | // application creates a pool at application startup and makes it available to 49 | // request handlers using a global variable. 50 | // 51 | // func newPool(server, password string) *redis.Pool { 52 | // return &redis.Pool{ 53 | // MaxIdle: 3, 54 | // IdleTimeout: 240 * time.Second, 55 | // Dial: func () (redis.Conn, error) { 56 | // c, err := redis.Dial("tcp", server) 57 | // if err != nil { 58 | // return nil, err 59 | // } 60 | // if _, err := c.Do("AUTH", password); err != nil { 61 | // c.Close() 62 | // return nil, err 63 | // } 64 | // return c, err 65 | // }, 66 | // TestOnBorrow: func(c redis.Conn, t time.Time) error { 67 | // _, err := c.Do("PING") 68 | // return err 69 | // }, 70 | // } 71 | // } 72 | // 73 | // var ( 74 | // pool *redis.Pool 75 | // redisServer = flag.String("redisServer", ":6379", "") 76 | // redisPassword = flag.String("redisPassword", "", "") 77 | // ) 78 | // 79 | // func main() { 80 | // flag.Parse() 81 | // pool = newPool(*redisServer, *redisPassword) 82 | // ... 83 | // } 84 | // 85 | // A request handler gets a connection from the pool and closes the connection 86 | // when the handler is done: 87 | // 88 | // func serveHome(w http.ResponseWriter, r *http.Request) { 89 | // conn := pool.Get() 90 | // defer conn.Close() 91 | // .... 92 | // } 93 | // 94 | type Pool struct { 95 | 96 | // Dial is an application supplied function for creating and configuring a 97 | // connection 98 | Dial func() (Conn, error) 99 | 100 | // TestOnBorrow is an optional application supplied function for checking 101 | // the health of an idle connection before the connection is used again by 102 | // the application. Argument t is the time that the connection was returned 103 | // to the pool. If the function returns an error, then the connection is 104 | // closed. 105 | TestOnBorrow func(c Conn, t time.Time) error 106 | 107 | // Maximum number of idle connections in the pool. 108 | MaxIdle int 109 | 110 | // Maximum number of connections allocated by the pool at a given time. 111 | // When zero, there is no limit on the number of connections in the pool. 112 | MaxActive int 113 | 114 | // Close connections after remaining idle for this duration. If the value 115 | // is zero, then idle connections are not closed. Applications should set 116 | // the timeout to a value less than the server's timeout. 117 | IdleTimeout time.Duration 118 | 119 | // If Wait is true and the pool is at the MaxIdle limit, then Get() waits 120 | // for a connection to be returned to the pool before returning. 121 | Wait bool 122 | 123 | // mu protects fields defined below. 124 | mu sync.Mutex 125 | cond *sync.Cond 126 | closed bool 127 | active int 128 | 129 | // Stack of idleConn with most recently used at the front. 130 | idle list.List 131 | } 132 | 133 | type idleConn struct { 134 | c Conn 135 | t time.Time 136 | } 137 | 138 | // NewPool creates a new pool. This function is deprecated. Applications should 139 | // initialize the Pool fields directly as shown in example. 140 | func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { 141 | return &Pool{Dial: newFn, MaxIdle: maxIdle} 142 | } 143 | 144 | // Get gets a connection. The application must close the returned connection. 145 | // This method always returns a valid connection so that applications can defer 146 | // error handling to the first use of the connection. If there is an error 147 | // getting an underlying connection, then the connection Err, Do, Send, Flush 148 | // and Receive methods return that error. 149 | func (p *Pool) Get() Conn { 150 | c, err := p.get() 151 | if err != nil { 152 | return errorConnection{err} 153 | } 154 | return &pooledConnection{p: p, c: c} 155 | } 156 | 157 | // ActiveCount returns the number of active connections in the pool. 158 | func (p *Pool) ActiveCount() int { 159 | p.mu.Lock() 160 | active := p.active 161 | p.mu.Unlock() 162 | return active 163 | } 164 | 165 | // Close releases the resources used by the pool. 166 | func (p *Pool) Close() error { 167 | p.mu.Lock() 168 | idle := p.idle 169 | p.idle.Init() 170 | p.closed = true 171 | p.active -= idle.Len() 172 | if p.cond != nil { 173 | p.cond.Broadcast() 174 | } 175 | p.mu.Unlock() 176 | for e := idle.Front(); e != nil; e = e.Next() { 177 | e.Value.(idleConn).c.Close() 178 | } 179 | return nil 180 | } 181 | 182 | // release decrements the active count and signals waiters. The caller must 183 | // hold p.mu during the call. 184 | func (p *Pool) release() { 185 | p.active -= 1 186 | if p.cond != nil { 187 | p.cond.Signal() 188 | } 189 | } 190 | 191 | // get prunes stale connections and returns a connection from the idle list or 192 | // creates a new connection. 193 | func (p *Pool) get() (Conn, error) { 194 | p.mu.Lock() 195 | 196 | // Prune stale connections. 197 | 198 | if timeout := p.IdleTimeout; timeout > 0 { 199 | for i, n := 0, p.idle.Len(); i < n; i++ { 200 | e := p.idle.Back() 201 | if e == nil { 202 | break 203 | } 204 | ic := e.Value.(idleConn) 205 | if ic.t.Add(timeout).After(nowFunc()) { 206 | break 207 | } 208 | p.idle.Remove(e) 209 | p.release() 210 | p.mu.Unlock() 211 | ic.c.Close() 212 | p.mu.Lock() 213 | } 214 | } 215 | 216 | for { 217 | 218 | // Get idle connection. 219 | 220 | for i, n := 0, p.idle.Len(); i < n; i++ { 221 | e := p.idle.Front() 222 | if e == nil { 223 | break 224 | } 225 | ic := e.Value.(idleConn) 226 | p.idle.Remove(e) 227 | test := p.TestOnBorrow 228 | p.mu.Unlock() 229 | if test == nil || test(ic.c, ic.t) == nil { 230 | return ic.c, nil 231 | } 232 | ic.c.Close() 233 | p.mu.Lock() 234 | p.release() 235 | } 236 | 237 | // Check for pool closed before dialing a new connection. 238 | 239 | if p.closed { 240 | p.mu.Unlock() 241 | return nil, errors.New("redigo: get on closed pool") 242 | } 243 | 244 | // Dial new connection if under limit. 245 | 246 | if p.MaxActive == 0 || p.active < p.MaxActive { 247 | dial := p.Dial 248 | p.active += 1 249 | p.mu.Unlock() 250 | c, err := dial() 251 | if err != nil { 252 | p.mu.Lock() 253 | p.release() 254 | p.mu.Unlock() 255 | c = nil 256 | } 257 | return c, err 258 | } 259 | 260 | if !p.Wait { 261 | p.mu.Unlock() 262 | return nil, ErrPoolExhausted 263 | } 264 | 265 | if p.cond == nil { 266 | p.cond = sync.NewCond(&p.mu) 267 | } 268 | p.cond.Wait() 269 | } 270 | } 271 | 272 | func (p *Pool) put(c Conn, forceClose bool) error { 273 | err := c.Err() 274 | p.mu.Lock() 275 | if !p.closed && err == nil && !forceClose { 276 | p.idle.PushFront(idleConn{t: nowFunc(), c: c}) 277 | if p.idle.Len() > p.MaxIdle { 278 | c = p.idle.Remove(p.idle.Back()).(idleConn).c 279 | } else { 280 | c = nil 281 | } 282 | } 283 | 284 | if c == nil { 285 | if p.cond != nil { 286 | p.cond.Signal() 287 | } 288 | p.mu.Unlock() 289 | return nil 290 | } 291 | 292 | p.release() 293 | p.mu.Unlock() 294 | return c.Close() 295 | } 296 | 297 | type pooledConnection struct { 298 | p *Pool 299 | c Conn 300 | state int 301 | } 302 | 303 | var ( 304 | sentinel []byte 305 | sentinelOnce sync.Once 306 | ) 307 | 308 | func initSentinel() { 309 | p := make([]byte, 64) 310 | if _, err := rand.Read(p); err == nil { 311 | sentinel = p 312 | } else { 313 | h := sha1.New() 314 | io.WriteString(h, "Oops, rand failed. Use time instead.") 315 | io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) 316 | sentinel = h.Sum(nil) 317 | } 318 | } 319 | 320 | func (pc *pooledConnection) Close() error { 321 | c := pc.c 322 | if _, ok := c.(errorConnection); ok { 323 | return nil 324 | } 325 | pc.c = errorConnection{errConnClosed} 326 | 327 | if pc.state&internal.MultiState != 0 { 328 | c.Send("DISCARD") 329 | pc.state &^= (internal.MultiState | internal.WatchState) 330 | } else if pc.state&internal.WatchState != 0 { 331 | c.Send("UNWATCH") 332 | pc.state &^= internal.WatchState 333 | } 334 | if pc.state&internal.SubscribeState != 0 { 335 | c.Send("UNSUBSCRIBE") 336 | c.Send("PUNSUBSCRIBE") 337 | // To detect the end of the message stream, ask the server to echo 338 | // a sentinel value and read until we see that value. 339 | sentinelOnce.Do(initSentinel) 340 | c.Send("ECHO", sentinel) 341 | c.Flush() 342 | for { 343 | p, err := c.Receive() 344 | if err != nil { 345 | break 346 | } 347 | if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { 348 | pc.state &^= internal.SubscribeState 349 | break 350 | } 351 | } 352 | } 353 | c.Do("") 354 | pc.p.put(c, pc.state != 0) 355 | return nil 356 | } 357 | 358 | func (pc *pooledConnection) Err() error { 359 | return pc.c.Err() 360 | } 361 | 362 | func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { 363 | ci := internal.LookupCommandInfo(commandName) 364 | pc.state = (pc.state | ci.Set) &^ ci.Clear 365 | return pc.c.Do(commandName, args...) 366 | } 367 | 368 | func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { 369 | ci := internal.LookupCommandInfo(commandName) 370 | pc.state = (pc.state | ci.Set) &^ ci.Clear 371 | return pc.c.Send(commandName, args...) 372 | } 373 | 374 | func (pc *pooledConnection) Flush() error { 375 | return pc.c.Flush() 376 | } 377 | 378 | func (pc *pooledConnection) Receive() (reply interface{}, err error) { 379 | return pc.c.Receive() 380 | } 381 | 382 | type errorConnection struct{ err error } 383 | 384 | func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } 385 | func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } 386 | func (ec errorConnection) Err() error { return ec.err } 387 | func (ec errorConnection) Close() error { return ec.err } 388 | func (ec errorConnection) Flush() error { return ec.err } 389 | func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } 390 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | // Subscription represents a subscribe or unsubscribe notification. 22 | type Subscription struct { 23 | 24 | // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" 25 | Kind string 26 | 27 | // The channel that was changed. 28 | Channel string 29 | 30 | // The current number of subscriptions for connection. 31 | Count int 32 | } 33 | 34 | // Message represents a message notification. 35 | type Message struct { 36 | 37 | // The originating channel. 38 | Channel string 39 | 40 | // The message data. 41 | Data []byte 42 | } 43 | 44 | // PMessage represents a pmessage notification. 45 | type PMessage struct { 46 | 47 | // The matched pattern. 48 | Pattern string 49 | 50 | // The originating channel. 51 | Channel string 52 | 53 | // The message data. 54 | Data []byte 55 | } 56 | 57 | // PubSubConn wraps a Conn with convenience methods for subscribers. 58 | type PubSubConn struct { 59 | Conn Conn 60 | } 61 | 62 | // Close closes the connection. 63 | func (c PubSubConn) Close() error { 64 | return c.Conn.Close() 65 | } 66 | 67 | // Subscribe subscribes the connection to the specified channels. 68 | func (c PubSubConn) Subscribe(channel ...interface{}) error { 69 | c.Conn.Send("SUBSCRIBE", channel...) 70 | return c.Conn.Flush() 71 | } 72 | 73 | // PSubscribe subscribes the connection to the given patterns. 74 | func (c PubSubConn) PSubscribe(channel ...interface{}) error { 75 | c.Conn.Send("PSUBSCRIBE", channel...) 76 | return c.Conn.Flush() 77 | } 78 | 79 | // Unsubscribe unsubscribes the connection from the given channels, or from all 80 | // of them if none is given. 81 | func (c PubSubConn) Unsubscribe(channel ...interface{}) error { 82 | c.Conn.Send("UNSUBSCRIBE", channel...) 83 | return c.Conn.Flush() 84 | } 85 | 86 | // PUnsubscribe unsubscribes the connection from the given patterns, or from all 87 | // of them if none is given. 88 | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { 89 | c.Conn.Send("PUNSUBSCRIBE", channel...) 90 | return c.Conn.Flush() 91 | } 92 | 93 | // Receive returns a pushed message as a Subscription, Message, PMessage or 94 | // error. The return value is intended to be used directly in a type switch as 95 | // illustrated in the PubSubConn example. 96 | func (c PubSubConn) Receive() interface{} { 97 | reply, err := Values(c.Conn.Receive()) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | var kind string 103 | reply, err = Scan(reply, &kind) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | switch kind { 109 | case "message": 110 | var m Message 111 | if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { 112 | return err 113 | } 114 | return m 115 | case "pmessage": 116 | var pm PMessage 117 | if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { 118 | return err 119 | } 120 | return pm 121 | case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": 122 | s := Subscription{Kind: kind} 123 | if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { 124 | return err 125 | } 126 | return s 127 | } 128 | return errors.New("redigo: unknown pubsub notification") 129 | } 130 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | // Error represents an error returned in a command reply. 18 | type Error string 19 | 20 | func (err Error) Error() string { return string(err) } 21 | 22 | // Conn represents a connection to a Redis server. 23 | type Conn interface { 24 | // Close closes the connection. 25 | Close() error 26 | 27 | // Err returns a non-nil value if the connection is broken. The returned 28 | // value is either the first non-nil value returned from the underlying 29 | // network connection or a protocol parsing error. Applications should 30 | // close broken connections. 31 | Err() error 32 | 33 | // Do sends a command to the server and returns the received reply. 34 | Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | 36 | // Send writes the command to the client's output buffer. 37 | Send(commandName string, args ...interface{}) error 38 | 39 | // Flush flushes the output buffer to the Redis server. 40 | Flush() error 41 | 42 | // Receive receives a single reply from the Redis server 43 | Receive() (reply interface{}, err error) 44 | } 45 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/reply.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strconv" 21 | ) 22 | 23 | // ErrNil indicates that a reply value is nil. 24 | var ErrNil = errors.New("redigo: nil returned") 25 | 26 | // Int is a helper that converts a command reply to an integer. If err is not 27 | // equal to nil, then Int returns 0, err. Otherwise, Int converts the 28 | // reply to an int as follows: 29 | // 30 | // Reply type Result 31 | // integer int(reply), nil 32 | // bulk string parsed reply, nil 33 | // nil 0, ErrNil 34 | // other 0, error 35 | func Int(reply interface{}, err error) (int, error) { 36 | if err != nil { 37 | return 0, err 38 | } 39 | switch reply := reply.(type) { 40 | case int64: 41 | x := int(reply) 42 | if int64(x) != reply { 43 | return 0, strconv.ErrRange 44 | } 45 | return x, nil 46 | case []byte: 47 | n, err := strconv.ParseInt(string(reply), 10, 0) 48 | return int(n), err 49 | case nil: 50 | return 0, ErrNil 51 | case Error: 52 | return 0, reply 53 | } 54 | return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) 55 | } 56 | 57 | // Int64 is a helper that converts a command reply to 64 bit integer. If err is 58 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 59 | // reply to an int64 as follows: 60 | // 61 | // Reply type Result 62 | // integer reply, nil 63 | // bulk string parsed reply, nil 64 | // nil 0, ErrNil 65 | // other 0, error 66 | func Int64(reply interface{}, err error) (int64, error) { 67 | if err != nil { 68 | return 0, err 69 | } 70 | switch reply := reply.(type) { 71 | case int64: 72 | return reply, nil 73 | case []byte: 74 | n, err := strconv.ParseInt(string(reply), 10, 64) 75 | return n, err 76 | case nil: 77 | return 0, ErrNil 78 | case Error: 79 | return 0, reply 80 | } 81 | return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) 82 | } 83 | 84 | var errNegativeInt = errors.New("redigo: unexpected value for Uint64") 85 | 86 | // Uint64 is a helper that converts a command reply to 64 bit integer. If err is 87 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 88 | // reply to an int64 as follows: 89 | // 90 | // Reply type Result 91 | // integer reply, nil 92 | // bulk string parsed reply, nil 93 | // nil 0, ErrNil 94 | // other 0, error 95 | func Uint64(reply interface{}, err error) (uint64, error) { 96 | if err != nil { 97 | return 0, err 98 | } 99 | switch reply := reply.(type) { 100 | case int64: 101 | if reply < 0 { 102 | return 0, errNegativeInt 103 | } 104 | return uint64(reply), nil 105 | case []byte: 106 | n, err := strconv.ParseUint(string(reply), 10, 64) 107 | return n, err 108 | case nil: 109 | return 0, ErrNil 110 | case Error: 111 | return 0, reply 112 | } 113 | return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) 114 | } 115 | 116 | // Float64 is a helper that converts a command reply to 64 bit float. If err is 117 | // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts 118 | // the reply to an int as follows: 119 | // 120 | // Reply type Result 121 | // bulk string parsed reply, nil 122 | // nil 0, ErrNil 123 | // other 0, error 124 | func Float64(reply interface{}, err error) (float64, error) { 125 | if err != nil { 126 | return 0, err 127 | } 128 | switch reply := reply.(type) { 129 | case []byte: 130 | n, err := strconv.ParseFloat(string(reply), 64) 131 | return n, err 132 | case nil: 133 | return 0, ErrNil 134 | case Error: 135 | return 0, reply 136 | } 137 | return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) 138 | } 139 | 140 | // String is a helper that converts a command reply to a string. If err is not 141 | // equal to nil, then String returns "", err. Otherwise String converts the 142 | // reply to a string as follows: 143 | // 144 | // Reply type Result 145 | // bulk string string(reply), nil 146 | // simple string reply, nil 147 | // nil "", ErrNil 148 | // other "", error 149 | func String(reply interface{}, err error) (string, error) { 150 | if err != nil { 151 | return "", err 152 | } 153 | switch reply := reply.(type) { 154 | case []byte: 155 | return string(reply), nil 156 | case string: 157 | return reply, nil 158 | case nil: 159 | return "", ErrNil 160 | case Error: 161 | return "", reply 162 | } 163 | return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) 164 | } 165 | 166 | // Bytes is a helper that converts a command reply to a slice of bytes. If err 167 | // is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts 168 | // the reply to a slice of bytes as follows: 169 | // 170 | // Reply type Result 171 | // bulk string reply, nil 172 | // simple string []byte(reply), nil 173 | // nil nil, ErrNil 174 | // other nil, error 175 | func Bytes(reply interface{}, err error) ([]byte, error) { 176 | if err != nil { 177 | return nil, err 178 | } 179 | switch reply := reply.(type) { 180 | case []byte: 181 | return reply, nil 182 | case string: 183 | return []byte(reply), nil 184 | case nil: 185 | return nil, ErrNil 186 | case Error: 187 | return nil, reply 188 | } 189 | return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) 190 | } 191 | 192 | // Bool is a helper that converts a command reply to a boolean. If err is not 193 | // equal to nil, then Bool returns false, err. Otherwise Bool converts the 194 | // reply to boolean as follows: 195 | // 196 | // Reply type Result 197 | // integer value != 0, nil 198 | // bulk string strconv.ParseBool(reply) 199 | // nil false, ErrNil 200 | // other false, error 201 | func Bool(reply interface{}, err error) (bool, error) { 202 | if err != nil { 203 | return false, err 204 | } 205 | switch reply := reply.(type) { 206 | case int64: 207 | return reply != 0, nil 208 | case []byte: 209 | return strconv.ParseBool(string(reply)) 210 | case nil: 211 | return false, ErrNil 212 | case Error: 213 | return false, reply 214 | } 215 | return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) 216 | } 217 | 218 | // MultiBulk is deprecated. Use Values. 219 | func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } 220 | 221 | // Values is a helper that converts an array command reply to a []interface{}. 222 | // If err is not equal to nil, then Values returns nil, err. Otherwise, Values 223 | // converts the reply as follows: 224 | // 225 | // Reply type Result 226 | // array reply, nil 227 | // nil nil, ErrNil 228 | // other nil, error 229 | func Values(reply interface{}, err error) ([]interface{}, error) { 230 | if err != nil { 231 | return nil, err 232 | } 233 | switch reply := reply.(type) { 234 | case []interface{}: 235 | return reply, nil 236 | case nil: 237 | return nil, ErrNil 238 | case Error: 239 | return nil, reply 240 | } 241 | return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) 242 | } 243 | 244 | // Strings is a helper that converts an array command reply to a []string. If 245 | // err is not equal to nil, then Strings returns nil, err. Nil array items are 246 | // converted to "" in the output slice. Strings returns an error if an array 247 | // item is not a bulk string or nil. 248 | func Strings(reply interface{}, err error) ([]string, error) { 249 | if err != nil { 250 | return nil, err 251 | } 252 | switch reply := reply.(type) { 253 | case []interface{}: 254 | result := make([]string, len(reply)) 255 | for i := range reply { 256 | if reply[i] == nil { 257 | continue 258 | } 259 | p, ok := reply[i].([]byte) 260 | if !ok { 261 | return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) 262 | } 263 | result[i] = string(p) 264 | } 265 | return result, nil 266 | case nil: 267 | return nil, ErrNil 268 | case Error: 269 | return nil, reply 270 | } 271 | return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) 272 | } 273 | 274 | // Ints is a helper that converts an array command reply to a []int. If 275 | // err is not equal to nil, then Ints returns nil, err. 276 | func Ints(reply interface{}, err error) ([]int, error) { 277 | var ints []int 278 | if reply == nil { 279 | return ints, ErrNil 280 | } 281 | values, err := Values(reply, err) 282 | if err != nil { 283 | return ints, err 284 | } 285 | if err := ScanSlice(values, &ints); err != nil { 286 | return ints, err 287 | } 288 | return ints, nil 289 | } 290 | -------------------------------------------------------------------------------- /vendor/github.com/garyburd/redigo/redis/script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Script encapsulates the source, hash and key count for a Lua script. See 25 | // http://redis.io/commands/eval for information on scripts in Redis. 26 | type Script struct { 27 | keyCount int 28 | src string 29 | hash string 30 | } 31 | 32 | // NewScript returns a new script object. If keyCount is greater than or equal 33 | // to zero, then the count is automatically inserted in the EVAL command 34 | // argument list. If keyCount is less than zero, then the application supplies 35 | // the count as the first value in the keysAndArgs argument to the Do, Send and 36 | // SendHash methods. 37 | func NewScript(keyCount int, src string) *Script { 38 | h := sha1.New() 39 | io.WriteString(h, src) 40 | return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} 41 | } 42 | 43 | func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { 44 | var args []interface{} 45 | if s.keyCount < 0 { 46 | args = make([]interface{}, 1+len(keysAndArgs)) 47 | args[0] = spec 48 | copy(args[1:], keysAndArgs) 49 | } else { 50 | args = make([]interface{}, 2+len(keysAndArgs)) 51 | args[0] = spec 52 | args[1] = s.keyCount 53 | copy(args[2:], keysAndArgs) 54 | } 55 | return args 56 | } 57 | 58 | // Do evaluates the script. Under the covers, Do optimistically evaluates the 59 | // script using the EVALSHA command. If the command fails because the script is 60 | // not loaded, then Do evaluates the script using the EVAL command (thus 61 | // causing the script to load). 62 | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { 63 | v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) 64 | if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { 65 | v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) 66 | } 67 | return v, err 68 | } 69 | 70 | // SendHash evaluates the script without waiting for the reply. The script is 71 | // evaluated with the EVALSHA command. The application must ensure that the 72 | // script is loaded by a previous call to Send, Do or Load methods. 73 | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { 74 | return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) 75 | } 76 | 77 | // Send evaluates the script without waiting for the reply. 78 | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { 79 | return c.Send("EVAL", s.args(s.src, keysAndArgs)...) 80 | } 81 | 82 | // Load loads the script without evaluating it. 83 | func (s *Script) Load(c Conn) error { 84 | _, err := c.Do("SCRIPT", "LOAD", s.src) 85 | return err 86 | } 87 | -------------------------------------------------------------------------------- /vendor/github.com/ngaut/logging/LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /vendor/github.com/ngaut/logging/README.md: -------------------------------------------------------------------------------- 1 | logging 2 | ======= 3 | -------------------------------------------------------------------------------- /vendor/github.com/ngaut/logging/log.go: -------------------------------------------------------------------------------- 1 | //high level log wrapper, so it can output different log based on level 2 | package logging 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "sync" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | const ( 15 | Ldate = log.Ldate 16 | Llongfile = log.Llongfile 17 | Lmicroseconds = log.Lmicroseconds 18 | Lshortfile = log.Lshortfile 19 | LstdFlags = log.LstdFlags 20 | Ltime = log.Ltime 21 | ) 22 | 23 | type ( 24 | LogLevel int 25 | LogType int 26 | ) 27 | 28 | const ( 29 | LOG_FATAL = LogType(0x1) 30 | LOG_ERROR = LogType(0x2) 31 | LOG_WARNING = LogType(0x4) 32 | LOG_INFO = LogType(0x8) 33 | LOG_DEBUG = LogType(0x10) 34 | ) 35 | 36 | const ( 37 | LOG_LEVEL_NONE = LogLevel(0x0) 38 | LOG_LEVEL_FATAL = LOG_LEVEL_NONE | LogLevel(LOG_FATAL) 39 | LOG_LEVEL_ERROR = LOG_LEVEL_FATAL | LogLevel(LOG_ERROR) 40 | LOG_LEVEL_WARN = LOG_LEVEL_ERROR | LogLevel(LOG_WARNING) 41 | LOG_LEVEL_INFO = LOG_LEVEL_WARN | LogLevel(LOG_INFO) 42 | LOG_LEVEL_DEBUG = LOG_LEVEL_INFO | LogLevel(LOG_DEBUG) 43 | LOG_LEVEL_ALL = LOG_LEVEL_DEBUG 44 | ) 45 | 46 | const FORMAT_TIME_DAY string = "20060102" 47 | const FORMAT_TIME_HOUR string = "2006010215" 48 | 49 | var _log *logger = New() 50 | 51 | func init() { 52 | SetFlags(Ldate | Ltime | Lshortfile) 53 | } 54 | 55 | func Logger() *log.Logger { 56 | return _log._log 57 | } 58 | 59 | func CrashLog(file string) { 60 | f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) 61 | if err != nil { 62 | log.Println(err.Error()) 63 | } else { 64 | syscall.Dup2(int(f.Fd()), 2) 65 | } 66 | } 67 | 68 | func SetLevel(level LogLevel) { 69 | _log.SetLevel(level) 70 | } 71 | func GetLogLevel() LogLevel { 72 | return _log.level 73 | } 74 | 75 | func SetOutput(out io.Writer) { 76 | _log.SetOutput(out) 77 | } 78 | 79 | func SetOutputByName(path string) error { 80 | return _log.SetOutputByName(path) 81 | } 82 | 83 | func SetFlags(flags int) { 84 | _log._log.SetFlags(flags) 85 | } 86 | 87 | func Info(v ...interface{}) { 88 | _log.Info(v...) 89 | } 90 | 91 | func Infof(format string, v ...interface{}) { 92 | _log.Infof(format, v...) 93 | } 94 | 95 | func Debug(v ...interface{}) { 96 | _log.Debug(v...) 97 | } 98 | 99 | func Debugf(format string, v ...interface{}) { 100 | _log.Debugf(format, v...) 101 | } 102 | 103 | func Warning(v ...interface{}) { 104 | _log.Warning(v...) 105 | } 106 | 107 | func Warningf(format string, v ...interface{}) { 108 | _log.Warningf(format, v...) 109 | } 110 | 111 | func Error(v ...interface{}) { 112 | _log.Error(v...) 113 | } 114 | 115 | func Errorf(format string, v ...interface{}) { 116 | _log.Errorf(format, v...) 117 | } 118 | 119 | func Fatal(v ...interface{}) { 120 | _log.Fatal(v...) 121 | } 122 | 123 | func Fatalf(format string, v ...interface{}) { 124 | _log.Fatalf(format, v...) 125 | } 126 | 127 | func SetLevelByString(level string) { 128 | _log.SetLevelByString(level) 129 | } 130 | 131 | func SetHighlighting(highlighting bool) { 132 | _log.SetHighlighting(highlighting) 133 | } 134 | 135 | func SetRotateByDay() { 136 | _log.SetRotateByDay() 137 | } 138 | 139 | func SetRotateByHour() { 140 | _log.SetRotateByHour() 141 | } 142 | 143 | type logger struct { 144 | _log *log.Logger 145 | level LogLevel 146 | highlighting bool 147 | 148 | dailyRolling bool 149 | hourRolling bool 150 | 151 | fileName string 152 | logSuffix string 153 | fd *os.File 154 | 155 | lock sync.Mutex 156 | } 157 | 158 | func (l *logger) SetHighlighting(highlighting bool) { 159 | l.highlighting = highlighting 160 | } 161 | 162 | func (l *logger) SetLevel(level LogLevel) { 163 | l.level = level 164 | } 165 | 166 | func (l *logger) SetLevelByString(level string) { 167 | l.level = StringToLogLevel(level) 168 | } 169 | 170 | func (l *logger) SetRotateByDay() { 171 | l.dailyRolling = true 172 | l.logSuffix = genDayTime(time.Now()) 173 | } 174 | 175 | func (l *logger) SetRotateByHour() { 176 | l.hourRolling = true 177 | l.logSuffix = genHourTime(time.Now()) 178 | } 179 | 180 | func (l *logger) rotate() error { 181 | l.lock.Lock() 182 | defer l.lock.Unlock() 183 | 184 | var suffix string 185 | if l.dailyRolling { 186 | suffix = genDayTime(time.Now()) 187 | } else if l.hourRolling { 188 | suffix = genHourTime(time.Now()) 189 | } else { 190 | return nil 191 | } 192 | 193 | // Notice: if suffix is not equal to l.LogSuffix, then rotate 194 | if suffix != l.logSuffix { 195 | err := l.doRotate(suffix) 196 | if err != nil { 197 | return err 198 | } 199 | } 200 | 201 | return nil 202 | } 203 | 204 | func (l *logger) doRotate(suffix string) error { 205 | lastFileName := l.fileName + "." + l.logSuffix 206 | err := os.Rename(l.fileName, lastFileName) 207 | if err != nil { 208 | return err 209 | } 210 | 211 | // Notice: Not check error, is this ok? 212 | l.fd.Close() 213 | 214 | err = l.SetOutputByName(l.fileName) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | l.logSuffix = suffix 220 | 221 | return nil 222 | } 223 | 224 | func (l *logger) SetOutput(out io.Writer) { 225 | l._log = log.New(out, l._log.Prefix(), l._log.Flags()) 226 | } 227 | 228 | func (l *logger) SetOutputByName(path string) error { 229 | f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) 230 | if err != nil { 231 | log.Fatal(err) 232 | } 233 | 234 | l.SetOutput(f) 235 | 236 | l.fileName = path 237 | l.fd = f 238 | 239 | return err 240 | } 241 | 242 | func (l *logger) log(t LogType, v ...interface{}) { 243 | if l.level|LogLevel(t) != l.level { 244 | return 245 | } 246 | 247 | err := l.rotate() 248 | if err != nil { 249 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 250 | return 251 | } 252 | 253 | v1 := make([]interface{}, len(v)+2) 254 | logStr, logColor := LogTypeToString(t) 255 | if l.highlighting { 256 | v1[0] = "\033" + logColor + "m[" + logStr + "]" 257 | copy(v1[1:], v) 258 | v1[len(v)+1] = "\033[0m" 259 | } else { 260 | v1[0] = "[" + logStr + "]" 261 | copy(v1[1:], v) 262 | v1[len(v)+1] = "" 263 | } 264 | 265 | s := fmt.Sprintln(v1...) 266 | l._log.Output(4, s) 267 | } 268 | 269 | func (l *logger) logf(t LogType, format string, v ...interface{}) { 270 | if l.level|LogLevel(t) != l.level { 271 | return 272 | } 273 | 274 | err := l.rotate() 275 | if err != nil { 276 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 277 | return 278 | } 279 | 280 | logStr, logColor := LogTypeToString(t) 281 | var s string 282 | if l.highlighting { 283 | s = "\033" + logColor + "m[" + logStr + "] " + fmt.Sprintf(format, v...) + "\033[0m" 284 | } else { 285 | s = "[" + logStr + "] " + fmt.Sprintf(format, v...) 286 | } 287 | l._log.Output(4, s) 288 | } 289 | 290 | func (l *logger) Fatal(v ...interface{}) { 291 | l.log(LOG_FATAL, v...) 292 | os.Exit(-1) 293 | } 294 | 295 | func (l *logger) Fatalf(format string, v ...interface{}) { 296 | l.logf(LOG_FATAL, format, v...) 297 | os.Exit(-1) 298 | } 299 | 300 | func (l *logger) Error(v ...interface{}) { 301 | l.log(LOG_ERROR, v...) 302 | } 303 | 304 | func (l *logger) Errorf(format string, v ...interface{}) { 305 | l.logf(LOG_ERROR, format, v...) 306 | } 307 | 308 | func (l *logger) Warning(v ...interface{}) { 309 | l.log(LOG_WARNING, v...) 310 | } 311 | 312 | func (l *logger) Warningf(format string, v ...interface{}) { 313 | l.logf(LOG_WARNING, format, v...) 314 | } 315 | 316 | func (l *logger) Debug(v ...interface{}) { 317 | l.log(LOG_DEBUG, v...) 318 | } 319 | 320 | func (l *logger) Debugf(format string, v ...interface{}) { 321 | l.logf(LOG_DEBUG, format, v...) 322 | } 323 | 324 | func (l *logger) Info(v ...interface{}) { 325 | l.log(LOG_INFO, v...) 326 | } 327 | 328 | func (l *logger) Infof(format string, v ...interface{}) { 329 | l.logf(LOG_INFO, format, v...) 330 | } 331 | 332 | func StringToLogLevel(level string) LogLevel { 333 | switch level { 334 | case "fatal": 335 | return LOG_LEVEL_FATAL 336 | case "error": 337 | return LOG_LEVEL_ERROR 338 | case "warn": 339 | return LOG_LEVEL_WARN 340 | case "warning": 341 | return LOG_LEVEL_WARN 342 | case "debug": 343 | return LOG_LEVEL_DEBUG 344 | case "info": 345 | return LOG_LEVEL_INFO 346 | } 347 | return LOG_LEVEL_ALL 348 | } 349 | 350 | func LogTypeToString(t LogType) (string, string) { 351 | switch t { 352 | case LOG_FATAL: 353 | return "fatal", "[0;31" 354 | case LOG_ERROR: 355 | return "error", "[0;31" 356 | case LOG_WARNING: 357 | return "warning", "[0;33" 358 | case LOG_DEBUG: 359 | return "debug", "[0;36" 360 | case LOG_INFO: 361 | return "info", "[0;37" 362 | } 363 | return "unknown", "[0;37" 364 | } 365 | 366 | func genDayTime(t time.Time) string { 367 | return t.Format(FORMAT_TIME_DAY) 368 | } 369 | 370 | func genHourTime(t time.Time) string { 371 | return t.Format(FORMAT_TIME_HOUR) 372 | } 373 | 374 | func New() *logger { 375 | return Newlogger(os.Stdout, "") 376 | } 377 | 378 | func Newlogger(w io.Writer, prefix string) *logger { 379 | return &logger{_log: log.New(w, prefix, LstdFlags), level: LOG_LEVEL_ALL, highlighting: true} 380 | } 381 | -------------------------------------------------------------------------------- /vendor/github.com/pmezard/go-difflib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Patrick Mezard 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | The names of its contributors may not be used to endorse or promote 14 | products derived from this software without specific prior written 15 | permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 18 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 20 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 23 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell 2 | 3 | Please consider promoting this project if you find it useful. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of the Software, 10 | and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 20 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT 21 | OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 22 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentWithoutT "a"}} 2 | func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { 3 | return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) 4 | } 5 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/doc.go: -------------------------------------------------------------------------------- 1 | // Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. 2 | // 3 | // Example Usage 4 | // 5 | // The following is a complete example using assert in a standard test function: 6 | // import ( 7 | // "testing" 8 | // "github.com/stretchr/testify/assert" 9 | // ) 10 | // 11 | // func TestSomething(t *testing.T) { 12 | // 13 | // var a string = "Hello" 14 | // var b string = "Hello" 15 | // 16 | // assert.Equal(t, a, b, "The two words should be the same.") 17 | // 18 | // } 19 | // 20 | // if you assert many times, use the format below: 21 | // 22 | // import ( 23 | // "testing" 24 | // "github.com/stretchr/testify/assert" 25 | // ) 26 | // 27 | // func TestSomething(t *testing.T) { 28 | // assert := assert.New(t) 29 | // 30 | // var a string = "Hello" 31 | // var b string = "Hello" 32 | // 33 | // assert.Equal(a, b, "The two words should be the same.") 34 | // } 35 | // 36 | // Assertions 37 | // 38 | // Assertions allow you to easily write test code, and are global funcs in the `assert` package. 39 | // All assertion functions take, as the first argument, the `*testing.T` object provided by the 40 | // testing framework. This allows the assertion funcs to write the failings and other details to 41 | // the correct place. 42 | // 43 | // Every assertion function also takes an optional string message as the final argument, 44 | // allowing custom error messages to be appended to the message the assertion method outputs. 45 | package assert 46 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/errors.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // AnError is an error instance useful for testing. If the code does not care 8 | // about error specifics, and only needs to return the error for example, this 9 | // error should be used to make the test code more readable. 10 | var AnError = errors.New("assert.AnError general error for testing") 11 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/forward_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | // Assertions provides assertion methods around the 4 | // TestingT interface. 5 | type Assertions struct { 6 | t TestingT 7 | } 8 | 9 | // New makes a new Assertions object for the specified TestingT. 10 | func New(t TestingT) *Assertions { 11 | return &Assertions{ 12 | t: t, 13 | } 14 | } 15 | 16 | //go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl 17 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/http_assertions.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "strings" 9 | ) 10 | 11 | // httpCode is a helper that returns HTTP code of the response. It returns -1 12 | // if building a new request fails. 13 | func httpCode(handler http.HandlerFunc, method, url string, values url.Values) int { 14 | w := httptest.NewRecorder() 15 | req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) 16 | if err != nil { 17 | return -1 18 | } 19 | handler(w, req) 20 | return w.Code 21 | } 22 | 23 | // HTTPSuccess asserts that a specified handler returns a success status code. 24 | // 25 | // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) 26 | // 27 | // Returns whether the assertion was successful (true) or not (false). 28 | func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { 29 | code := httpCode(handler, method, url, values) 30 | if code == -1 { 31 | return false 32 | } 33 | return code >= http.StatusOK && code <= http.StatusPartialContent 34 | } 35 | 36 | // HTTPRedirect asserts that a specified handler returns a redirect status code. 37 | // 38 | // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} 39 | // 40 | // Returns whether the assertion was successful (true) or not (false). 41 | func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { 42 | code := httpCode(handler, method, url, values) 43 | if code == -1 { 44 | return false 45 | } 46 | return code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect 47 | } 48 | 49 | // HTTPError asserts that a specified handler returns an error status code. 50 | // 51 | // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} 52 | // 53 | // Returns whether the assertion was successful (true) or not (false). 54 | func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values) bool { 55 | code := httpCode(handler, method, url, values) 56 | if code == -1 { 57 | return false 58 | } 59 | return code >= http.StatusBadRequest 60 | } 61 | 62 | // HTTPBody is a helper that returns HTTP body of the response. It returns 63 | // empty string if building a new request fails. 64 | func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { 65 | w := httptest.NewRecorder() 66 | req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) 67 | if err != nil { 68 | return "" 69 | } 70 | handler(w, req) 71 | return w.Body.String() 72 | } 73 | 74 | // HTTPBodyContains asserts that a specified handler returns a 75 | // body that contains a string. 76 | // 77 | // assert.HTTPBodyContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 78 | // 79 | // Returns whether the assertion was successful (true) or not (false). 80 | func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { 81 | body := HTTPBody(handler, method, url, values) 82 | 83 | contains := strings.Contains(body, fmt.Sprint(str)) 84 | if !contains { 85 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 86 | } 87 | 88 | return contains 89 | } 90 | 91 | // HTTPBodyNotContains asserts that a specified handler returns a 92 | // body that does not contain a string. 93 | // 94 | // assert.HTTPBodyNotContains(t, myHandler, "www.google.com", nil, "I'm Feeling Lucky") 95 | // 96 | // Returns whether the assertion was successful (true) or not (false). 97 | func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}) bool { 98 | body := HTTPBody(handler, method, url, values) 99 | 100 | contains := strings.Contains(body, fmt.Sprint(str)) 101 | if contains { 102 | Fail(t, "Expected response body for %s to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body) 103 | } 104 | 105 | return !contains 106 | } 107 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fatih/pool.v2/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Fatih Arslan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fatih/pool.v2/README.md: -------------------------------------------------------------------------------- 1 | # Pool [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/gopkg.in/fatih/pool.v2) [![Build Status](http://img.shields.io/travis/fatih/pool.svg?style=flat-square)](https://travis-ci.org/fatih/pool) 2 | 3 | 4 | Pool is a thread safe connection pool for net.Conn interface. It can be used to 5 | manage and reuse connections. 6 | 7 | 8 | ## Install and Usage 9 | 10 | Install the package with: 11 | 12 | ```bash 13 | go get gopkg.in/fatih/pool.v2 14 | ``` 15 | 16 | Import it with: 17 | 18 | ```go 19 | import "gopkg.in/fatih/pool.v2" 20 | ``` 21 | 22 | and use `pool` as the package name inside the code. 23 | 24 | ## Example 25 | 26 | ```go 27 | // create a factory() to be used with channel based pool 28 | factory := func() (net.Conn, error) { return net.Dial("tcp", "127.0.0.1:4000") } 29 | 30 | // create a new channel based pool with an initial capacity of 5 and maximum 31 | // capacity of 30. The factory will create 5 initial connections and put it 32 | // into the pool. 33 | p, err := pool.NewChannelPool(5, 30, factory) 34 | 35 | // now you can get a connection from the pool, if there is no connection 36 | // available it will create a new one via the factory function. 37 | conn, err := p.Get() 38 | 39 | // do something with conn and put it back to the pool by closing the connection 40 | // (this doesn't close the underlying connection instead it's putting it back 41 | // to the pool). 42 | conn.Close() 43 | 44 | // close pool any time you want, this closes all the connections inside a pool 45 | p.Close() 46 | 47 | // currently available connections in the pool 48 | current := p.Len() 49 | ``` 50 | 51 | 52 | ## Credits 53 | 54 | * [Fatih Arslan](https://github.com/fatih) 55 | * [sougou](https://github.com/sougou) 56 | 57 | ## License 58 | 59 | The MIT License (MIT) - see LICENSE for more details 60 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fatih/pool.v2/channel.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | // channelPool implements the Pool interface based on buffered channels. 11 | type channelPool struct { 12 | // storage for our net.Conn connections 13 | mu sync.Mutex 14 | conns chan net.Conn 15 | 16 | // net.Conn generator 17 | factory Factory 18 | } 19 | 20 | // Factory is a function to create new connections. 21 | type Factory func() (net.Conn, error) 22 | 23 | // NewChannelPool returns a new pool based on buffered channels with an initial 24 | // capacity and maximum capacity. Factory is used when initial capacity is 25 | // greater than zero to fill the pool. A zero initialCap doesn't fill the Pool 26 | // until a new Get() is called. During a Get(), If there is no new connection 27 | // available in the pool, a new connection will be created via the Factory() 28 | // method. 29 | func NewChannelPool(initialCap, maxCap int, factory Factory) (Pool, error) { 30 | if initialCap < 0 || maxCap <= 0 || initialCap > maxCap { 31 | return nil, errors.New("invalid capacity settings") 32 | } 33 | 34 | c := &channelPool{ 35 | conns: make(chan net.Conn, maxCap), 36 | factory: factory, 37 | } 38 | 39 | // create initial connections, if something goes wrong, 40 | // just close the pool error out. 41 | for i := 0; i < initialCap; i++ { 42 | conn, err := factory() 43 | if err != nil { 44 | c.Close() 45 | return nil, fmt.Errorf("factory is not able to fill the pool: %s", err) 46 | } 47 | c.conns <- conn 48 | } 49 | 50 | return c, nil 51 | } 52 | 53 | func (c *channelPool) getConns() chan net.Conn { 54 | c.mu.Lock() 55 | conns := c.conns 56 | c.mu.Unlock() 57 | return conns 58 | } 59 | 60 | // Get implements the Pool interfaces Get() method. If there is no new 61 | // connection available in the pool, a new connection will be created via the 62 | // Factory() method. 63 | func (c *channelPool) Get() (net.Conn, error) { 64 | conns := c.getConns() 65 | if conns == nil { 66 | return nil, ErrClosed 67 | } 68 | 69 | // wrap our connections with out custom net.Conn implementation (wrapConn 70 | // method) that puts the connection back to the pool if it's closed. 71 | select { 72 | case conn := <-conns: 73 | if conn == nil { 74 | return nil, ErrClosed 75 | } 76 | 77 | return c.wrapConn(conn), nil 78 | default: 79 | conn, err := c.factory() 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | return c.wrapConn(conn), nil 85 | } 86 | } 87 | 88 | // put puts the connection back to the pool. If the pool is full or closed, 89 | // conn is simply closed. A nil conn will be rejected. 90 | func (c *channelPool) put(conn net.Conn) error { 91 | if conn == nil { 92 | return errors.New("connection is nil. rejecting") 93 | } 94 | 95 | c.mu.Lock() 96 | defer c.mu.Unlock() 97 | 98 | if c.conns == nil { 99 | // pool is closed, close passed connection 100 | return conn.Close() 101 | } 102 | 103 | // put the resource back into the pool. If the pool is full, this will 104 | // block and the default case will be executed. 105 | select { 106 | case c.conns <- conn: 107 | return nil 108 | default: 109 | // pool is full, close passed connection 110 | return conn.Close() 111 | } 112 | } 113 | 114 | func (c *channelPool) Close() { 115 | c.mu.Lock() 116 | conns := c.conns 117 | c.conns = nil 118 | c.factory = nil 119 | c.mu.Unlock() 120 | 121 | if conns == nil { 122 | return 123 | } 124 | 125 | close(conns) 126 | for conn := range conns { 127 | conn.Close() 128 | } 129 | } 130 | 131 | func (c *channelPool) Len() int { return len(c.getConns()) } 132 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fatih/pool.v2/conn.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | ) 7 | 8 | // PoolConn is a wrapper around net.Conn to modify the the behavior of 9 | // net.Conn's Close() method. 10 | type PoolConn struct { 11 | net.Conn 12 | mu sync.RWMutex 13 | c *channelPool 14 | unusable bool 15 | } 16 | 17 | // Close() puts the given connects back to the pool instead of closing it. 18 | func (p *PoolConn) Close() error { 19 | p.mu.RLock() 20 | defer p.mu.RUnlock() 21 | 22 | if p.unusable { 23 | if p.Conn != nil { 24 | return p.Conn.Close() 25 | } 26 | return nil 27 | } 28 | return p.c.put(p.Conn) 29 | } 30 | 31 | // MarkUnusable() marks the connection not usable any more, to let the pool close it instead of returning it to pool. 32 | func (p *PoolConn) MarkUnusable() { 33 | p.mu.Lock() 34 | p.unusable = true 35 | p.mu.Unlock() 36 | } 37 | 38 | // newConn wraps a standard net.Conn to a poolConn net.Conn. 39 | func (c *channelPool) wrapConn(conn net.Conn) net.Conn { 40 | p := &PoolConn{c: c} 41 | p.Conn = conn 42 | return p 43 | } 44 | -------------------------------------------------------------------------------- /vendor/gopkg.in/fatih/pool.v2/pool.go: -------------------------------------------------------------------------------- 1 | // Package pool implements a pool of net.Conn interfaces to manage and reuse them. 2 | package pool 3 | 4 | import ( 5 | "errors" 6 | "net" 7 | ) 8 | 9 | var ( 10 | // ErrClosed is the error resulting if the pool is closed via pool.Close(). 11 | ErrClosed = errors.New("pool is closed") 12 | ) 13 | 14 | // Pool interface describes a pool implementation. A pool should have maximum 15 | // capacity. An ideal pool is threadsafe and easy to use. 16 | type Pool interface { 17 | // Get returns a new connection from the pool. Closing the connections puts 18 | // it back to the Pool. Closing it when the pool is destroyed or full will 19 | // be counted as an error. 20 | Get() (net.Conn, error) 21 | 22 | // Close closes the pool and all its connections. After Close() the pool is 23 | // no longer usable. 24 | Close() 25 | 26 | // Len returns the current number of connections of the pool. 27 | Len() int 28 | } 29 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "LmLMEdq8YG9USf9/MpUoZKeokTY=", 7 | "path": "github.com/artyom/autoflags", 8 | "revision": "8d4acb53349c5f11e663d75ad9d3e54f5ce17d3c", 9 | "revisionTime": "2015-06-17T10:05:51Z" 10 | }, 11 | { 12 | "checksumSHA1": "xLtviWfkUxeukwBUdyMYMs0JC+U=", 13 | "path": "github.com/collinmsn/resp", 14 | "revision": "4e07f2f7996886e54362c473f6f4f252d23546da", 15 | "revisionTime": "2017-06-02T10:16:52Z" 16 | }, 17 | { 18 | "checksumSHA1": "5rPfda8jFccr3A6heL+JAmi9K9g=", 19 | "origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew", 20 | "path": "github.com/davecgh/go-spew/spew", 21 | "revision": "c5d7a69bf8a2c9c374798160849c071093e41dd1", 22 | "revisionTime": "2016-04-18T22:58:27Z" 23 | }, 24 | { 25 | "checksumSHA1": "2IZGKMD4y38B05rNQIb5tnRIpR8=", 26 | "path": "github.com/garyburd/redigo/internal", 27 | "revision": "785e0ca9319196b94fd61abd4c9eab5c11777377", 28 | "revisionTime": "2015-01-27T01:26:49Z" 29 | }, 30 | { 31 | "checksumSHA1": "kcLMU8PuaamhBuD9LpS5Zlbn248=", 32 | "path": "github.com/garyburd/redigo/redis", 33 | "revision": "785e0ca9319196b94fd61abd4c9eab5c11777377", 34 | "revisionTime": "2015-01-27T01:26:49Z" 35 | }, 36 | { 37 | "checksumSHA1": "YOZo0sIVeRBGXPbFnh4TGtSgO2Q=", 38 | "path": "github.com/ngaut/logging", 39 | "revision": "f98f5f4cd523647678af6f1726c5b5a14c9193d0", 40 | "revisionTime": "2015-02-03T14:11:11Z" 41 | }, 42 | { 43 | "checksumSHA1": "zKKp5SZ3d3ycKe4EKMNT0BqAWBw=", 44 | "origin": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib", 45 | "path": "github.com/pmezard/go-difflib/difflib", 46 | "revision": "c5d7a69bf8a2c9c374798160849c071093e41dd1", 47 | "revisionTime": "2016-04-18T22:58:27Z" 48 | }, 49 | { 50 | "checksumSHA1": "Bn333k9lTndxU3D6n/G5c+GMcYY=", 51 | "path": "github.com/stretchr/testify/assert", 52 | "revision": "c5d7a69bf8a2c9c374798160849c071093e41dd1", 53 | "revisionTime": "2016-04-18T22:58:27Z" 54 | }, 55 | { 56 | "checksumSHA1": "1xrcK4/LiP98awGcvqTL5CClTIc=", 57 | "path": "gopkg.in/fatih/pool.v2", 58 | "revision": "6e328e67893eb46323ad06f0e92cb9536babbabc", 59 | "revisionTime": "2017-01-11T21:10:42Z" 60 | } 61 | ], 62 | "rootPath": "github.com/collinmsn/rcproxy" 63 | } 64 | --------------------------------------------------------------------------------