├── .gitignore ├── vendor └── github.com │ ├── redis-go │ └── redcon │ │ ├── .travis.yml │ │ ├── logo.png │ │ ├── LICENSE │ │ ├── README.md │ │ └── append.go │ ├── go-redis │ └── redis │ │ ├── .gitignore │ │ ├── doc.go │ │ ├── internal │ │ ├── util │ │ │ ├── safe.go │ │ │ ├── unsafe.go │ │ │ └── strconv.go │ │ ├── log.go │ │ ├── util.go │ │ ├── internal.go │ │ ├── pool │ │ │ ├── pool_single.go │ │ │ ├── pool_sticky.go │ │ │ ├── conn.go │ │ │ └── pool.go │ │ ├── error.go │ │ ├── singleflight │ │ │ └── singleflight.go │ │ ├── once.go │ │ ├── consistenthash │ │ │ └── consistenthash.go │ │ ├── hashtag │ │ │ └── hashtag.go │ │ └── proto │ │ │ ├── writer.go │ │ │ ├── scan.go │ │ │ └── reader.go │ │ ├── .travis.yml │ │ ├── cluster_commands.go │ │ ├── Makefile │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── script.go │ │ ├── iterator.go │ │ ├── pipeline.go │ │ ├── tx.go │ │ ├── result.go │ │ ├── universal.go │ │ ├── README.md │ │ ├── options.go │ │ └── sentinel.go │ ├── pkg │ └── errors │ │ ├── .travis.yml │ │ ├── .gitignore │ │ ├── appveyor.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── stack.go │ │ └── errors.go │ ├── stretchr │ └── testify │ │ ├── assert │ │ ├── assertion_format.go.tmpl │ │ ├── assertion_forward.go.tmpl │ │ ├── errors.go │ │ ├── forward_assertions.go │ │ ├── doc.go │ │ └── http_assertions.go │ │ └── LICENSE │ ├── davecgh │ └── go-spew │ │ ├── LICENSE │ │ └── spew │ │ ├── bypasssafe.go │ │ ├── bypass.go │ │ ├── spew.go │ │ └── doc.go │ └── pmezard │ └── go-difflib │ └── LICENSE ├── cmd └── main.go ├── cmd_del.go ├── keyexpire_test.go ├── cmd_ping.go ├── cmd_get.go ├── t_string.go ├── cmd_ttl.go ├── cmd_lpop.go ├── cmd_rpop.go ├── cmd_rpush.go ├── cmd_lpush.go ├── Gopkg.toml ├── LICENSE ├── cmd_lrange.go ├── serve.go ├── list_test.go ├── README.md ├── Gopkg.lock ├── keyexpire.go ├── redis_test.go ├── client.go ├── cmd_set.go ├── command.go ├── redis.go ├── t_list.go └── redisdb.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea -------------------------------------------------------------------------------- /vendor/github.com/redis-go/redcon/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/.gitignore: -------------------------------------------------------------------------------- 1 | *.rdb 2 | testdata/*/ 3 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package redis implements a Redis client. 3 | */ 4 | package redis 5 | -------------------------------------------------------------------------------- /vendor/github.com/redis-go/redcon/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/redis-go/redis/HEAD/vendor/github.com/redis-go/redcon/logo.png -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/redis-go/redis" 5 | "log" 6 | ) 7 | 8 | func main() { 9 | log.Println("Work in Progress version") 10 | log.Fatal(redis.Run(":6379")) 11 | } 12 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go_import_path: github.com/pkg/errors 3 | go: 4 | - 1.4.3 5 | - 1.5.4 6 | - 1.6.2 7 | - 1.7.1 8 | - tip 9 | 10 | script: 11 | - go test -v ./... 12 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/util/safe.go: -------------------------------------------------------------------------------- 1 | // +build appengine 2 | 3 | package util 4 | 5 | func BytesToString(b []byte) string { 6 | return string(b) 7 | } 8 | 9 | func StringToBytes(s string) []byte { 10 | return []byte(s) 11 | } 12 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_format.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentFormat}} 2 | func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool { 3 | if h, ok := t.(tHelper); ok { h.Helper() } 4 | return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl: -------------------------------------------------------------------------------- 1 | {{.CommentWithoutT "a"}} 2 | func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool { 3 | if h, ok := a.t.(tHelper); ok { h.Helper() } 4 | return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/log.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | var Logger *log.Logger 9 | 10 | func Logf(s string, args ...interface{}) { 11 | if Logger == nil { 12 | return 13 | } 14 | Logger.Output(2, fmt.Sprintf(s, args...)) 15 | } 16 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | 4 | services: 5 | - redis-server 6 | 7 | go: 8 | - 1.7.x 9 | - 1.8.x 10 | - 1.9.x 11 | - 1.10.x 12 | - 1.11.x 13 | - tip 14 | 15 | matrix: 16 | allow_failures: 17 | - go: tip 18 | 19 | install: 20 | - go get github.com/onsi/ginkgo 21 | - go get github.com/onsi/gomega 22 | -------------------------------------------------------------------------------- /cmd_del.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/redis-go/redcon" 5 | ) 6 | 7 | func DelCommand(c *Client, cmd redcon.Command) { 8 | db := c.Db() 9 | keys := make([]*string, 0, len(cmd.Args)-1) 10 | for i := 1; i < len(cmd.Args); i++ { 11 | k := string(cmd.Args[i]) 12 | keys = append(keys, &k) 13 | } 14 | dels := db.Delete(keys...) 15 | c.Conn().WriteInt(dels) 16 | } 17 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /keyexpire_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestKeyExpirer(t *testing.T) { 10 | s, err := c.Set("a", "v", 53*time.Millisecond).Result() 11 | assert.Equal(t, "OK", s) 12 | assert.NoError(t, err) 13 | time.Sleep(1 * time.Second) 14 | 15 | s, err = c.Get("a").Result() 16 | assert.NotEqual(t, "v", s) 17 | assert.Error(t, err) 18 | } 19 | -------------------------------------------------------------------------------- /cmd_ping.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "bytes" 5 | "github.com/redis-go/redcon" 6 | ) 7 | 8 | func PingCommand(c *Client, cmd redcon.Command) { 9 | if len(cmd.Args) > 1 { 10 | var buf bytes.Buffer 11 | for i := 1; i < len(cmd.Args); i++ { 12 | buf.Write(cmd.Args[i]) 13 | buf.WriteString(" ") 14 | } 15 | s := buf.String() 16 | s = s[:len(s)-1] 17 | c.Conn().WriteString(s) 18 | return 19 | } 20 | c.Conn().WriteString("PONG") 21 | } 22 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/util/unsafe.go: -------------------------------------------------------------------------------- 1 | // +build !appengine 2 | 3 | package util 4 | 5 | import ( 6 | "unsafe" 7 | ) 8 | 9 | // BytesToString converts byte slice to string. 10 | func BytesToString(b []byte) string { 11 | return *(*string)(unsafe.Pointer(&b)) 12 | } 13 | 14 | // StringToBytes converts string to byte slice. 15 | func StringToBytes(s string) []byte { 16 | return *(*[]byte)(unsafe.Pointer( 17 | &struct { 18 | string 19 | Cap int 20 | }{s, len(s)}, 21 | )) 22 | } 23 | -------------------------------------------------------------------------------- /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 -include-format-funcs 17 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/cluster_commands.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import "sync/atomic" 4 | 5 | func (c *ClusterClient) DBSize() *IntCmd { 6 | cmd := NewIntCmd("dbsize") 7 | var size int64 8 | err := c.ForEachMaster(func(master *Client) error { 9 | n, err := master.DBSize().Result() 10 | if err != nil { 11 | return err 12 | } 13 | atomic.AddInt64(&size, n) 14 | return nil 15 | }) 16 | if err != nil { 17 | cmd.setErr(err) 18 | return cmd 19 | } 20 | cmd.val = size 21 | return cmd 22 | } 23 | -------------------------------------------------------------------------------- /cmd_get.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/redis-go/redcon" 6 | ) 7 | 8 | func GetCommand(c *Client, cmd redcon.Command) { 9 | key := string(cmd.Args[1]) 10 | 11 | i := c.Db().GetOrExpire(&key, true) 12 | if i == nil { 13 | c.Conn().WriteNull() 14 | return 15 | } 16 | 17 | if i.Type() != StringType { 18 | c.Conn().WriteError(fmt.Sprintf("%s: key is a %s not a %s", WrongTypeErr, i.TypeFancy(), StringTypeFancy)) 19 | return 20 | } 21 | 22 | v := *i.Value().(*string) 23 | c.Conn().WriteBulkString(v) 24 | } 25 | -------------------------------------------------------------------------------- /t_string.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | const StringType = uint64(0) 4 | const StringTypeFancy = "string" 5 | 6 | var _ Item = (*String)(nil) 7 | 8 | type String struct { 9 | value *string 10 | } 11 | 12 | func NewString(value *string) *String { 13 | return &String{value: value} 14 | } 15 | 16 | func (s *String) Value() interface{} { 17 | return s.value 18 | } 19 | 20 | func (s *String) Type() uint64 { 21 | return StringType 22 | } 23 | 24 | func (s *String) TypeFancy() string { 25 | return StringTypeFancy 26 | } 27 | 28 | func (s *String) OnDelete(key *string, db *RedisDb) { 29 | } 30 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/util.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "github.com/go-redis/redis/internal/util" 4 | 5 | func ToLower(s string) string { 6 | if isLower(s) { 7 | return s 8 | } 9 | 10 | b := make([]byte, len(s)) 11 | for i := range b { 12 | c := s[i] 13 | if c >= 'A' && c <= 'Z' { 14 | c += 'a' - 'A' 15 | } 16 | b[i] = c 17 | } 18 | return util.BytesToString(b) 19 | } 20 | 21 | func isLower(s string) bool { 22 | for i := 0; i < len(s); i++ { 23 | c := s[i] 24 | if c >= 'A' && c <= 'Z' { 25 | return false 26 | } 27 | } 28 | return true 29 | } 30 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/util/strconv.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "strconv" 4 | 5 | func Atoi(b []byte) (int, error) { 6 | return strconv.Atoi(BytesToString(b)) 7 | } 8 | 9 | func ParseInt(b []byte, base int, bitSize int) (int64, error) { 10 | return strconv.ParseInt(BytesToString(b), base, bitSize) 11 | } 12 | 13 | func ParseUint(b []byte, base int, bitSize int) (uint64, error) { 14 | return strconv.ParseUint(BytesToString(b), base, bitSize) 15 | } 16 | 17 | func ParseFloat(b []byte, bitSize int) (float64, error) { 18 | return strconv.ParseFloat(BytesToString(b), bitSize) 19 | } 20 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/Makefile: -------------------------------------------------------------------------------- 1 | all: testdeps 2 | go test ./... 3 | go test ./... -short -race 4 | env GOOS=linux GOARCH=386 go test ./... 5 | go vet 6 | 7 | testdeps: testdata/redis/src/redis-server 8 | 9 | bench: testdeps 10 | go test ./... -test.run=NONE -test.bench=. -test.benchmem 11 | 12 | .PHONY: all test testdeps bench 13 | 14 | testdata/redis: 15 | mkdir -p $@ 16 | wget -qO- https://github.com/antirez/redis/archive/unstable.tar.gz | tar xvz --strip-components=1 -C $@ 17 | 18 | testdata/redis/src/redis-server: testdata/redis 19 | sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $ maxBackoff || backoff < minBackoff { 17 | backoff = maxBackoff 18 | } 19 | 20 | if backoff == 0 { 21 | return 0 22 | } 23 | return time.Duration(rand.Int63n(int64(backoff))) 24 | } 25 | -------------------------------------------------------------------------------- /cmd_lpop.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/redis-go/redcon" 6 | ) 7 | 8 | func LPopCommand(c *Client, cmd redcon.Command) { 9 | if len(cmd.Args) < 2 { 10 | c.Conn().WriteError(fmt.Sprintf(WrongNumOfArgsErr, "lpop")) 11 | return 12 | } 13 | key := string(cmd.Args[1]) 14 | 15 | db := c.Db() 16 | i := db.GetOrExpire(&key, true) 17 | if i == nil { 18 | c.Conn().WriteNull() 19 | return 20 | } else if i.Type() != ListType { 21 | c.Conn().WriteError(fmt.Sprintf("%s: key is a %s not a %s", WrongTypeErr, i.TypeFancy(), ListTypeFancy)) 22 | return 23 | } 24 | 25 | l := i.(*List) 26 | c.Redis().Mu().Lock() 27 | v, b := l.LPop() 28 | if b { 29 | db.Delete(&key) 30 | } 31 | c.Redis().Mu().Unlock() 32 | 33 | c.Conn().WriteBulkString(*v) 34 | } 35 | -------------------------------------------------------------------------------- /cmd_rpop.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/redis-go/redcon" 6 | ) 7 | 8 | func RPopCommand(c *Client, cmd redcon.Command) { 9 | if len(cmd.Args) < 2 { 10 | c.Conn().WriteError(fmt.Sprintf(WrongNumOfArgsErr, "rpop")) 11 | return 12 | } 13 | key := string(cmd.Args[1]) 14 | 15 | db := c.Db() 16 | i := db.GetOrExpire(&key, true) 17 | if i == nil { 18 | c.Conn().WriteNull() 19 | return 20 | } else if i.Type() != ListType { 21 | c.Conn().WriteError(fmt.Sprintf("%s: key is a %s not a %s", WrongTypeErr, i.TypeFancy(), ListTypeFancy)) 22 | return 23 | } 24 | 25 | l := i.(*List) 26 | c.Redis().Mu().Lock() 27 | v, b := l.RPop() 28 | if b { 29 | db.Delete(&key) 30 | } 31 | c.Redis().Mu().Unlock() 32 | 33 | c.Conn().WriteBulkString(*v) 34 | } 35 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: build-{build}.{branch} 2 | 3 | clone_folder: C:\gopath\src\github.com\pkg\errors 4 | shallow_clone: true # for startup speed 5 | 6 | environment: 7 | GOPATH: C:\gopath 8 | 9 | platform: 10 | - x64 11 | 12 | # http://www.appveyor.com/docs/installed-software 13 | install: 14 | # some helpful output for debugging builds 15 | - go version 16 | - go env 17 | # pre-installed MinGW at C:\MinGW is 32bit only 18 | # but MSYS2 at C:\msys64 has mingw64 19 | - set PATH=C:\msys64\mingw64\bin;%PATH% 20 | - gcc --version 21 | - g++ --version 22 | 23 | build_script: 24 | - go install -v ./... 25 | 26 | test_script: 27 | - set PATH=C:\gopath\bin;%PATH% 28 | - go test -v ./... 29 | 30 | #artifacts: 31 | # - path: '%GOPATH%\bin\*.exe' 32 | deploy: off 33 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2012-2016 Dave Collins 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /cmd_rpush.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/redis-go/redcon" 6 | "time" 7 | ) 8 | 9 | func RPushCommand(c *Client, cmd redcon.Command) { 10 | if len(cmd.Args) < 3 { 11 | c.Conn().WriteError(fmt.Sprintf(WrongNumOfArgsErr, "rpush")) 12 | return 13 | } 14 | key := string(cmd.Args[1]) 15 | 16 | db := c.Db() 17 | i := db.GetOrExpire(&key, true) 18 | if i == nil { 19 | i := NewList() 20 | db.Set(&key, i, false, time.Time{}) 21 | } else if i.Type() != ListType { 22 | c.Conn().WriteError(fmt.Sprintf("%s: key is a %s not a %s", WrongTypeErr, i.TypeFancy(), ListTypeFancy)) 23 | return 24 | } 25 | 26 | l := i.(*List) 27 | var length int 28 | c.Redis().Mu().Lock() 29 | for j := 2; j < len(cmd.Args); j++ { 30 | v := string(cmd.Args[j]) 31 | length = l.RPush(&v) 32 | } 33 | c.Redis().Mu().Unlock() 34 | 35 | c.Conn().WriteInt(length) 36 | } 37 | -------------------------------------------------------------------------------- /cmd_lpush.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/redis-go/redcon" 6 | "time" 7 | ) 8 | 9 | func LPushCommand(c *Client, cmd redcon.Command) { 10 | if len(cmd.Args) < 3 { 11 | c.Conn().WriteError(fmt.Sprintf(WrongNumOfArgsErr, "lpush")) 12 | return 13 | } 14 | key := string(cmd.Args[1]) 15 | fmt.Println("KEY:", key) 16 | db := c.Db() 17 | i := db.GetOrExpire(&key, true) 18 | if i == nil { 19 | i = NewList() 20 | db.Set(&key, i, false, time.Time{}) 21 | fmt.Println("CREATED NEW LIST") 22 | } else if i.Type() != ListType { 23 | c.Conn().WriteError(fmt.Sprintf("%s: key is a %s not a %s", WrongTypeErr, i.TypeFancy(), ListTypeFancy)) 24 | return 25 | } 26 | 27 | l := i.(*List) 28 | var length int 29 | c.Redis().Mu().Lock() 30 | for j := 2; j < len(cmd.Args); j++ { 31 | v := string(cmd.Args[j]) 32 | length = l.LPush(&v) 33 | } 34 | c.Redis().Mu().Unlock() 35 | 36 | c.Conn().WriteInt(length) 37 | } 38 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/go-redis/redis" 31 | 32 | [[constraint]] 33 | branch = "master" 34 | name = "github.com/redis-go/redcon" 35 | 36 | [prune] 37 | go-tests = true 38 | unused-packages = true 39 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/pool/pool_single.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | type SingleConnPool struct { 4 | cn *Conn 5 | } 6 | 7 | var _ Pooler = (*SingleConnPool)(nil) 8 | 9 | func NewSingleConnPool(cn *Conn) *SingleConnPool { 10 | return &SingleConnPool{ 11 | cn: cn, 12 | } 13 | } 14 | 15 | func (p *SingleConnPool) NewConn() (*Conn, error) { 16 | panic("not implemented") 17 | } 18 | 19 | func (p *SingleConnPool) CloseConn(*Conn) error { 20 | panic("not implemented") 21 | } 22 | 23 | func (p *SingleConnPool) Get() (*Conn, error) { 24 | return p.cn, nil 25 | } 26 | 27 | func (p *SingleConnPool) Put(cn *Conn) { 28 | if p.cn != cn { 29 | panic("p.cn != cn") 30 | } 31 | } 32 | 33 | func (p *SingleConnPool) Remove(cn *Conn) { 34 | if p.cn != cn { 35 | panic("p.cn != cn") 36 | } 37 | } 38 | 39 | func (p *SingleConnPool) Len() int { 40 | return 1 41 | } 42 | 43 | func (p *SingleConnPool) IdleLen() int { 44 | return 0 45 | } 46 | 47 | func (p *SingleConnPool) Stats() *Stats { 48 | return nil 49 | } 50 | 51 | func (p *SingleConnPool) Close() error { 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Robin Brämer 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/redis-go/redcon/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Josh Baker 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 | -------------------------------------------------------------------------------- /cmd_lrange.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/redis-go/redcon" 6 | "strconv" 7 | ) 8 | 9 | func LRangeCommand(c *Client, cmd redcon.Command) { 10 | if len(cmd.Args) < 3 { 11 | c.Conn().WriteError(fmt.Sprintf(WrongNumOfArgsErr, "lrange")) 12 | return 13 | } 14 | key := string(cmd.Args[1]) 15 | start, err := strconv.Atoi(string(cmd.Args[2])) 16 | if err != nil { 17 | c.Conn().WriteError(fmt.Sprintf("%s: %s", InvalidIntErr, err.Error())) 18 | return 19 | } 20 | end, err := strconv.Atoi(string(cmd.Args[3])) 21 | if err != nil { 22 | c.Conn().WriteError(fmt.Sprintf("%s: %s", InvalidIntErr, err.Error())) 23 | return 24 | } 25 | 26 | db := c.Db() 27 | i := db.GetOrExpire(&key, true) 28 | if i == nil { 29 | c.Conn().WriteNull() 30 | return 31 | } else if i.Type() != ListType { 32 | c.Conn().WriteError(fmt.Sprintf("%s: key is a %s not a %s", WrongTypeErr, i.TypeFancy(), ListTypeFancy)) 33 | return 34 | } 35 | 36 | l := i.(*List) 37 | c.Redis().Mu().RLock() 38 | values := l.LRange(start, end) 39 | c.Redis().Mu().RUnlock() 40 | 41 | c.Conn().WriteArray(len(values)) 42 | for _, v := range values { 43 | c.Conn().WriteBulkString(v) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | - Cluster and Ring pipelines process commands for each node in its own goroutine. 6 | 7 | ## 6.14 8 | 9 | - Added Options.MinIdleConns. 10 | - Added Options.MaxConnAge. 11 | - PoolStats.FreeConns is renamed to PoolStats.IdleConns. 12 | - Add Client.Do to simplify creating custom commands. 13 | - Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. 14 | - Lower memory usage. 15 | 16 | ## v6.13 17 | 18 | - Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards. 19 | - Cluster client was optimized to use much less memory when reloading cluster state. 20 | - PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead. 21 | - Dialer.KeepAlive is set to 5 minutes by default. 22 | 23 | ## v6.12 24 | 25 | - ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /serve.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "crypto/tls" 5 | "github.com/redis-go/redcon" 6 | "time" 7 | ) 8 | 9 | // Run runs the default redis server. 10 | // Initializes the default redis if not already. 11 | func Run(addr string) error { 12 | return Default().Run(addr) 13 | } 14 | 15 | // Run runs the redis server. 16 | func (r *Redis) Run(addr string) error { 17 | go r.KeyExpirer().Start(100*time.Millisecond, 20, 25) 18 | return redcon.ListenAndServe( 19 | addr, 20 | func(conn redcon.Conn, cmd redcon.Command) { 21 | r.HandlerFn()(r.NewClient(conn), cmd) 22 | }, 23 | func(conn redcon.Conn) bool { 24 | return r.AcceptFn()(r.NewClient(conn)) 25 | }, 26 | func(conn redcon.Conn, err error) { 27 | r.OnCloseFn()(r.NewClient(conn), err) 28 | }, 29 | ) 30 | } 31 | 32 | // Run runs the redis server with tls. 33 | func (r *Redis) RunTLS(addr string, tls *tls.Config) error { 34 | go r.KeyExpirer().Start(100*time.Millisecond, 20, 25) 35 | return redcon.ListenAndServeTLS( 36 | addr, 37 | func(conn redcon.Conn, cmd redcon.Command) { 38 | r.HandlerFn()(r.NewClient(conn), cmd) 39 | }, 40 | func(conn redcon.Conn) bool { 41 | return r.AcceptFn()(r.NewClient(conn)) 42 | }, 43 | func(conn redcon.Conn, err error) { 44 | r.OnCloseFn()(r.NewClient(conn), err) 45 | }, 46 | tls, 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 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 met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The github.com/go-redis/redis Authors. 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 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /list_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestLPushCommand(t *testing.T) { 9 | i, err := c.LPush("lpushkey", "va").Result() 10 | assert.NoError(t, err) 11 | assert.Equal(t, int64(1), i) 12 | 13 | i, err = c.LPush("lpushkey", "vb").Result() 14 | assert.NoError(t, err) 15 | assert.Equal(t, int64(2), i) 16 | 17 | i, err = c.LPush("lpushkey", "vc", "vd").Result() 18 | assert.NoError(t, err) 19 | assert.Equal(t, int64(4), i) 20 | 21 | i, err = c.LPush("lpushkey2", "1", "2").Result() 22 | assert.NoError(t, err) 23 | assert.Equal(t, int64(2), i) 24 | 25 | i, err = c.LPush("lpush3key").Result() 26 | assert.Error(t, err) 27 | } 28 | 29 | func TestLPopCommand(t *testing.T) { 30 | s, err := c.LPop("lpop1").Result() 31 | assert.Zero(t, s) 32 | assert.Error(t, err) 33 | 34 | i, err := c.LPush("list", "a", "b").Result() 35 | assert.NoError(t, err) 36 | assert.Equal(t, int64(2), i) 37 | 38 | s, err = c.LPop("list").Result() 39 | assert.NoError(t, err) 40 | assert.Equal(t, "b", s) 41 | 42 | s, err = c.LPop("list").Result() 43 | assert.NoError(t, err) 44 | assert.Equal(t, "a", s) 45 | 46 | s, err = c.LPop("list").Result() 47 | assert.Error(t, err) 48 | assert.Zero(t, s) 49 | } 50 | 51 | func TestLRangeCommand(t *testing.T) { 52 | s, err := c.LRange("lrange", 0, 0).Result() 53 | assert.Error(t, err) 54 | assert.Zero(t, s) 55 | 56 | sl, err := c.Set("works", "esfkjsefj", 0).Result() 57 | assert.NoError(t, err) 58 | assert.NotZero(t, sl) 59 | assert.NotEmpty(t, sl) 60 | 61 | i, err := c.LPush("list2", "a", "b").Result() 62 | assert.NoError(t, err) 63 | assert.Equal(t, int64(2), i) 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 5 |
6 |

7 | 8 |

Becoming a full Redis implementation in Go

9 | 10 | This project started to see how easy it is to implement a full Redis clone in Go. 11 | As one of the side effects, imagine you could write redis modules in Go, that would be awesome! 12 | 13 | # Get involved! 14 | This project is in *work-in-progress*, so share ideas, code and have fun. 15 | 16 | The goal is to have all features and commands like the actual [redis](https://github.com/antirez/redis) written in C have. 17 | We are searching contributors! 18 | 19 | 20 | ### Documentation 21 | 22 | godoc: https://godoc.org/github.com/redis-go/redis 23 | 24 | ### Getting Started 25 | 26 | You can already test out the API. 27 | 28 | To install, run: 29 | ```bash 30 | go get -u github.com/redis-go/redis 31 | ``` 32 | 33 | 34 | ### Roadmap 35 | - [x] Client connection / request / respond 36 | - [x] RESP protocol 37 | - [x] able to register commands 38 | - [x] in-mem database 39 | - [x] active key expirer 40 | - [ ] Implementing data structures 41 | - [x] String 42 | - [x] List 43 | - [ ] Set 44 | - [ ] Sorted Set 45 | - [ ] Hash 46 | - [ ] ... 47 | - [ ] Tests 48 | - [x] For existing commands 49 | - [x] For key expirer 50 | - [ ] Alpha Release 51 | 52 | ### TODO beside Roadmap 53 | - [ ] Persistence 54 | - [ ] Redis config 55 | - [ ] Default redis config format 56 | - [ ] YAML support 57 | - [ ] Json support 58 | - [ ] Pub/Sub 59 | - [ ] Redis modules 60 | - [ ] Benchmarks 61 | - [ ] master slaves 62 | - [ ] cluster 63 | - [ ] ... 64 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/script.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | type scripter interface { 11 | Eval(script string, keys []string, args ...interface{}) *Cmd 12 | EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd 13 | ScriptExists(hashes ...string) *BoolSliceCmd 14 | ScriptLoad(script string) *StringCmd 15 | } 16 | 17 | var _ scripter = (*Client)(nil) 18 | var _ scripter = (*Ring)(nil) 19 | var _ scripter = (*ClusterClient)(nil) 20 | 21 | type Script struct { 22 | src, hash string 23 | } 24 | 25 | func NewScript(src string) *Script { 26 | h := sha1.New() 27 | io.WriteString(h, src) 28 | return &Script{ 29 | src: src, 30 | hash: hex.EncodeToString(h.Sum(nil)), 31 | } 32 | } 33 | 34 | func (s *Script) Hash() string { 35 | return s.hash 36 | } 37 | 38 | func (s *Script) Load(c scripter) *StringCmd { 39 | return c.ScriptLoad(s.src) 40 | } 41 | 42 | func (s *Script) Exists(c scripter) *BoolSliceCmd { 43 | return c.ScriptExists(s.hash) 44 | } 45 | 46 | func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd { 47 | return c.Eval(s.src, keys, args...) 48 | } 49 | 50 | func (s *Script) EvalSha(c scripter, keys []string, args ...interface{}) *Cmd { 51 | return c.EvalSha(s.hash, keys, args...) 52 | } 53 | 54 | // Run optimistically uses EVALSHA to run the script. If script does not exist 55 | // it is retried using EVAL. 56 | func (s *Script) Run(c scripter, keys []string, args ...interface{}) *Cmd { 57 | r := s.EvalSha(c, keys, args...) 58 | if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { 59 | return s.Eval(c, keys, args...) 60 | } 61 | return r 62 | } 63 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/error.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "strings" 7 | 8 | "github.com/go-redis/redis/internal/proto" 9 | ) 10 | 11 | func IsRetryableError(err error, retryTimeout bool) bool { 12 | if err == io.EOF { 13 | return true 14 | } 15 | if netErr, ok := err.(net.Error); ok { 16 | if netErr.Timeout() { 17 | return retryTimeout 18 | } 19 | return true 20 | } 21 | s := err.Error() 22 | if s == "ERR max number of clients reached" { 23 | return true 24 | } 25 | if strings.HasPrefix(s, "LOADING ") { 26 | return true 27 | } 28 | if strings.HasPrefix(s, "READONLY ") { 29 | return true 30 | } 31 | if strings.HasPrefix(s, "CLUSTERDOWN ") { 32 | return true 33 | } 34 | return false 35 | } 36 | 37 | func IsRedisError(err error) bool { 38 | _, ok := err.(proto.RedisError) 39 | return ok 40 | } 41 | 42 | func IsBadConn(err error, allowTimeout bool) bool { 43 | if err == nil { 44 | return false 45 | } 46 | if IsRedisError(err) { 47 | return strings.HasPrefix(err.Error(), "READONLY ") 48 | } 49 | if allowTimeout { 50 | if netErr, ok := err.(net.Error); ok && netErr.Timeout() { 51 | return false 52 | } 53 | } 54 | return true 55 | } 56 | 57 | func IsMovedError(err error) (moved bool, ask bool, addr string) { 58 | if !IsRedisError(err) { 59 | return 60 | } 61 | 62 | s := err.Error() 63 | if strings.HasPrefix(s, "MOVED ") { 64 | moved = true 65 | } else if strings.HasPrefix(s, "ASK ") { 66 | ask = true 67 | } else { 68 | return 69 | } 70 | 71 | ind := strings.LastIndex(s, " ") 72 | if ind == -1 { 73 | return false, false, "" 74 | } 75 | addr = s[ind+1:] 76 | return 77 | } 78 | 79 | func IsLoadingError(err error) bool { 80 | return strings.HasPrefix(err.Error(), "LOADING ") 81 | } 82 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/iterator.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import "sync" 4 | 5 | // ScanIterator is used to incrementally iterate over a collection of elements. 6 | // It's safe for concurrent use by multiple goroutines. 7 | type ScanIterator struct { 8 | mu sync.Mutex // protects Scanner and pos 9 | cmd *ScanCmd 10 | pos int 11 | } 12 | 13 | // Err returns the last iterator error, if any. 14 | func (it *ScanIterator) Err() error { 15 | it.mu.Lock() 16 | err := it.cmd.Err() 17 | it.mu.Unlock() 18 | return err 19 | } 20 | 21 | // Next advances the cursor and returns true if more values can be read. 22 | func (it *ScanIterator) Next() bool { 23 | it.mu.Lock() 24 | defer it.mu.Unlock() 25 | 26 | // Instantly return on errors. 27 | if it.cmd.Err() != nil { 28 | return false 29 | } 30 | 31 | // Advance cursor, check if we are still within range. 32 | if it.pos < len(it.cmd.page) { 33 | it.pos++ 34 | return true 35 | } 36 | 37 | for { 38 | // Return if there is no more data to fetch. 39 | if it.cmd.cursor == 0 { 40 | return false 41 | } 42 | 43 | // Fetch next page. 44 | if it.cmd._args[0] == "scan" { 45 | it.cmd._args[1] = it.cmd.cursor 46 | } else { 47 | it.cmd._args[2] = it.cmd.cursor 48 | } 49 | 50 | err := it.cmd.process(it.cmd) 51 | if err != nil { 52 | return false 53 | } 54 | 55 | it.pos = 1 56 | 57 | // Redis can occasionally return empty page. 58 | if len(it.cmd.page) > 0 { 59 | return true 60 | } 61 | } 62 | } 63 | 64 | // Val returns the key/field at the current cursor position. 65 | func (it *ScanIterator) Val() string { 66 | var v string 67 | it.mu.Lock() 68 | if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) { 69 | v = it.cmd.page[it.pos-1] 70 | } 71 | it.mu.Unlock() 72 | return v 73 | } 74 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypasssafe.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 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 running on Google App Engine, compiled by GopherJS, or 17 | // "-tags safe" is added to the go build command line. The "disableunsafe" 18 | // tag is deprecated and thus should not be used. 19 | // +build js appengine safe disableunsafe !go1.4 20 | 21 | package spew 22 | 23 | import "reflect" 24 | 25 | const ( 26 | // UnsafeDisabled is a build-time constant which specifies whether or 27 | // not access to the unsafe package is available. 28 | UnsafeDisabled = true 29 | ) 30 | 31 | // unsafeReflectValue typically converts the passed reflect.Value into a one 32 | // that bypasses the typical safety restrictions preventing access to 33 | // unaddressable and unexported data. However, doing this relies on access to 34 | // the unsafe package. This is a stub version which simply returns the passed 35 | // reflect.Value when the unsafe package is not available. 36 | func unsafeReflectValue(v reflect.Value) reflect.Value { 37 | return v 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package singleflight provides a duplicate function call suppression 18 | // mechanism. 19 | package singleflight 20 | 21 | import "sync" 22 | 23 | // call is an in-flight or completed Do call 24 | type call struct { 25 | wg sync.WaitGroup 26 | val interface{} 27 | err error 28 | } 29 | 30 | // Group represents a class of work and forms a namespace in which 31 | // units of work can be executed with duplicate suppression. 32 | type Group struct { 33 | mu sync.Mutex // protects m 34 | m map[string]*call // lazily initialized 35 | } 36 | 37 | // Do executes and returns the results of the given function, making 38 | // sure that only one execution is in-flight for a given key at a 39 | // time. If a duplicate comes in, the duplicate caller waits for the 40 | // original to complete and receives the same results. 41 | func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { 42 | g.mu.Lock() 43 | if g.m == nil { 44 | g.m = make(map[string]*call) 45 | } 46 | if c, ok := g.m[key]; ok { 47 | g.mu.Unlock() 48 | c.wg.Wait() 49 | return c.val, c.err 50 | } 51 | c := new(call) 52 | c.wg.Add(1) 53 | g.m[key] = c 54 | g.mu.Unlock() 55 | 56 | c.val, c.err = fn() 57 | c.wg.Done() 58 | 59 | g.mu.Lock() 60 | delete(g.m, key) 61 | g.mu.Unlock() 62 | 63 | return c.val, c.err 64 | } 65 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/once.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 The Camlistore Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package internal 18 | 19 | import ( 20 | "sync" 21 | "sync/atomic" 22 | ) 23 | 24 | // A Once will perform a successful action exactly once. 25 | // 26 | // Unlike a sync.Once, this Once's func returns an error 27 | // and is re-armed on failure. 28 | type Once struct { 29 | m sync.Mutex 30 | done uint32 31 | } 32 | 33 | // Do calls the function f if and only if Do has not been invoked 34 | // without error for this instance of Once. In other words, given 35 | // var once Once 36 | // if once.Do(f) is called multiple times, only the first call will 37 | // invoke f, even if f has a different value in each invocation unless 38 | // f returns an error. A new instance of Once is required for each 39 | // function to execute. 40 | // 41 | // Do is intended for initialization that must be run exactly once. Since f 42 | // is niladic, it may be necessary to use a function literal to capture the 43 | // arguments to a function to be invoked by Do: 44 | // err := config.once.Do(func() error { return config.init(filename) }) 45 | func (o *Once) Do(f func() error) error { 46 | if atomic.LoadUint32(&o.done) == 1 { 47 | return nil 48 | } 49 | // Slow-path. 50 | o.m.Lock() 51 | defer o.m.Unlock() 52 | var err error 53 | if o.done == 0 { 54 | err = f() 55 | if err == nil { 56 | atomic.StoreUint32(&o.done, 1) 57 | } 58 | } 59 | return err 60 | } 61 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import "sync" 4 | 5 | type StickyConnPool struct { 6 | pool *ConnPool 7 | reusable bool 8 | 9 | cn *Conn 10 | closed bool 11 | mu sync.Mutex 12 | } 13 | 14 | var _ Pooler = (*StickyConnPool)(nil) 15 | 16 | func NewStickyConnPool(pool *ConnPool, reusable bool) *StickyConnPool { 17 | return &StickyConnPool{ 18 | pool: pool, 19 | reusable: reusable, 20 | } 21 | } 22 | 23 | func (p *StickyConnPool) NewConn() (*Conn, error) { 24 | panic("not implemented") 25 | } 26 | 27 | func (p *StickyConnPool) CloseConn(*Conn) error { 28 | panic("not implemented") 29 | } 30 | 31 | func (p *StickyConnPool) Get() (*Conn, error) { 32 | p.mu.Lock() 33 | defer p.mu.Unlock() 34 | 35 | if p.closed { 36 | return nil, ErrClosed 37 | } 38 | if p.cn != nil { 39 | return p.cn, nil 40 | } 41 | 42 | cn, err := p.pool.Get() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | p.cn = cn 48 | return cn, nil 49 | } 50 | 51 | func (p *StickyConnPool) putUpstream() { 52 | p.pool.Put(p.cn) 53 | p.cn = nil 54 | } 55 | 56 | func (p *StickyConnPool) Put(cn *Conn) {} 57 | 58 | func (p *StickyConnPool) removeUpstream() { 59 | p.pool.Remove(p.cn) 60 | p.cn = nil 61 | } 62 | 63 | func (p *StickyConnPool) Remove(cn *Conn) { 64 | p.removeUpstream() 65 | } 66 | 67 | func (p *StickyConnPool) Len() int { 68 | p.mu.Lock() 69 | defer p.mu.Unlock() 70 | 71 | if p.cn == nil { 72 | return 0 73 | } 74 | return 1 75 | } 76 | 77 | func (p *StickyConnPool) IdleLen() int { 78 | p.mu.Lock() 79 | defer p.mu.Unlock() 80 | 81 | if p.cn == nil { 82 | return 1 83 | } 84 | return 0 85 | } 86 | 87 | func (p *StickyConnPool) Stats() *Stats { 88 | return nil 89 | } 90 | 91 | func (p *StickyConnPool) Close() error { 92 | p.mu.Lock() 93 | defer p.mu.Unlock() 94 | 95 | if p.closed { 96 | return ErrClosed 97 | } 98 | p.closed = true 99 | 100 | if p.cn != nil { 101 | if p.reusable { 102 | p.putUpstream() 103 | } else { 104 | p.removeUpstream() 105 | } 106 | } 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package consistenthash provides an implementation of a ring hash. 18 | package consistenthash 19 | 20 | import ( 21 | "hash/crc32" 22 | "sort" 23 | "strconv" 24 | ) 25 | 26 | type Hash func(data []byte) uint32 27 | 28 | type Map struct { 29 | hash Hash 30 | replicas int 31 | keys []int // Sorted 32 | hashMap map[int]string 33 | } 34 | 35 | func New(replicas int, fn Hash) *Map { 36 | m := &Map{ 37 | replicas: replicas, 38 | hash: fn, 39 | hashMap: make(map[int]string), 40 | } 41 | if m.hash == nil { 42 | m.hash = crc32.ChecksumIEEE 43 | } 44 | return m 45 | } 46 | 47 | // Returns true if there are no items available. 48 | func (m *Map) IsEmpty() bool { 49 | return len(m.keys) == 0 50 | } 51 | 52 | // Adds some keys to the hash. 53 | func (m *Map) Add(keys ...string) { 54 | for _, key := range keys { 55 | for i := 0; i < m.replicas; i++ { 56 | hash := int(m.hash([]byte(strconv.Itoa(i) + key))) 57 | m.keys = append(m.keys, hash) 58 | m.hashMap[hash] = key 59 | } 60 | } 61 | sort.Ints(m.keys) 62 | } 63 | 64 | // Gets the closest item in the hash to the provided key. 65 | func (m *Map) Get(key string) string { 66 | if m.IsEmpty() { 67 | return "" 68 | } 69 | 70 | hash := int(m.hash([]byte(key))) 71 | 72 | // Binary search for appropriate replica. 73 | idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash }) 74 | 75 | // Means we have cycled back to the first replica. 76 | if idx == len(m.keys) { 77 | idx = 0 78 | } 79 | 80 | return m.hashMap[m.keys[idx]] 81 | } 82 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/pool/conn.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "net" 5 | "sync/atomic" 6 | "time" 7 | 8 | "github.com/go-redis/redis/internal/proto" 9 | ) 10 | 11 | var noDeadline = time.Time{} 12 | 13 | type Conn struct { 14 | netConn net.Conn 15 | 16 | rd *proto.Reader 17 | rdLocked bool 18 | wr *proto.Writer 19 | 20 | InitedAt time.Time 21 | pooled bool 22 | usedAt atomic.Value 23 | } 24 | 25 | func NewConn(netConn net.Conn) *Conn { 26 | cn := &Conn{ 27 | netConn: netConn, 28 | } 29 | cn.rd = proto.NewReader(netConn) 30 | cn.wr = proto.NewWriter(netConn) 31 | cn.SetUsedAt(time.Now()) 32 | return cn 33 | } 34 | 35 | func (cn *Conn) UsedAt() time.Time { 36 | return cn.usedAt.Load().(time.Time) 37 | } 38 | 39 | func (cn *Conn) SetUsedAt(tm time.Time) { 40 | cn.usedAt.Store(tm) 41 | } 42 | 43 | func (cn *Conn) SetNetConn(netConn net.Conn) { 44 | cn.netConn = netConn 45 | cn.rd.Reset(netConn) 46 | cn.wr.Reset(netConn) 47 | } 48 | 49 | func (cn *Conn) setReadTimeout(timeout time.Duration) error { 50 | now := time.Now() 51 | cn.SetUsedAt(now) 52 | if timeout > 0 { 53 | return cn.netConn.SetReadDeadline(now.Add(timeout)) 54 | } 55 | return cn.netConn.SetReadDeadline(noDeadline) 56 | } 57 | 58 | func (cn *Conn) setWriteTimeout(timeout time.Duration) error { 59 | now := time.Now() 60 | cn.SetUsedAt(now) 61 | if timeout > 0 { 62 | return cn.netConn.SetWriteDeadline(now.Add(timeout)) 63 | } 64 | return cn.netConn.SetWriteDeadline(noDeadline) 65 | } 66 | 67 | func (cn *Conn) Write(b []byte) (int, error) { 68 | return cn.netConn.Write(b) 69 | } 70 | 71 | func (cn *Conn) RemoteAddr() net.Addr { 72 | return cn.netConn.RemoteAddr() 73 | } 74 | 75 | func (cn *Conn) WithReader(timeout time.Duration, fn func(rd *proto.Reader) error) error { 76 | _ = cn.setReadTimeout(timeout) 77 | return fn(cn.rd) 78 | } 79 | 80 | func (cn *Conn) WithWriter(timeout time.Duration, fn func(wr *proto.Writer) error) error { 81 | _ = cn.setWriteTimeout(timeout) 82 | 83 | firstErr := fn(cn.wr) 84 | err := cn.wr.Flush() 85 | if err != nil && firstErr == nil { 86 | firstErr = err 87 | } 88 | return firstErr 89 | } 90 | 91 | func (cn *Conn) Close() error { 92 | return cn.netConn.Close() 93 | } 94 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" 6 | name = "github.com/davecgh/go-spew" 7 | packages = ["spew"] 8 | pruneopts = "UT" 9 | revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" 10 | version = "v1.1.1" 11 | 12 | [[projects]] 13 | branch = "master" 14 | digest = "1:6761624242cf691dacb03aa092871fd1f492fe9459dc3b3190a61237cd702ca6" 15 | name = "github.com/go-redis/redis" 16 | packages = [ 17 | ".", 18 | "internal", 19 | "internal/consistenthash", 20 | "internal/hashtag", 21 | "internal/pool", 22 | "internal/proto", 23 | "internal/singleflight", 24 | "internal/util", 25 | ] 26 | pruneopts = "UT" 27 | revision = "69445c6e87e0af14f9625a1596620845a2b88e9e" 28 | 29 | [[projects]] 30 | digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" 31 | name = "github.com/pkg/errors" 32 | packages = ["."] 33 | pruneopts = "UT" 34 | revision = "645ef00459ed84a119197bfb8d8205042c6df63d" 35 | version = "v0.8.0" 36 | 37 | [[projects]] 38 | digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" 39 | name = "github.com/pmezard/go-difflib" 40 | packages = ["difflib"] 41 | pruneopts = "UT" 42 | revision = "792786c7400a136282c1664665ae0a8db921c6c2" 43 | version = "v1.0.0" 44 | 45 | [[projects]] 46 | branch = "master" 47 | digest = "1:b71eb6ea46bd52fc536b9b88ea26aa88d23d0af779b40515433643ecd9c5940b" 48 | name = "github.com/redis-go/redcon" 49 | packages = ["."] 50 | pruneopts = "UT" 51 | revision = "56b016127ae730019c5e3073a413ad9526440c38" 52 | 53 | [[projects]] 54 | digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83" 55 | name = "github.com/stretchr/testify" 56 | packages = ["assert"] 57 | pruneopts = "UT" 58 | revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" 59 | version = "v1.2.2" 60 | 61 | [solve-meta] 62 | analyzer-name = "dep" 63 | analyzer-version = 1 64 | input-imports = [ 65 | "github.com/go-redis/redis", 66 | "github.com/pkg/errors", 67 | "github.com/redis-go/redcon", 68 | "github.com/stretchr/testify/assert", 69 | ] 70 | solver-name = "gps-cdcl" 71 | solver-version = 1 72 | -------------------------------------------------------------------------------- /keyexpire.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "math" 5 | "time" 6 | ) 7 | 8 | type KeyExpirer interface { 9 | Start(tick time.Duration, keyNum int, againPercentage int) 10 | Stop() 11 | } 12 | 13 | var _ KeyExpirer = (*Expirer)(nil) 14 | 15 | type Expirer struct { 16 | redis *Redis 17 | 18 | done chan bool 19 | } 20 | 21 | func NewKeyExpirer(r *Redis) *Expirer { 22 | return &Expirer{ 23 | redis: r, 24 | done: make(chan bool, math.MaxInt32), 25 | } 26 | } 27 | 28 | // Start starts the Expirer. 29 | // 30 | // tick - How fast is the cleaner triggered. 31 | // 32 | // randomKeys - Amount of random expiring keys to get checked. 33 | // 34 | // againPercentage - If more than x% of keys were expired, start again in same tick. 35 | func (e *Expirer) Start(tick time.Duration, randomKeys int, againPercentage int) { 36 | ticker := time.NewTicker(tick) 37 | for { 38 | select { 39 | case <-ticker.C: 40 | e.do(randomKeys, againPercentage) 41 | case <-e.done: 42 | ticker.Stop() 43 | return 44 | } 45 | } 46 | } 47 | 48 | // Stop stops the 49 | func (e *Expirer) Stop() { 50 | if e.done != nil { 51 | e.done <- true 52 | close(e.done) 53 | } 54 | } 55 | 56 | func (e *Expirer) do(randomKeys, againPercentage int) { 57 | var deletedKeys int 58 | 59 | dbs := make(map[*RedisDb]struct{}) 60 | for _, db := range e.Redis().RedisDbs() { 61 | if !db.HasExpiringKeys() { 62 | continue 63 | } 64 | dbs[db] = struct{}{} 65 | } 66 | 67 | if len(dbs) == 0 { 68 | return 69 | } 70 | 71 | for c := 0; c < randomKeys; c++ { 72 | // get random db 73 | db := func() *RedisDb { 74 | for db := range dbs { 75 | return db 76 | } 77 | return nil // won't happen 78 | }() 79 | 80 | // get random key 81 | k := func() *string { 82 | for k := range db.ExpiringKeys() { 83 | return &k 84 | } 85 | return nil 86 | }() 87 | 88 | if k == nil { 89 | continue 90 | } 91 | 92 | // del if expired 93 | if db.DeleteExpired(k) != 0 { 94 | deletedKeys++ 95 | } 96 | } 97 | 98 | // Start again in new goroutine so keys are deleted fast 99 | if againPercentage > 0 && deletedKeys/randomKeys*100 > againPercentage { 100 | go e.do(randomKeys, againPercentage) 101 | } 102 | } 103 | 104 | // Redis gets the redis instance. 105 | func (e *Expirer) Redis() *Redis { 106 | return e.redis 107 | } 108 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/README.md: -------------------------------------------------------------------------------- 1 | # errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) 2 | 3 | Package errors provides simple error handling primitives. 4 | 5 | `go get github.com/pkg/errors` 6 | 7 | The traditional error handling idiom in Go is roughly akin to 8 | ```go 9 | if err != nil { 10 | return err 11 | } 12 | ``` 13 | which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. 14 | 15 | ## Adding context to an error 16 | 17 | The errors.Wrap function returns a new error that adds context to the original error. For example 18 | ```go 19 | _, err := ioutil.ReadAll(r) 20 | if err != nil { 21 | return errors.Wrap(err, "read failed") 22 | } 23 | ``` 24 | ## Retrieving the cause of an error 25 | 26 | Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. 27 | ```go 28 | type causer interface { 29 | Cause() error 30 | } 31 | ``` 32 | `errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: 33 | ```go 34 | switch err := errors.Cause(err).(type) { 35 | case *MyError: 36 | // handle specifically 37 | default: 38 | // unknown error 39 | } 40 | ``` 41 | 42 | [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). 43 | 44 | ## Contributing 45 | 46 | We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. 47 | 48 | Before proposing a change, please discuss your change by raising an issue. 49 | 50 | ## Licence 51 | 52 | BSD-2-Clause 53 | -------------------------------------------------------------------------------- /redis_test.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | // redis server 11 | var r = Default() 12 | 13 | // redis client 14 | var c = redis.NewClient(&redis.Options{ 15 | Addr: ":6379", 16 | }) 17 | 18 | func init() { 19 | go r.Run(":6379") 20 | } 21 | 22 | func TestPingCommand(t *testing.T) { 23 | s, err := c.Ping().Result() 24 | assert.Equal(t, "PONG", s) 25 | assert.NoError(t, err) 26 | 27 | pingCmd := redis.NewStringCmd("ping", "Hello,", "redis server!") 28 | c.Process(pingCmd) 29 | s, err = pingCmd.Result() 30 | assert.Equal(t, "Hello, redis server!", s) 31 | assert.NoError(t, err) 32 | } 33 | 34 | func TestSetCommand(t *testing.T) { 35 | s, err := c.Set("k", "v", 0).Result() 36 | assert.Equal(t, "OK", s) 37 | assert.NoError(t, err) 38 | 39 | s, err = c.Set("k2", nil, 0).Result() 40 | assert.Equal(t, "OK", s) 41 | assert.NoError(t, err) 42 | 43 | s, err = c.Set("k3", "v", 1*time.Hour).Result() 44 | assert.Equal(t, "OK", s) 45 | assert.NoError(t, err) 46 | } 47 | 48 | func TestGetCommand(t *testing.T) { 49 | s, err := c.Get("k").Result() 50 | assert.Equal(t, "v", s) 51 | assert.NoError(t, err) 52 | } 53 | 54 | func TestDelCommand(t *testing.T) { 55 | i, err := c.Del("k", "k3").Result() 56 | assert.Equal(t, i, int64(2)) 57 | assert.NoError(t, err) 58 | 59 | i, err = c.Del("abc").Result() 60 | assert.Zero(t, i) 61 | assert.NoError(t, err) 62 | } 63 | 64 | func TestTtlCommand(t *testing.T) { 65 | s, err := c.Set("aKey", "hey", 1*time.Minute).Result() 66 | assert.Equal(t, "OK", s) 67 | assert.NoError(t, err) 68 | s, err = c.Set("bKey", "hallo", 0).Result() 69 | assert.Equal(t, "OK", s) 70 | assert.NoError(t, err) 71 | 72 | ttl, err := c.TTL("aKey").Result() 73 | assert.True(t, ttl.Seconds() > 55 && ttl.Seconds() < 61, "ttl: %d", ttl) 74 | assert.NoError(t, err) 75 | 76 | ttl, err = c.TTL("none").Result() 77 | assert.Equal(t, time.Duration(-2000000000), ttl) 78 | assert.NoError(t, err) 79 | 80 | ttl, err = c.TTL("bKey").Result() 81 | assert.NoError(t, err) 82 | assert.Equal(t, time.Duration(-1000000000), ttl) 83 | } 84 | 85 | func TestExpiry(t *testing.T) { 86 | s, err := c.Set("x", "val", 10*time.Millisecond).Result() 87 | assert.NoError(t, err) 88 | assert.Equal(t, "OK", s) 89 | 90 | time.Sleep(10 * time.Millisecond) 91 | 92 | s, err = c.Get("x").Result() 93 | assert.Equal(t, "", s) 94 | assert.Error(t, err) 95 | } 96 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/redis-go/redcon" 5 | "sync" 6 | ) 7 | 8 | // TODO Client flags 9 | const ( 10 | // client is a master 11 | // client is a slave 12 | // ... 13 | ) 14 | 15 | // A connected Client. 16 | type Client struct { 17 | clientId ClientId 18 | // The client connection. 19 | conn redcon.Conn 20 | 21 | // Selected database (default 0) 22 | db DatabaseId 23 | 24 | redis *Redis 25 | } 26 | 27 | // NewClient creates new client and adds it to the redis. 28 | func (r *Redis) NewClient(conn redcon.Conn) *Client { 29 | c := &Client{ 30 | conn: conn, 31 | redis: r, 32 | clientId: r.NextClientId(), 33 | } 34 | return c 35 | } 36 | 37 | // NextClientId atomically gets and increments a counter to return the next client id. 38 | func (r *Redis) NextClientId() ClientId { 39 | r.Mu().Lock() 40 | defer r.Mu().Unlock() 41 | id := r.nextClientId 42 | r.nextClientId++ 43 | return id 44 | } 45 | 46 | // Clients gets the current connected clients. 47 | func (r *Redis) Clients() Clients { 48 | r.Mu().RLock() 49 | defer r.Mu().RUnlock() 50 | return r.clients 51 | } 52 | 53 | func (r *Redis) getClients() Clients { 54 | return r.clients 55 | } 56 | 57 | // Redis gets the redis instance. 58 | func (c *Client) Redis() *Redis { 59 | return c.redis 60 | } 61 | 62 | // Mu the mutex. 63 | func (c *Client) Mu() *sync.RWMutex { 64 | return c.Redis().Mu() 65 | } 66 | 67 | // ClientId get the client id. 68 | func (c *Client) ClientId() ClientId { 69 | return c.clientId 70 | } 71 | 72 | // The client's connection. 73 | func (c *Client) Conn() redcon.Conn { 74 | c.Mu().RLock() 75 | defer c.Mu().RUnlock() 76 | return c.conn 77 | } 78 | 79 | // SelectDb selects the clients database. 80 | func (c *Client) SelectDb(db DatabaseId) { 81 | c.Mu().Lock() 82 | defer c.Mu().Unlock() 83 | c.db = db 84 | } 85 | 86 | // DbId gets the clients selected database id. 87 | func (c *Client) DbId() DatabaseId { 88 | c.Mu().RLock() 89 | defer c.Mu().RUnlock() 90 | return c.db 91 | } 92 | 93 | // Db gets the clients selected database. 94 | func (c *Client) Db() *RedisDb { 95 | return c.Redis().RedisDb(c.DbId()) 96 | } 97 | 98 | // Disconnects and removes a Client. 99 | func (c *Client) FreeClient() { 100 | c.Conn().Close() // TODO should we log on error? 101 | c.Mu().Lock() 102 | defer c.Mu().Unlock() 103 | delete(c.Redis().getClients(), c.ClientId()) 104 | } 105 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/pipeline.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/go-redis/redis/internal/pool" 7 | ) 8 | 9 | type pipelineExecer func([]Cmder) error 10 | 11 | type Pipeliner interface { 12 | StatefulCmdable 13 | Process(cmd Cmder) error 14 | Close() error 15 | Discard() error 16 | Exec() ([]Cmder, error) 17 | } 18 | 19 | var _ Pipeliner = (*Pipeline)(nil) 20 | 21 | // Pipeline implements pipelining as described in 22 | // http://redis.io/topics/pipelining. It's safe for concurrent use 23 | // by multiple goroutines. 24 | type Pipeline struct { 25 | statefulCmdable 26 | 27 | exec pipelineExecer 28 | 29 | mu sync.Mutex 30 | cmds []Cmder 31 | closed bool 32 | } 33 | 34 | // Process queues the cmd for later execution. 35 | func (c *Pipeline) Process(cmd Cmder) error { 36 | c.mu.Lock() 37 | c.cmds = append(c.cmds, cmd) 38 | c.mu.Unlock() 39 | return nil 40 | } 41 | 42 | // Close closes the pipeline, releasing any open resources. 43 | func (c *Pipeline) Close() error { 44 | c.mu.Lock() 45 | c.discard() 46 | c.closed = true 47 | c.mu.Unlock() 48 | return nil 49 | } 50 | 51 | // Discard resets the pipeline and discards queued commands. 52 | func (c *Pipeline) Discard() error { 53 | c.mu.Lock() 54 | err := c.discard() 55 | c.mu.Unlock() 56 | return err 57 | } 58 | 59 | func (c *Pipeline) discard() error { 60 | if c.closed { 61 | return pool.ErrClosed 62 | } 63 | c.cmds = c.cmds[:0] 64 | return nil 65 | } 66 | 67 | // Exec executes all previously queued commands using one 68 | // client-server roundtrip. 69 | // 70 | // Exec always returns list of commands and error of the first failed 71 | // command if any. 72 | func (c *Pipeline) Exec() ([]Cmder, error) { 73 | c.mu.Lock() 74 | defer c.mu.Unlock() 75 | 76 | if c.closed { 77 | return nil, pool.ErrClosed 78 | } 79 | 80 | if len(c.cmds) == 0 { 81 | return nil, nil 82 | } 83 | 84 | cmds := c.cmds 85 | c.cmds = nil 86 | 87 | return cmds, c.exec(cmds) 88 | } 89 | 90 | func (c *Pipeline) pipelined(fn func(Pipeliner) error) ([]Cmder, error) { 91 | if err := fn(c); err != nil { 92 | return nil, err 93 | } 94 | cmds, err := c.Exec() 95 | _ = c.Close() 96 | return cmds, err 97 | } 98 | 99 | func (c *Pipeline) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { 100 | return c.pipelined(fn) 101 | } 102 | 103 | func (c *Pipeline) Pipeline() Pipeliner { 104 | return c 105 | } 106 | 107 | func (c *Pipeline) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { 108 | return c.pipelined(fn) 109 | } 110 | 111 | func (c *Pipeline) TxPipeline() Pipeliner { 112 | return c 113 | } 114 | -------------------------------------------------------------------------------- /cmd_set.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "fmt" 5 | "github.com/redis-go/redcon" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // SET key value [NX] [XX] [EX ] [PX ] 12 | func SetCommand(c *Client, cmd redcon.Command) { 13 | if len(cmd.Args) == 1 { // nothing done 14 | c.Conn().WriteString("OK") 15 | return 16 | } 17 | 18 | k := string(cmd.Args[1]) 19 | key := &k 20 | var value string 21 | if len(cmd.Args) > 1 { 22 | value = string(cmd.Args[2]) 23 | } 24 | 25 | var yesExpire bool 26 | var expire time.Time 27 | 28 | var isEX bool 29 | var isPX bool 30 | 31 | var NX bool 32 | var XX bool 33 | 34 | if len(cmd.Args) > 2 { 35 | for i := 3; i+1 < len(cmd.Args); { 36 | arg := strings.ToLower(string(cmd.Args[i])) 37 | switch arg { 38 | default: 39 | c.Conn().WriteError(SyntaxErr) 40 | return 41 | case "ex": 42 | if isPX { // is already px 43 | c.Conn().WriteError(SyntaxErr) 44 | return 45 | } 46 | 47 | // was last arg? 48 | if len(cmd.Args) == i { 49 | c.Conn().WriteError(SyntaxErr) 50 | return 51 | } 52 | 53 | // read next arg 54 | i++ 55 | i, err := strconv.ParseUint(string(cmd.Args[i]), 10, 64) 56 | if err != nil { 57 | c.Conn().WriteError(fmt.Sprintf("%s: %s", InvalidIntErr, err.Error())) 58 | return 59 | } 60 | if i == 0 { 61 | c.Conn().WriteError("ERR invalid expire time in set: cannot be 0") 62 | return 63 | } 64 | expire = time.Now().Add(time.Duration(i * uint64(time.Second))) 65 | yesExpire, isEX = true, true 66 | i++ 67 | continue 68 | case "px": 69 | if isEX { // is already ex 70 | c.Conn().WriteError(SyntaxErr) 71 | return 72 | } 73 | 74 | // was last arg? 75 | if len(cmd.Args) == i { 76 | c.Conn().WriteError(SyntaxErr) 77 | return 78 | } 79 | 80 | // read next arg 81 | i++ 82 | i, err := strconv.ParseUint(string(cmd.Args[i]), 10, 64) 83 | if err != nil { 84 | c.Conn().WriteError(fmt.Sprintf("%s: %s", InvalidIntErr, err.Error())) 85 | return 86 | } 87 | if i == 0 { 88 | c.Conn().WriteError("ERR invalid expire time in set: cannot be 0") 89 | return 90 | } 91 | expire = time.Now().Add(time.Duration(i * uint64(time.Millisecond))) 92 | yesExpire, isPX = true, true 93 | i++ 94 | continue 95 | case "nx": 96 | if XX { // is already xx 97 | c.Conn().WriteError(SyntaxErr) 98 | return 99 | } 100 | NX = true 101 | i++ 102 | continue 103 | case "xx": 104 | if NX { // is already nx 105 | c.Conn().WriteError(SyntaxErr) 106 | return 107 | } 108 | XX = true 109 | i++ 110 | continue 111 | } 112 | } 113 | } 114 | 115 | // clients selected db 116 | db := c.Db() 117 | 118 | exists := db.Exists(key) 119 | if NX && exists || XX && !exists { 120 | c.Conn().WriteNull() 121 | return 122 | } 123 | 124 | db.Set(key, NewString(&value), yesExpire, expire) 125 | c.Conn().WriteString("OK") 126 | } 127 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go: -------------------------------------------------------------------------------- 1 | package hashtag 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | ) 7 | 8 | const slotNumber = 16384 9 | 10 | // CRC16 implementation according to CCITT standards. 11 | // Copyright 2001-2010 Georges Menie (www.menie.org) 12 | // Copyright 2013 The Go Authors. All rights reserved. 13 | // http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c 14 | var crc16tab = [256]uint16{ 15 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 16 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 17 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 18 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 19 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 20 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 21 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 22 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 23 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 24 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 25 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 26 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 27 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 28 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 29 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 30 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 31 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 32 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 33 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 34 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 35 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 36 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 37 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 38 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 39 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 40 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 41 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 42 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 43 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 44 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 45 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 46 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, 47 | } 48 | 49 | func Key(key string) string { 50 | if s := strings.IndexByte(key, '{'); s > -1 { 51 | if e := strings.IndexByte(key[s+1:], '}'); e > 0 { 52 | return key[s+1 : s+e+1] 53 | } 54 | } 55 | return key 56 | } 57 | 58 | func RandomSlot() int { 59 | return rand.Intn(slotNumber) 60 | } 61 | 62 | // hashSlot returns a consistent slot number between 0 and 16383 63 | // for any given string key. 64 | func Slot(key string) int { 65 | if key == "" { 66 | return RandomSlot() 67 | } 68 | key = Key(key) 69 | return int(crc16sum(key)) % slotNumber 70 | } 71 | 72 | func crc16sum(key string) (crc uint16) { 73 | for i := 0; i < len(key); i++ { 74 | crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff] 75 | } 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/tx.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/go-redis/redis/internal/pool" 5 | "github.com/go-redis/redis/internal/proto" 6 | ) 7 | 8 | // TxFailedErr transaction redis failed. 9 | const TxFailedErr = proto.RedisError("redis: transaction failed") 10 | 11 | // Tx implements Redis transactions as described in 12 | // http://redis.io/topics/transactions. It's NOT safe for concurrent use 13 | // by multiple goroutines, because Exec resets list of watched keys. 14 | // If you don't need WATCH it is better to use Pipeline. 15 | type Tx struct { 16 | statefulCmdable 17 | baseClient 18 | } 19 | 20 | func (c *Client) newTx() *Tx { 21 | tx := Tx{ 22 | baseClient: baseClient{ 23 | opt: c.opt, 24 | connPool: pool.NewStickyConnPool(c.connPool.(*pool.ConnPool), true), 25 | }, 26 | } 27 | tx.baseClient.init() 28 | tx.statefulCmdable.setProcessor(tx.Process) 29 | return &tx 30 | } 31 | 32 | // Watch prepares a transcaction and marks the keys to be watched 33 | // for conditional execution if there are any keys. 34 | // 35 | // The transaction is automatically closed when the fn exits. 36 | func (c *Client) Watch(fn func(*Tx) error, keys ...string) error { 37 | tx := c.newTx() 38 | if len(keys) > 0 { 39 | if err := tx.Watch(keys...).Err(); err != nil { 40 | _ = tx.Close() 41 | return err 42 | } 43 | } 44 | 45 | err := fn(tx) 46 | _ = tx.Close() 47 | return err 48 | } 49 | 50 | // Close closes the transaction, releasing any open resources. 51 | func (c *Tx) Close() error { 52 | _ = c.Unwatch().Err() 53 | return c.baseClient.Close() 54 | } 55 | 56 | // Watch marks the keys to be watched for conditional execution 57 | // of a transaction. 58 | func (c *Tx) Watch(keys ...string) *StatusCmd { 59 | args := make([]interface{}, 1+len(keys)) 60 | args[0] = "watch" 61 | for i, key := range keys { 62 | args[1+i] = key 63 | } 64 | cmd := NewStatusCmd(args...) 65 | c.Process(cmd) 66 | return cmd 67 | } 68 | 69 | // Unwatch flushes all the previously watched keys for a transaction. 70 | func (c *Tx) Unwatch(keys ...string) *StatusCmd { 71 | args := make([]interface{}, 1+len(keys)) 72 | args[0] = "unwatch" 73 | for i, key := range keys { 74 | args[1+i] = key 75 | } 76 | cmd := NewStatusCmd(args...) 77 | c.Process(cmd) 78 | return cmd 79 | } 80 | 81 | // Pipeline creates a new pipeline. It is more convenient to use Pipelined. 82 | func (c *Tx) Pipeline() Pipeliner { 83 | pipe := Pipeline{ 84 | exec: c.processTxPipeline, 85 | } 86 | pipe.statefulCmdable.setProcessor(pipe.Process) 87 | return &pipe 88 | } 89 | 90 | // Pipelined executes commands queued in the fn in a transaction. 91 | // 92 | // When using WATCH, EXEC will execute commands only if the watched keys 93 | // were not modified, allowing for a check-and-set mechanism. 94 | // 95 | // Exec always returns list of commands. If transaction fails 96 | // TxFailedErr is returned. Otherwise Exec returns an error of the first 97 | // failed command or nil. 98 | func (c *Tx) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) { 99 | return c.Pipeline().Pipelined(fn) 100 | } 101 | 102 | // TxPipelined is an alias for Pipelined. 103 | func (c *Tx) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) { 104 | return c.Pipelined(fn) 105 | } 106 | 107 | // TxPipeline is an alias for Pipeline. 108 | func (c *Tx) TxPipeline() Pipeliner { 109 | return c.Pipeline() 110 | } 111 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/proto/writer.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bufio" 5 | "encoding" 6 | "fmt" 7 | "io" 8 | "strconv" 9 | 10 | "github.com/go-redis/redis/internal/util" 11 | ) 12 | 13 | type Writer struct { 14 | wr *bufio.Writer 15 | 16 | lenBuf []byte 17 | numBuf []byte 18 | } 19 | 20 | func NewWriter(wr io.Writer) *Writer { 21 | return &Writer{ 22 | wr: bufio.NewWriter(wr), 23 | 24 | lenBuf: make([]byte, 64), 25 | numBuf: make([]byte, 64), 26 | } 27 | } 28 | 29 | func (w *Writer) WriteArgs(args []interface{}) error { 30 | err := w.wr.WriteByte(ArrayReply) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | err = w.writeLen(len(args)) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | for _, arg := range args { 41 | err := w.writeArg(arg) 42 | if err != nil { 43 | return err 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (w *Writer) writeLen(n int) error { 51 | w.lenBuf = strconv.AppendUint(w.lenBuf[:0], uint64(n), 10) 52 | w.lenBuf = append(w.lenBuf, '\r', '\n') 53 | _, err := w.wr.Write(w.lenBuf) 54 | return err 55 | } 56 | 57 | func (w *Writer) writeArg(v interface{}) error { 58 | switch v := v.(type) { 59 | case nil: 60 | return w.string("") 61 | case string: 62 | return w.string(v) 63 | case []byte: 64 | return w.bytes(v) 65 | case int: 66 | return w.int(int64(v)) 67 | case int8: 68 | return w.int(int64(v)) 69 | case int16: 70 | return w.int(int64(v)) 71 | case int32: 72 | return w.int(int64(v)) 73 | case int64: 74 | return w.int(v) 75 | case uint: 76 | return w.uint(uint64(v)) 77 | case uint8: 78 | return w.uint(uint64(v)) 79 | case uint16: 80 | return w.uint(uint64(v)) 81 | case uint32: 82 | return w.uint(uint64(v)) 83 | case uint64: 84 | return w.uint(v) 85 | case float32: 86 | return w.float(float64(v)) 87 | case float64: 88 | return w.float(v) 89 | case bool: 90 | if v { 91 | return w.int(1) 92 | } else { 93 | return w.int(0) 94 | } 95 | case encoding.BinaryMarshaler: 96 | b, err := v.MarshalBinary() 97 | if err != nil { 98 | return err 99 | } 100 | return w.bytes(b) 101 | default: 102 | return fmt.Errorf( 103 | "redis: can't marshal %T (implement encoding.BinaryMarshaler)", v) 104 | } 105 | } 106 | 107 | func (w *Writer) bytes(b []byte) error { 108 | err := w.wr.WriteByte(StringReply) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | err = w.writeLen(len(b)) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | _, err = w.wr.Write(b) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | return w.crlf() 124 | } 125 | 126 | func (w *Writer) string(s string) error { 127 | return w.bytes(util.StringToBytes(s)) 128 | } 129 | 130 | func (w *Writer) uint(n uint64) error { 131 | w.numBuf = strconv.AppendUint(w.numBuf[:0], n, 10) 132 | return w.bytes(w.numBuf) 133 | } 134 | 135 | func (w *Writer) int(n int64) error { 136 | w.numBuf = strconv.AppendInt(w.numBuf[:0], n, 10) 137 | return w.bytes(w.numBuf) 138 | } 139 | 140 | func (w *Writer) float(f float64) error { 141 | w.numBuf = strconv.AppendFloat(w.numBuf[:0], f, 'f', -1, 64) 142 | return w.bytes(w.numBuf) 143 | } 144 | 145 | func (w *Writer) crlf() error { 146 | err := w.wr.WriteByte('\r') 147 | if err != nil { 148 | return err 149 | } 150 | return w.wr.WriteByte('\n') 151 | } 152 | 153 | func (w *Writer) Reset(wr io.Writer) { 154 | w.wr.Reset(wr) 155 | } 156 | 157 | func (w *Writer) Flush() error { 158 | return w.wr.Flush() 159 | } 160 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/proto/scan.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "reflect" 7 | 8 | "github.com/go-redis/redis/internal/util" 9 | ) 10 | 11 | func Scan(b []byte, v interface{}) error { 12 | switch v := v.(type) { 13 | case nil: 14 | return fmt.Errorf("redis: Scan(nil)") 15 | case *string: 16 | *v = util.BytesToString(b) 17 | return nil 18 | case *[]byte: 19 | *v = b 20 | return nil 21 | case *int: 22 | var err error 23 | *v, err = util.Atoi(b) 24 | return err 25 | case *int8: 26 | n, err := util.ParseInt(b, 10, 8) 27 | if err != nil { 28 | return err 29 | } 30 | *v = int8(n) 31 | return nil 32 | case *int16: 33 | n, err := util.ParseInt(b, 10, 16) 34 | if err != nil { 35 | return err 36 | } 37 | *v = int16(n) 38 | return nil 39 | case *int32: 40 | n, err := util.ParseInt(b, 10, 32) 41 | if err != nil { 42 | return err 43 | } 44 | *v = int32(n) 45 | return nil 46 | case *int64: 47 | n, err := util.ParseInt(b, 10, 64) 48 | if err != nil { 49 | return err 50 | } 51 | *v = n 52 | return nil 53 | case *uint: 54 | n, err := util.ParseUint(b, 10, 64) 55 | if err != nil { 56 | return err 57 | } 58 | *v = uint(n) 59 | return nil 60 | case *uint8: 61 | n, err := util.ParseUint(b, 10, 8) 62 | if err != nil { 63 | return err 64 | } 65 | *v = uint8(n) 66 | return nil 67 | case *uint16: 68 | n, err := util.ParseUint(b, 10, 16) 69 | if err != nil { 70 | return err 71 | } 72 | *v = uint16(n) 73 | return nil 74 | case *uint32: 75 | n, err := util.ParseUint(b, 10, 32) 76 | if err != nil { 77 | return err 78 | } 79 | *v = uint32(n) 80 | return nil 81 | case *uint64: 82 | n, err := util.ParseUint(b, 10, 64) 83 | if err != nil { 84 | return err 85 | } 86 | *v = n 87 | return nil 88 | case *float32: 89 | n, err := util.ParseFloat(b, 32) 90 | if err != nil { 91 | return err 92 | } 93 | *v = float32(n) 94 | return err 95 | case *float64: 96 | var err error 97 | *v, err = util.ParseFloat(b, 64) 98 | return err 99 | case *bool: 100 | *v = len(b) == 1 && b[0] == '1' 101 | return nil 102 | case encoding.BinaryUnmarshaler: 103 | return v.UnmarshalBinary(b) 104 | default: 105 | return fmt.Errorf( 106 | "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) 107 | } 108 | } 109 | 110 | func ScanSlice(data []string, slice interface{}) error { 111 | v := reflect.ValueOf(slice) 112 | if !v.IsValid() { 113 | return fmt.Errorf("redis: ScanSlice(nil)") 114 | } 115 | if v.Kind() != reflect.Ptr { 116 | return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) 117 | } 118 | v = v.Elem() 119 | if v.Kind() != reflect.Slice { 120 | return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) 121 | } 122 | 123 | next := makeSliceNextElemFunc(v) 124 | for i, s := range data { 125 | elem := next() 126 | if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { 127 | err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %s", i, s, err) 128 | return err 129 | } 130 | } 131 | 132 | return nil 133 | } 134 | 135 | func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { 136 | elemType := v.Type().Elem() 137 | 138 | if elemType.Kind() == reflect.Ptr { 139 | elemType = elemType.Elem() 140 | return func() reflect.Value { 141 | if v.Len() < v.Cap() { 142 | v.Set(v.Slice(0, v.Len()+1)) 143 | elem := v.Index(v.Len() - 1) 144 | if elem.IsNil() { 145 | elem.Set(reflect.New(elemType)) 146 | } 147 | return elem.Elem() 148 | } 149 | 150 | elem := reflect.New(elemType) 151 | v.Set(reflect.Append(v, elem)) 152 | return elem.Elem() 153 | } 154 | } 155 | 156 | zero := reflect.Zero(elemType) 157 | return func() reflect.Value { 158 | if v.Len() < v.Cap() { 159 | v.Set(v.Slice(0, v.Len()+1)) 160 | return v.Index(v.Len() - 1) 161 | } 162 | 163 | v.Set(reflect.Append(v, zero)) 164 | return v.Index(v.Len() - 1) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/result.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import "time" 4 | 5 | // NewCmdResult returns a Cmd initialised with val and err for testing 6 | func NewCmdResult(val interface{}, err error) *Cmd { 7 | var cmd Cmd 8 | cmd.val = val 9 | cmd.setErr(err) 10 | return &cmd 11 | } 12 | 13 | // NewSliceResult returns a SliceCmd initialised with val and err for testing 14 | func NewSliceResult(val []interface{}, err error) *SliceCmd { 15 | var cmd SliceCmd 16 | cmd.val = val 17 | cmd.setErr(err) 18 | return &cmd 19 | } 20 | 21 | // NewStatusResult returns a StatusCmd initialised with val and err for testing 22 | func NewStatusResult(val string, err error) *StatusCmd { 23 | var cmd StatusCmd 24 | cmd.val = val 25 | cmd.setErr(err) 26 | return &cmd 27 | } 28 | 29 | // NewIntResult returns an IntCmd initialised with val and err for testing 30 | func NewIntResult(val int64, err error) *IntCmd { 31 | var cmd IntCmd 32 | cmd.val = val 33 | cmd.setErr(err) 34 | return &cmd 35 | } 36 | 37 | // NewDurationResult returns a DurationCmd initialised with val and err for testing 38 | func NewDurationResult(val time.Duration, err error) *DurationCmd { 39 | var cmd DurationCmd 40 | cmd.val = val 41 | cmd.setErr(err) 42 | return &cmd 43 | } 44 | 45 | // NewBoolResult returns a BoolCmd initialised with val and err for testing 46 | func NewBoolResult(val bool, err error) *BoolCmd { 47 | var cmd BoolCmd 48 | cmd.val = val 49 | cmd.setErr(err) 50 | return &cmd 51 | } 52 | 53 | // NewStringResult returns a StringCmd initialised with val and err for testing 54 | func NewStringResult(val string, err error) *StringCmd { 55 | var cmd StringCmd 56 | cmd.val = val 57 | cmd.setErr(err) 58 | return &cmd 59 | } 60 | 61 | // NewFloatResult returns a FloatCmd initialised with val and err for testing 62 | func NewFloatResult(val float64, err error) *FloatCmd { 63 | var cmd FloatCmd 64 | cmd.val = val 65 | cmd.setErr(err) 66 | return &cmd 67 | } 68 | 69 | // NewStringSliceResult returns a StringSliceCmd initialised with val and err for testing 70 | func NewStringSliceResult(val []string, err error) *StringSliceCmd { 71 | var cmd StringSliceCmd 72 | cmd.val = val 73 | cmd.setErr(err) 74 | return &cmd 75 | } 76 | 77 | // NewBoolSliceResult returns a BoolSliceCmd initialised with val and err for testing 78 | func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd { 79 | var cmd BoolSliceCmd 80 | cmd.val = val 81 | cmd.setErr(err) 82 | return &cmd 83 | } 84 | 85 | // NewStringStringMapResult returns a StringStringMapCmd initialised with val and err for testing 86 | func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd { 87 | var cmd StringStringMapCmd 88 | cmd.val = val 89 | cmd.setErr(err) 90 | return &cmd 91 | } 92 | 93 | // NewStringIntMapCmdResult returns a StringIntMapCmd initialised with val and err for testing 94 | func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd { 95 | var cmd StringIntMapCmd 96 | cmd.val = val 97 | cmd.setErr(err) 98 | return &cmd 99 | } 100 | 101 | // NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing 102 | func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd { 103 | var cmd ZSliceCmd 104 | cmd.val = val 105 | cmd.setErr(err) 106 | return &cmd 107 | } 108 | 109 | // NewScanCmdResult returns a ScanCmd initialised with val and err for testing 110 | func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd { 111 | var cmd ScanCmd 112 | cmd.page = keys 113 | cmd.cursor = cursor 114 | cmd.setErr(err) 115 | return &cmd 116 | } 117 | 118 | // NewClusterSlotsCmdResult returns a ClusterSlotsCmd initialised with val and err for testing 119 | func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd { 120 | var cmd ClusterSlotsCmd 121 | cmd.val = val 122 | cmd.setErr(err) 123 | return &cmd 124 | } 125 | 126 | // NewGeoLocationCmdResult returns a GeoLocationCmd initialised with val and err for testing 127 | func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd { 128 | var cmd GeoLocationCmd 129 | cmd.locations = val 130 | cmd.setErr(err) 131 | return &cmd 132 | } 133 | 134 | // NewCommandsInfoCmdResult returns a CommandsInfoCmd initialised with val and err for testing 135 | func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd { 136 | var cmd CommandsInfoCmd 137 | cmd.val = val 138 | cmd.setErr(err) 139 | return &cmd 140 | } 141 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import "github.com/redis-go/redcon" 4 | 5 | // Command flags. Please check the command table defined in the redis.c file 6 | // for more information about the meaning of every flag. 7 | const ( 8 | CMD_WRITE CmdFlag = iota + 1 /* "w" flag */ 9 | CMD_READONLY /* "r" flag */ 10 | CMD_DENYOOM /* "m" flag */ 11 | CMD_MODULE /* Command exported by module. */ 12 | CMD_ADMIN /* "a" flag */ 13 | CMD_PUBSUB /* "p" flag */ 14 | CMD_NOSCRIPT /* "s" flag */ 15 | CMD_RANDOM /* "R" flag */ 16 | CMD_SORT_FOR_SCRIPT /* "S" flag */ 17 | CMD_LOADING /* "l" flag */ 18 | CMD_STALE /* "t" flag */ 19 | CMD_SKIP_MONITOR /* "M" flag */ 20 | CMD_ASKING /* "k" flag */ 21 | CMD_FAST /* "F" flag */ 22 | CMD_MODULE_GETKEYS /* Use the modules getkeys interface. */ 23 | CMD_MODULE_NO_CLUSTER /* Deny on Redis Cluster. */ 24 | ) 25 | 26 | // A command can be registered. 27 | type Command struct { 28 | // The command name. 29 | name string 30 | 31 | // Handler 32 | handler CommandHandler 33 | 34 | // Command flags 35 | flags map[CmdFlag]struct{} // Use map as a set data structure 36 | } 37 | 38 | func NewCommand(name string, handler CommandHandler, flags ...CmdFlag) *Command { 39 | mFlags := make(map[CmdFlag]struct{}, len(flags)) 40 | for _, f := range flags { 41 | mFlags[f] = struct{}{} 42 | } 43 | 44 | return &Command{ 45 | name: name, 46 | handler: handler, 47 | flags: mFlags, 48 | } 49 | } 50 | 51 | // Command flag type. 52 | type CmdFlag uint 53 | 54 | // Commands map 55 | type Commands map[string]*Command 56 | 57 | // The CommandHandler is triggered when the received 58 | // command equals a registered command. 59 | // 60 | // However the CommandHandler is executed by the Handler, 61 | // so if you implement an own Handler make sure the CommandHandler is called. 62 | type CommandHandler func(c *Client, cmd redcon.Command) 63 | 64 | // Is called when a request is received, 65 | // after Accept and if the command is not registered. 66 | // 67 | // However UnknownCommand is executed by the Handler, 68 | // so if you implement an own Handler make sure to include UnknownCommand. 69 | type UnknownCommand func(c *Client, cmd redcon.Command) 70 | 71 | // Gets registered commands name. 72 | func (cmd *Command) Name() string { 73 | return cmd.name 74 | } 75 | 76 | // RegisterCommands adds commands to the redis instance. 77 | // If a cmd already exists the handler is overridden. 78 | func (r *Redis) RegisterCommands(cmds []*Command) { 79 | r.Mu().Lock() 80 | defer r.Mu().Unlock() 81 | for _, cmd := range cmds { 82 | r.registerCommand(cmd) 83 | } 84 | } 85 | 86 | // RegisterCommand adds a command to the redis instance. 87 | // If cmd already exists the handler is overridden. 88 | func (r *Redis) RegisterCommand(cmd *Command) { 89 | r.Mu().Lock() 90 | defer r.Mu().Unlock() 91 | r.registerCommand(cmd) 92 | } 93 | func (r *Redis) registerCommand(cmd *Command) { 94 | r.getCommands()[cmd.Name()] = cmd 95 | } 96 | 97 | // UnregisterCommand removes a command. 98 | func (r *Redis) UnregisterCommand(name string) { 99 | r.Mu().Lock() 100 | defer r.Mu().Unlock() 101 | delete(r.commands, name) 102 | } 103 | 104 | // Command returns the registered command or nil if not exists. 105 | func (r *Redis) Command(name string) *Command { 106 | r.Mu().RLock() 107 | defer r.Mu().RUnlock() 108 | return r.command(name) 109 | } 110 | 111 | func (r *Redis) command(name string) *Command { 112 | return r.commands[name] 113 | } 114 | 115 | // Commands returns the commands map. 116 | func (r *Redis) Commands() Commands { 117 | r.Mu().RLock() 118 | defer r.Mu().RUnlock() 119 | return r.getCommands() 120 | } 121 | 122 | func (r *Redis) getCommands() Commands { 123 | return r.commands 124 | } 125 | 126 | // CommandExists checks if one or more commands are registered. 127 | func (r *Redis) CommandExists(cmds ...string) bool { 128 | regCmds := r.Commands() 129 | 130 | // TODO does this make the performance better because it does not create a loop every time? 131 | if len(cmds) == 1 { 132 | _, ex := regCmds[cmds[0]] 133 | return ex 134 | } 135 | 136 | for _, cmd := range cmds { 137 | if _, ex := regCmds[cmd]; !ex { 138 | return false 139 | } 140 | } 141 | return true 142 | } 143 | 144 | // FlushCommands removes all commands. 145 | func (r *Redis) FlushCommands() { 146 | r.Mu().Lock() 147 | defer r.Mu().Unlock() 148 | r.commands = make(Commands) 149 | } 150 | 151 | // CommandHandlerFn returns the CommandHandler of cmd. 152 | func (r *Redis) CommandHandlerFn(name string) CommandHandler { 153 | r.Mu().RLock() 154 | defer r.Mu().RUnlock() 155 | return r.command(name).handler 156 | } 157 | 158 | // UnknownCommandFn returns the UnknownCommand function. 159 | func (r *Redis) UnknownCommandFn() UnknownCommand { 160 | r.Mu().RLock() 161 | defer r.Mu().RUnlock() 162 | return r.unknownCommand 163 | } 164 | -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "github.com/redis-go/redcon" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | const ( 10 | SyntaxErr = "ERR syntax error" 11 | InvalidIntErr = "ERR value is not an integer or out of range" 12 | WrongTypeErr = "WRONGTYPE Operation against a key holding the wrong kind of value" 13 | WrongNumOfArgsErr = "ERR wrong number of arguments for '%s' command" 14 | ) 15 | 16 | // This is the redis server. 17 | type Redis struct { 18 | // databases/keyspaces 19 | redisDbs RedisDbs 20 | 21 | // Locking is important, share this mutex around to provide state. 22 | mu *sync.RWMutex 23 | 24 | commands Commands 25 | unknownCommand UnknownCommand 26 | 27 | handler Handler 28 | 29 | accept Accept 30 | onClose OnClose 31 | 32 | // TODO version 33 | // TODO log writer 34 | // TODO modules 35 | // TODO redis options type 36 | 37 | keyExpirer KeyExpirer 38 | 39 | clients Clients 40 | nextClientId ClientId 41 | } 42 | 43 | // A Handler is called when a request is received and after Accept 44 | // (if Accept allowed the connection by returning true). 45 | // 46 | // For implementing an own handler see the default handler 47 | // as a perfect example in the createDefault() function. 48 | type Handler func(c *Client, cmd redcon.Command) 49 | 50 | // Accept is called when a Client tries to connect and before everything else, 51 | // the Client connection will be closed instantaneously if the function returns false. 52 | type Accept func(c *Client) bool 53 | 54 | // OnClose is called when a Client connection is closed. 55 | type OnClose func(c *Client, err error) 56 | 57 | // Client map 58 | type Clients map[ClientId]*Client 59 | 60 | // Client id 61 | type ClientId uint64 62 | 63 | // Gets the handler func. 64 | func (r *Redis) HandlerFn() Handler { 65 | r.Mu().RLock() 66 | defer r.Mu().RUnlock() 67 | return r.handler 68 | } 69 | 70 | // Sets the handler func. 71 | // Live updates (while redis is running) works. 72 | func (r *Redis) SetHandlerFn(new Handler) { 73 | r.Mu().Lock() 74 | defer r.Mu().Unlock() 75 | r.handler = new 76 | } 77 | 78 | // Gets the accept func. 79 | func (r *Redis) AcceptFn() Accept { 80 | r.Mu().RLock() 81 | defer r.Mu().RUnlock() 82 | return r.accept 83 | } 84 | 85 | // Sets the accept func. 86 | // Live updates (while redis is running) works. 87 | func (r *Redis) SetAcceptFn(new Accept) { 88 | r.Mu().Lock() 89 | defer r.Mu().Unlock() 90 | r.accept = new 91 | } 92 | 93 | // Gets the onclose func. 94 | func (r *Redis) OnCloseFn() OnClose { 95 | r.Mu().RLock() 96 | defer r.Mu().RUnlock() 97 | return r.onClose 98 | } 99 | 100 | // Sets the onclose func. 101 | // Live updates (while redis is running) works. 102 | func (r *Redis) SetOnCloseFn(new OnClose) { 103 | r.Mu().Lock() 104 | defer r.Mu().Unlock() 105 | r.onClose = new 106 | } 107 | 108 | // The mutex of the redis. 109 | func (r *Redis) Mu() *sync.RWMutex { 110 | return r.mu 111 | } 112 | 113 | func (r *Redis) KeyExpirer() KeyExpirer { 114 | r.Mu().RLock() 115 | defer r.Mu().RUnlock() 116 | return r.keyExpirer 117 | } 118 | func (r *Redis) SetKeyExpirer(ke KeyExpirer) { 119 | r.Mu().Lock() 120 | defer r.Mu().Unlock() 121 | r.keyExpirer = ke 122 | } 123 | 124 | var defaultRedis *Redis 125 | 126 | // Default redis server. 127 | // Initializes the default redis if not already. 128 | // You can change the fields or value behind the pointer 129 | // of the returned redis pointer to extend/change the default. 130 | func Default() *Redis { 131 | if defaultRedis != nil { 132 | return defaultRedis 133 | } 134 | defaultRedis = createDefault() 135 | return defaultRedis 136 | } 137 | 138 | // createDefault creates a new default redis. 139 | func createDefault() *Redis { 140 | // initialize default redis server 141 | r := &Redis{ 142 | mu: new(sync.RWMutex), 143 | accept: func(c *Client) bool { 144 | return true 145 | }, 146 | onClose: func(c *Client, err error) { 147 | }, 148 | handler: func(c *Client, cmd redcon.Command) { 149 | cmdl := strings.ToLower(string(cmd.Args[0])) 150 | if c.Redis().CommandExists(cmdl) { 151 | c.Redis().CommandHandlerFn(cmdl)(c, cmd) 152 | } else { 153 | c.Redis().UnknownCommandFn()(c, cmd) 154 | } 155 | }, 156 | unknownCommand: func(c *Client, cmd redcon.Command) { 157 | c.Conn().WriteError("ERR unknown command '" + string(cmd.Args[0]) + "'") 158 | }, 159 | commands: make(Commands, 0), 160 | } 161 | r.redisDbs = make(RedisDbs, redisDbMapSizeDefault) 162 | r.RedisDb(0) // initializes default db 0 163 | r.keyExpirer = KeyExpirer(NewKeyExpirer(r)) 164 | 165 | r.RegisterCommands([]*Command{ 166 | NewCommand("ping", PingCommand, CMD_STALE, CMD_FAST), 167 | NewCommand("set", SetCommand, CMD_WRITE, CMD_DENYOOM), 168 | NewCommand("get", GetCommand, CMD_READONLY, CMD_FAST), 169 | NewCommand("del", DelCommand, CMD_WRITE), 170 | NewCommand("ttl", TtlCommand, CMD_READONLY, CMD_FAST), 171 | 172 | NewCommand("lpush", LPushCommand, CMD_WRITE, CMD_FAST, CMD_DENYOOM), 173 | NewCommand("rpush", RPushCommand, CMD_WRITE, CMD_FAST, CMD_DENYOOM), 174 | NewCommand("lpop", LPopCommand, CMD_WRITE, CMD_FAST), 175 | NewCommand("rpop", RPopCommand, CMD_WRITE, CMD_FAST), 176 | NewCommand("lrange", LRangeCommand, CMD_READONLY), 177 | }) 178 | return r 179 | } 180 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/stack.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | // Frame represents a program counter inside a stack frame. 12 | type Frame uintptr 13 | 14 | // pc returns the program counter for this frame; 15 | // multiple frames may have the same PC value. 16 | func (f Frame) pc() uintptr { return uintptr(f) - 1 } 17 | 18 | // file returns the full path to the file that contains the 19 | // function for this Frame's pc. 20 | func (f Frame) file() string { 21 | fn := runtime.FuncForPC(f.pc()) 22 | if fn == nil { 23 | return "unknown" 24 | } 25 | file, _ := fn.FileLine(f.pc()) 26 | return file 27 | } 28 | 29 | // line returns the line number of source code of the 30 | // function for this Frame's pc. 31 | func (f Frame) line() int { 32 | fn := runtime.FuncForPC(f.pc()) 33 | if fn == nil { 34 | return 0 35 | } 36 | _, line := fn.FileLine(f.pc()) 37 | return line 38 | } 39 | 40 | // Format formats the frame according to the fmt.Formatter interface. 41 | // 42 | // %s source file 43 | // %d source line 44 | // %n function name 45 | // %v equivalent to %s:%d 46 | // 47 | // Format accepts flags that alter the printing of some verbs, as follows: 48 | // 49 | // %+s path of source file relative to the compile time GOPATH 50 | // %+v equivalent to %+s:%d 51 | func (f Frame) Format(s fmt.State, verb rune) { 52 | switch verb { 53 | case 's': 54 | switch { 55 | case s.Flag('+'): 56 | pc := f.pc() 57 | fn := runtime.FuncForPC(pc) 58 | if fn == nil { 59 | io.WriteString(s, "unknown") 60 | } else { 61 | file, _ := fn.FileLine(pc) 62 | fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) 63 | } 64 | default: 65 | io.WriteString(s, path.Base(f.file())) 66 | } 67 | case 'd': 68 | fmt.Fprintf(s, "%d", f.line()) 69 | case 'n': 70 | name := runtime.FuncForPC(f.pc()).Name() 71 | io.WriteString(s, funcname(name)) 72 | case 'v': 73 | f.Format(s, 's') 74 | io.WriteString(s, ":") 75 | f.Format(s, 'd') 76 | } 77 | } 78 | 79 | // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). 80 | type StackTrace []Frame 81 | 82 | func (st StackTrace) Format(s fmt.State, verb rune) { 83 | switch verb { 84 | case 'v': 85 | switch { 86 | case s.Flag('+'): 87 | for _, f := range st { 88 | fmt.Fprintf(s, "\n%+v", f) 89 | } 90 | case s.Flag('#'): 91 | fmt.Fprintf(s, "%#v", []Frame(st)) 92 | default: 93 | fmt.Fprintf(s, "%v", []Frame(st)) 94 | } 95 | case 's': 96 | fmt.Fprintf(s, "%s", []Frame(st)) 97 | } 98 | } 99 | 100 | // stack represents a stack of program counters. 101 | type stack []uintptr 102 | 103 | func (s *stack) Format(st fmt.State, verb rune) { 104 | switch verb { 105 | case 'v': 106 | switch { 107 | case st.Flag('+'): 108 | for _, pc := range *s { 109 | f := Frame(pc) 110 | fmt.Fprintf(st, "\n%+v", f) 111 | } 112 | } 113 | } 114 | } 115 | 116 | func (s *stack) StackTrace() StackTrace { 117 | f := make([]Frame, len(*s)) 118 | for i := 0; i < len(f); i++ { 119 | f[i] = Frame((*s)[i]) 120 | } 121 | return f 122 | } 123 | 124 | func callers() *stack { 125 | const depth = 32 126 | var pcs [depth]uintptr 127 | n := runtime.Callers(3, pcs[:]) 128 | var st stack = pcs[0:n] 129 | return &st 130 | } 131 | 132 | // funcname removes the path prefix component of a function's name reported by func.Name(). 133 | func funcname(name string) string { 134 | i := strings.LastIndex(name, "/") 135 | name = name[i+1:] 136 | i = strings.Index(name, ".") 137 | return name[i+1:] 138 | } 139 | 140 | func trimGOPATH(name, file string) string { 141 | // Here we want to get the source file path relative to the compile time 142 | // GOPATH. As of Go 1.6.x there is no direct way to know the compiled 143 | // GOPATH at runtime, but we can infer the number of path segments in the 144 | // GOPATH. We note that fn.Name() returns the function name qualified by 145 | // the import path, which does not include the GOPATH. Thus we can trim 146 | // segments from the beginning of the file path until the number of path 147 | // separators remaining is one more than the number of path separators in 148 | // the function name. For example, given: 149 | // 150 | // GOPATH /home/user 151 | // file /home/user/src/pkg/sub/file.go 152 | // fn.Name() pkg/sub.Type.Method 153 | // 154 | // We want to produce: 155 | // 156 | // pkg/sub/file.go 157 | // 158 | // From this we can easily see that fn.Name() has one less path separator 159 | // than our desired output. We count separators from the end of the file 160 | // path until it finds two more than in the function name and then move 161 | // one character forward to preserve the initial path segment without a 162 | // leading separator. 163 | const sep = "/" 164 | goal := strings.Count(name, sep) + 2 165 | i := len(file) 166 | for n := 0; n < goal; n++ { 167 | i = strings.LastIndex(file[:i], sep) 168 | if i == -1 { 169 | // not enough separators found, set i so that the slice expression 170 | // below leaves file unmodified 171 | i = -len(sep) 172 | break 173 | } 174 | } 175 | // get back to 0 or trim the leading separator 176 | file = file[i+len(sep):] 177 | return file 178 | } 179 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/bypass.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015-2016 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, compiled by GopherJS, and 17 | // "-tags safe" is not added to the go build command line. The "disableunsafe" 18 | // tag is deprecated and thus should not be used. 19 | // Go versions prior to 1.4 are disabled because they use a different layout 20 | // for interfaces which make the implementation of unsafeReflectValue more complex. 21 | // +build !js,!appengine,!safe,!disableunsafe,go1.4 22 | 23 | package spew 24 | 25 | import ( 26 | "reflect" 27 | "unsafe" 28 | ) 29 | 30 | const ( 31 | // UnsafeDisabled is a build-time constant which specifies whether or 32 | // not access to the unsafe package is available. 33 | UnsafeDisabled = false 34 | 35 | // ptrSize is the size of a pointer on the current arch. 36 | ptrSize = unsafe.Sizeof((*byte)(nil)) 37 | ) 38 | 39 | type flag uintptr 40 | 41 | var ( 42 | // flagRO indicates whether the value field of a reflect.Value 43 | // is read-only. 44 | flagRO flag 45 | 46 | // flagAddr indicates whether the address of the reflect.Value's 47 | // value may be taken. 48 | flagAddr flag 49 | ) 50 | 51 | // flagKindMask holds the bits that make up the kind 52 | // part of the flags field. In all the supported versions, 53 | // it is in the lower 5 bits. 54 | const flagKindMask = flag(0x1f) 55 | 56 | // Different versions of Go have used different 57 | // bit layouts for the flags type. This table 58 | // records the known combinations. 59 | var okFlags = []struct { 60 | ro, addr flag 61 | }{{ 62 | // From Go 1.4 to 1.5 63 | ro: 1 << 5, 64 | addr: 1 << 7, 65 | }, { 66 | // Up to Go tip. 67 | ro: 1<<5 | 1<<6, 68 | addr: 1 << 8, 69 | }} 70 | 71 | var flagValOffset = func() uintptr { 72 | field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") 73 | if !ok { 74 | panic("reflect.Value has no flag field") 75 | } 76 | return field.Offset 77 | }() 78 | 79 | // flagField returns a pointer to the flag field of a reflect.Value. 80 | func flagField(v *reflect.Value) *flag { 81 | return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset)) 82 | } 83 | 84 | // unsafeReflectValue converts the passed reflect.Value into a one that bypasses 85 | // the typical safety restrictions preventing access to unaddressable and 86 | // unexported data. It works by digging the raw pointer to the underlying 87 | // value out of the protected value and generating a new unprotected (unsafe) 88 | // reflect.Value to it. 89 | // 90 | // This allows us to check for implementations of the Stringer and error 91 | // interfaces to be used for pretty printing ordinarily unaddressable and 92 | // inaccessible values such as unexported struct fields. 93 | func unsafeReflectValue(v reflect.Value) reflect.Value { 94 | if !v.IsValid() || (v.CanInterface() && v.CanAddr()) { 95 | return v 96 | } 97 | flagFieldPtr := flagField(&v) 98 | *flagFieldPtr &^= flagRO 99 | *flagFieldPtr |= flagAddr 100 | return v 101 | } 102 | 103 | // Sanity checks against future reflect package changes 104 | // to the type or semantics of the Value.flag field. 105 | func init() { 106 | field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") 107 | if !ok { 108 | panic("reflect.Value has no flag field") 109 | } 110 | if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() { 111 | panic("reflect.Value flag field has changed kind") 112 | } 113 | type t0 int 114 | var t struct { 115 | A t0 116 | // t0 will have flagEmbedRO set. 117 | t0 118 | // a will have flagStickyRO set 119 | a t0 120 | } 121 | vA := reflect.ValueOf(t).FieldByName("A") 122 | va := reflect.ValueOf(t).FieldByName("a") 123 | vt0 := reflect.ValueOf(t).FieldByName("t0") 124 | 125 | // Infer flagRO from the difference between the flags 126 | // for the (otherwise identical) fields in t. 127 | flagPublic := *flagField(&vA) 128 | flagWithRO := *flagField(&va) | *flagField(&vt0) 129 | flagRO = flagPublic ^ flagWithRO 130 | 131 | // Infer flagAddr from the difference between a value 132 | // taken from a pointer and not. 133 | vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A") 134 | flagNoPtr := *flagField(&vA) 135 | flagPtr := *flagField(&vPtrA) 136 | flagAddr = flagNoPtr ^ flagPtr 137 | 138 | // Check that the inferred flags tally with one of the known versions. 139 | for _, f := range okFlags { 140 | if flagRO == f.ro && flagAddr == f.addr { 141 | return 142 | } 143 | } 144 | panic("reflect.Value read-only flag has changed semantics") 145 | } 146 | -------------------------------------------------------------------------------- /vendor/github.com/redis-go/redcon/README.md: -------------------------------------------------------------------------------- 1 |

2 | REDCON 5 |
6 | Build Status 7 | GoDoc 8 |

9 | 10 |

Fast Redis compatible server framework for Go

11 | 12 | Redcon is a custom Redis server framework for Go that is fast and simple to use. The reason for this library it to give an efficient server front-end for the [BuntDB](https://github.com/tidwall/buntdb) and [Tile38](https://github.com/tidwall/tile38) projects. 13 | 14 | Features 15 | -------- 16 | - Create a [Fast](#benchmarks) custom Redis compatible server in Go 17 | - Simple interface. One function `ListenAndServe` and two types `Conn` & `Command` 18 | - Support for pipelining and telnet commands 19 | - Works with Redis clients such as [redigo](https://github.com/garyburd/redigo), [redis-py](https://github.com/andymccurdy/redis-py), [node_redis](https://github.com/NodeRedis/node_redis), and [jedis](https://github.com/xetorthio/jedis) 20 | - [TLS Support](#tls-example) 21 | 22 | Installing 23 | ---------- 24 | 25 | ``` 26 | go get -u github.com/tidwall/redcon 27 | ``` 28 | 29 | Example 30 | ------- 31 | 32 | Here's a full example of a Redis clone that accepts: 33 | 34 | - SET key value 35 | - GET key 36 | - DEL key 37 | - PING 38 | - QUIT 39 | 40 | You can run this example from a terminal: 41 | 42 | ```sh 43 | go run example/clone.go 44 | ``` 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "log" 51 | "strings" 52 | "sync" 53 | 54 | "github.com/tidwall/redcon" 55 | ) 56 | 57 | var addr = ":6380" 58 | 59 | func main() { 60 | var mu sync.RWMutex 61 | var items = make(map[string][]byte) 62 | go log.Printf("started server at %s", addr) 63 | err := redcon.ListenAndServe(addr, 64 | func(conn redcon.Conn, cmd redcon.Command) { 65 | switch strings.ToLower(string(cmd.Args[0])) { 66 | default: 67 | conn.WriteError("ERR unknown command '" + string(cmd.Args[0]) + "'") 68 | case "ping": 69 | conn.WriteString("PONG") 70 | case "quit": 71 | conn.WriteString("OK") 72 | conn.Close() 73 | case "set": 74 | if len(cmd.Args) != 3 { 75 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 76 | return 77 | } 78 | mu.Lock() 79 | items[string(cmd.Args[1])] = cmd.Args[2] 80 | mu.Unlock() 81 | conn.WriteString("OK") 82 | case "get": 83 | if len(cmd.Args) != 2 { 84 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 85 | return 86 | } 87 | mu.RLock() 88 | val, ok := items[string(cmd.Args[1])] 89 | mu.RUnlock() 90 | if !ok { 91 | conn.WriteNull() 92 | } else { 93 | conn.WriteBulk(val) 94 | } 95 | case "del": 96 | if len(cmd.Args) != 2 { 97 | conn.WriteError("ERR wrong number of arguments for '" + string(cmd.Args[0]) + "' command") 98 | return 99 | } 100 | mu.Lock() 101 | _, ok := items[string(cmd.Args[1])] 102 | delete(items, string(cmd.Args[1])) 103 | mu.Unlock() 104 | if !ok { 105 | conn.WriteInt(0) 106 | } else { 107 | conn.WriteInt(1) 108 | } 109 | } 110 | }, 111 | func(conn redcon.Conn) bool { 112 | // use this function to accept or deny the connection. 113 | // log.Printf("accept: %s", conn.RemoteAddr()) 114 | return true 115 | }, 116 | func(conn redcon.Conn, err error) { 117 | // this is called when the connection has been closed 118 | // log.Printf("closed: %s, err: %v", conn.RemoteAddr(), err) 119 | }, 120 | ) 121 | if err != nil { 122 | log.Fatal(err) 123 | } 124 | } 125 | ``` 126 | 127 | TLS Example 128 | ----------- 129 | 130 | Redcon has full TLS support through the `ListenAndServeTLS` function. 131 | 132 | The [same example](example/tls/clone.go) is also provided for serving Redcon over TLS. 133 | 134 | ```sh 135 | go run example/tls/clone.go 136 | ``` 137 | 138 | Benchmarks 139 | ---------- 140 | 141 | **Redis**: Single-threaded, no disk persistence. 142 | 143 | ``` 144 | $ redis-server --port 6379 --appendonly no 145 | ``` 146 | ``` 147 | redis-benchmark -p 6379 -t set,get -n 10000000 -q -P 512 -c 512 148 | SET: 941265.12 requests per second 149 | GET: 1189909.50 requests per second 150 | ``` 151 | 152 | **Redcon**: Single-threaded, no disk persistence. 153 | 154 | ``` 155 | $ GOMAXPROCS=1 go run example/clone.go 156 | ``` 157 | ``` 158 | redis-benchmark -p 6380 -t set,get -n 10000000 -q -P 512 -c 512 159 | SET: 2018570.88 requests per second 160 | GET: 2403846.25 requests per second 161 | ``` 162 | 163 | **Redcon**: Multi-threaded, no disk persistence. 164 | 165 | ``` 166 | $ GOMAXPROCS=0 go run example/clone.go 167 | ``` 168 | ``` 169 | $ redis-benchmark -p 6380 -t set,get -n 10000000 -q -P 512 -c 512 170 | SET: 1944390.38 requests per second 171 | GET: 3993610.25 requests per second 172 | ``` 173 | 174 | *Running on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7* 175 | 176 | Contact 177 | ------- 178 | Josh Baker [@tidwall](http://twitter.com/tidwall) 179 | 180 | License 181 | ------- 182 | Redcon source code is available under the MIT [License](/LICENSE). 183 | -------------------------------------------------------------------------------- /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 and 12 | // an error if building a new request fails. 13 | func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { 14 | w := httptest.NewRecorder() 15 | req, err := http.NewRequest(method, url, nil) 16 | if err != nil { 17 | return -1, err 18 | } 19 | req.URL.RawQuery = values.Encode() 20 | handler(w, req) 21 | return w.Code, nil 22 | } 23 | 24 | // HTTPSuccess asserts that a specified handler returns a success status code. 25 | // 26 | // assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) 27 | // 28 | // Returns whether the assertion was successful (true) or not (false). 29 | func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 30 | if h, ok := t.(tHelper); ok { 31 | h.Helper() 32 | } 33 | code, err := httpCode(handler, method, url, values) 34 | if err != nil { 35 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 36 | return false 37 | } 38 | 39 | isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent 40 | if !isSuccessCode { 41 | Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code)) 42 | } 43 | 44 | return isSuccessCode 45 | } 46 | 47 | // HTTPRedirect asserts that a specified handler returns a redirect status code. 48 | // 49 | // assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} 50 | // 51 | // Returns whether the assertion was successful (true) or not (false). 52 | func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 53 | if h, ok := t.(tHelper); ok { 54 | h.Helper() 55 | } 56 | code, err := httpCode(handler, method, url, values) 57 | if err != nil { 58 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 59 | return false 60 | } 61 | 62 | isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect 63 | if !isRedirectCode { 64 | Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code)) 65 | } 66 | 67 | return isRedirectCode 68 | } 69 | 70 | // HTTPError asserts that a specified handler returns an error status code. 71 | // 72 | // assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} 73 | // 74 | // Returns whether the assertion was successful (true) or not (false). 75 | func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool { 76 | if h, ok := t.(tHelper); ok { 77 | h.Helper() 78 | } 79 | code, err := httpCode(handler, method, url, values) 80 | if err != nil { 81 | Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err)) 82 | return false 83 | } 84 | 85 | isErrorCode := code >= http.StatusBadRequest 86 | if !isErrorCode { 87 | Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code)) 88 | } 89 | 90 | return isErrorCode 91 | } 92 | 93 | // HTTPBody is a helper that returns HTTP body of the response. It returns 94 | // empty string if building a new request fails. 95 | func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { 96 | w := httptest.NewRecorder() 97 | req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) 98 | if err != nil { 99 | return "" 100 | } 101 | handler(w, req) 102 | return w.Body.String() 103 | } 104 | 105 | // HTTPBodyContains asserts that a specified handler returns a 106 | // body that contains a string. 107 | // 108 | // assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") 109 | // 110 | // Returns whether the assertion was successful (true) or not (false). 111 | func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { 112 | if h, ok := t.(tHelper); ok { 113 | h.Helper() 114 | } 115 | body := HTTPBody(handler, method, url, values) 116 | 117 | contains := strings.Contains(body, fmt.Sprint(str)) 118 | if !contains { 119 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 120 | } 121 | 122 | return contains 123 | } 124 | 125 | // HTTPBodyNotContains asserts that a specified handler returns a 126 | // body that does not contain a string. 127 | // 128 | // assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") 129 | // 130 | // Returns whether the assertion was successful (true) or not (false). 131 | func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool { 132 | if h, ok := t.(tHelper); ok { 133 | h.Helper() 134 | } 135 | body := HTTPBody(handler, method, url, values) 136 | 137 | contains := strings.Contains(body, fmt.Sprint(str)) 138 | if contains { 139 | Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body)) 140 | } 141 | 142 | return !contains 143 | } 144 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/universal.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "crypto/tls" 5 | "time" 6 | ) 7 | 8 | // UniversalOptions information is required by UniversalClient to establish 9 | // connections. 10 | type UniversalOptions struct { 11 | // Either a single address or a seed list of host:port addresses 12 | // of cluster/sentinel nodes. 13 | Addrs []string 14 | 15 | // Database to be selected after connecting to the server. 16 | // Only single-node and failover clients. 17 | DB int 18 | 19 | // Common options. 20 | 21 | OnConnect func(*Conn) error 22 | Password string 23 | MaxRetries int 24 | MinRetryBackoff time.Duration 25 | MaxRetryBackoff time.Duration 26 | DialTimeout time.Duration 27 | ReadTimeout time.Duration 28 | WriteTimeout time.Duration 29 | PoolSize int 30 | MinIdleConns int 31 | MaxConnAge time.Duration 32 | PoolTimeout time.Duration 33 | IdleTimeout time.Duration 34 | IdleCheckFrequency time.Duration 35 | TLSConfig *tls.Config 36 | 37 | // Only cluster clients. 38 | 39 | MaxRedirects int 40 | ReadOnly bool 41 | RouteByLatency bool 42 | RouteRandomly bool 43 | 44 | // The sentinel master name. 45 | // Only failover clients. 46 | MasterName string 47 | } 48 | 49 | func (o *UniversalOptions) cluster() *ClusterOptions { 50 | if len(o.Addrs) == 0 { 51 | o.Addrs = []string{"127.0.0.1:6379"} 52 | } 53 | 54 | return &ClusterOptions{ 55 | Addrs: o.Addrs, 56 | OnConnect: o.OnConnect, 57 | 58 | Password: o.Password, 59 | 60 | MaxRedirects: o.MaxRedirects, 61 | ReadOnly: o.ReadOnly, 62 | RouteByLatency: o.RouteByLatency, 63 | RouteRandomly: o.RouteRandomly, 64 | 65 | MaxRetries: o.MaxRetries, 66 | MinRetryBackoff: o.MinRetryBackoff, 67 | MaxRetryBackoff: o.MaxRetryBackoff, 68 | 69 | DialTimeout: o.DialTimeout, 70 | ReadTimeout: o.ReadTimeout, 71 | WriteTimeout: o.WriteTimeout, 72 | PoolSize: o.PoolSize, 73 | MinIdleConns: o.MinIdleConns, 74 | MaxConnAge: o.MaxConnAge, 75 | PoolTimeout: o.PoolTimeout, 76 | IdleTimeout: o.IdleTimeout, 77 | IdleCheckFrequency: o.IdleCheckFrequency, 78 | 79 | TLSConfig: o.TLSConfig, 80 | } 81 | } 82 | 83 | func (o *UniversalOptions) failover() *FailoverOptions { 84 | if len(o.Addrs) == 0 { 85 | o.Addrs = []string{"127.0.0.1:26379"} 86 | } 87 | 88 | return &FailoverOptions{ 89 | SentinelAddrs: o.Addrs, 90 | MasterName: o.MasterName, 91 | OnConnect: o.OnConnect, 92 | 93 | DB: o.DB, 94 | Password: o.Password, 95 | 96 | MaxRetries: o.MaxRetries, 97 | MinRetryBackoff: o.MinRetryBackoff, 98 | MaxRetryBackoff: o.MaxRetryBackoff, 99 | 100 | DialTimeout: o.DialTimeout, 101 | ReadTimeout: o.ReadTimeout, 102 | WriteTimeout: o.WriteTimeout, 103 | 104 | PoolSize: o.PoolSize, 105 | MinIdleConns: o.MinIdleConns, 106 | MaxConnAge: o.MaxConnAge, 107 | PoolTimeout: o.PoolTimeout, 108 | IdleTimeout: o.IdleTimeout, 109 | IdleCheckFrequency: o.IdleCheckFrequency, 110 | 111 | TLSConfig: o.TLSConfig, 112 | } 113 | } 114 | 115 | func (o *UniversalOptions) simple() *Options { 116 | addr := "127.0.0.1:6379" 117 | if len(o.Addrs) > 0 { 118 | addr = o.Addrs[0] 119 | } 120 | 121 | return &Options{ 122 | Addr: addr, 123 | OnConnect: o.OnConnect, 124 | 125 | DB: o.DB, 126 | Password: o.Password, 127 | 128 | MaxRetries: o.MaxRetries, 129 | MinRetryBackoff: o.MinRetryBackoff, 130 | MaxRetryBackoff: o.MaxRetryBackoff, 131 | 132 | DialTimeout: o.DialTimeout, 133 | ReadTimeout: o.ReadTimeout, 134 | WriteTimeout: o.WriteTimeout, 135 | 136 | PoolSize: o.PoolSize, 137 | MinIdleConns: o.MinIdleConns, 138 | MaxConnAge: o.MaxConnAge, 139 | PoolTimeout: o.PoolTimeout, 140 | IdleTimeout: o.IdleTimeout, 141 | IdleCheckFrequency: o.IdleCheckFrequency, 142 | 143 | TLSConfig: o.TLSConfig, 144 | } 145 | } 146 | 147 | // -------------------------------------------------------------------- 148 | 149 | // UniversalClient is an abstract client which - based on the provided options - 150 | // can connect to either clusters, or sentinel-backed failover instances or simple 151 | // single-instance servers. This can be useful for testing cluster-specific 152 | // applications locally. 153 | type UniversalClient interface { 154 | Cmdable 155 | Watch(fn func(*Tx) error, keys ...string) error 156 | Process(cmd Cmder) error 157 | WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) 158 | Subscribe(channels ...string) *PubSub 159 | PSubscribe(channels ...string) *PubSub 160 | Close() error 161 | } 162 | 163 | var _ UniversalClient = (*Client)(nil) 164 | var _ UniversalClient = (*ClusterClient)(nil) 165 | 166 | // NewUniversalClient returns a new multi client. The type of client returned depends 167 | // on the following three conditions: 168 | // 169 | // 1. if a MasterName is passed a sentinel-backed FailoverClient will be returned 170 | // 2. if the number of Addrs is two or more, a ClusterClient will be returned 171 | // 3. otherwise, a single-node redis Client will be returned. 172 | func NewUniversalClient(opts *UniversalOptions) UniversalClient { 173 | if opts.MasterName != "" { 174 | return NewFailoverClient(opts.failover()) 175 | } else if len(opts.Addrs) > 1 { 176 | return NewClusterClient(opts.cluster()) 177 | } 178 | return NewClient(opts.simple()) 179 | } 180 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/README.md: -------------------------------------------------------------------------------- 1 | # Redis client for Golang 2 | 3 | [![Build Status](https://travis-ci.org/go-redis/redis.png?branch=master)](https://travis-ci.org/go-redis/redis) 4 | [![GoDoc](https://godoc.org/github.com/go-redis/redis?status.svg)](https://godoc.org/github.com/go-redis/redis) 5 | [![Airbrake](https://img.shields.io/badge/kudos-airbrake.io-orange.svg)](https://airbrake.io) 6 | 7 | Supports: 8 | 9 | - Redis 3 commands except QUIT, MONITOR, SLOWLOG and SYNC. 10 | - Automatic connection pooling with [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support. 11 | - [Pub/Sub](https://godoc.org/github.com/go-redis/redis#PubSub). 12 | - [Transactions](https://godoc.org/github.com/go-redis/redis#Multi). 13 | - [Pipeline](https://godoc.org/github.com/go-redis/redis#example-Client-Pipeline) and [TxPipeline](https://godoc.org/github.com/go-redis/redis#example-Client-TxPipeline). 14 | - [Scripting](https://godoc.org/github.com/go-redis/redis#Script). 15 | - [Timeouts](https://godoc.org/github.com/go-redis/redis#Options). 16 | - [Redis Sentinel](https://godoc.org/github.com/go-redis/redis#NewFailoverClient). 17 | - [Redis Cluster](https://godoc.org/github.com/go-redis/redis#NewClusterClient). 18 | - [Cluster of Redis Servers](https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup) without using cluster mode and Redis Sentinel. 19 | - [Ring](https://godoc.org/github.com/go-redis/redis#NewRing). 20 | - [Instrumentation](https://godoc.org/github.com/go-redis/redis#ex-package--Instrumentation). 21 | - [Cache friendly](https://github.com/go-redis/cache). 22 | - [Rate limiting](https://github.com/go-redis/redis_rate). 23 | - [Distributed Locks](https://github.com/bsm/redis-lock). 24 | 25 | API docs: https://godoc.org/github.com/go-redis/redis. 26 | Examples: https://godoc.org/github.com/go-redis/redis#pkg-examples. 27 | 28 | ## Installation 29 | 30 | Install: 31 | 32 | ```shell 33 | go get -u github.com/go-redis/redis 34 | ``` 35 | 36 | Import: 37 | 38 | ```go 39 | import "github.com/go-redis/redis" 40 | ``` 41 | 42 | ## Quickstart 43 | 44 | ```go 45 | func ExampleNewClient() { 46 | client := redis.NewClient(&redis.Options{ 47 | Addr: "localhost:6379", 48 | Password: "", // no password set 49 | DB: 0, // use default DB 50 | }) 51 | 52 | pong, err := client.Ping().Result() 53 | fmt.Println(pong, err) 54 | // Output: PONG 55 | } 56 | 57 | func ExampleClient() { 58 | err := client.Set("key", "value", 0).Err() 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | val, err := client.Get("key").Result() 64 | if err != nil { 65 | panic(err) 66 | } 67 | fmt.Println("key", val) 68 | 69 | val2, err := client.Get("key2").Result() 70 | if err == redis.Nil { 71 | fmt.Println("key2 does not exist") 72 | } else if err != nil { 73 | panic(err) 74 | } else { 75 | fmt.Println("key2", val2) 76 | } 77 | // Output: key value 78 | // key2 does not exist 79 | } 80 | ``` 81 | 82 | ## Howto 83 | 84 | Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package. 85 | 86 | ## Look and feel 87 | 88 | Some corner cases: 89 | 90 | ```go 91 | // SET key value EX 10 NX 92 | set, err := client.SetNX("key", "value", 10*time.Second).Result() 93 | 94 | // SORT list LIMIT 0 2 ASC 95 | vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() 96 | 97 | // ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 98 | vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{ 99 | Min: "-inf", 100 | Max: "+inf", 101 | Offset: 0, 102 | Count: 2, 103 | }).Result() 104 | 105 | // ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM 106 | vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result() 107 | 108 | // EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" 109 | vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() 110 | ``` 111 | 112 | ## Benchmark 113 | 114 | go-redis vs redigo: 115 | 116 | ``` 117 | BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op 118 | BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op 119 | BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op 120 | BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op 121 | BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op 122 | BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op 123 | BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op 124 | BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op 125 | BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op 126 | BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op 127 | BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op 128 | BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op 129 | BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op 130 | BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op 131 | BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op 132 | BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op 133 | ``` 134 | 135 | Redis Cluster: 136 | 137 | ``` 138 | BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op 139 | BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op 140 | ``` 141 | 142 | ## See also 143 | 144 | - [Golang PostgreSQL ORM](https://github.com/go-pg/pg) 145 | - [Golang msgpack](https://github.com/vmihailenco/msgpack) 146 | - [Golang message task queue](https://github.com/go-msgqueue/msgqueue) 147 | -------------------------------------------------------------------------------- /t_list.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "container/list" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | const ListType = uint64(1) 9 | const ListTypeFancy = "list" 10 | 11 | var _ Item = (*List)(nil) 12 | 13 | type List struct { 14 | goList *list.List 15 | } 16 | 17 | func NewList() *List { 18 | return &List{goList: list.New()} 19 | } 20 | 21 | func (l *List) Value() interface{} { 22 | return l.goList 23 | } 24 | 25 | func (l *List) Type() uint64 { 26 | return ListType 27 | } 28 | 29 | func (l *List) TypeFancy() string { 30 | return ListTypeFancy 31 | } 32 | 33 | func (l *List) OnDelete(key *string, db *RedisDb) { 34 | panic("implement me") 35 | } 36 | 37 | // LLen returns number of elements. 38 | func (l *List) LLen() int { 39 | return l.goList.Len() 40 | } 41 | 42 | // LPush returns the length of the list after the push operation. 43 | func (l *List) LPush(values ...*string) int { 44 | for _, v := range values { 45 | l.goList.PushFront(*v) 46 | } 47 | return l.LLen() 48 | } 49 | 50 | // RPush returns the length of the list after the push operation. 51 | func (l *List) RPush(values ...*string) int { 52 | for _, v := range values { 53 | l.goList.PushBack(*v) 54 | } 55 | return l.LLen() 56 | } 57 | 58 | // LInsert see redis doc 59 | func (l *List) LInsert(isBefore bool, pivot, value *string) int { 60 | for e := l.goList.Front(); e.Next() != nil; e = e.Next() { 61 | if *vts(e) == *pivot { 62 | if isBefore { 63 | l.goList.InsertBefore(*value, e) 64 | } else { 65 | l.goList.InsertAfter(*value, e) 66 | } 67 | return l.LLen() 68 | } 69 | } 70 | return -1 71 | } 72 | 73 | // LPop returns popped value and false - 74 | // returns true if list is now emptied so the key can be deleted. 75 | func (l *List) LPop() (*string, bool) { 76 | if e := l.goList.Front(); e == nil { 77 | return nil, true 78 | } else { 79 | l.goList.Remove(e) 80 | return vts(e), false 81 | } 82 | } 83 | 84 | // RPop returns popped value and false - 85 | // returns true if list is now emptied so the key can be deleted. 86 | func (l *List) RPop() (*string, bool) { 87 | if e := l.goList.Back(); e == nil { 88 | return nil, true 89 | } else { 90 | l.goList.Remove(e) 91 | return vts(e), false 92 | } 93 | } 94 | 95 | // LRem see redis doc 96 | func (l *List) LRem(count int, value *string) int { 97 | // count > 0: Remove elements equal to value moving from head to tail. 98 | // count < 0: Remove elements equal to value moving from tail to head. 99 | // count = 0: Remove all elements equal to value. 100 | var rem int 101 | if count >= 0 { 102 | for e := l.goList.Front(); e.Next() != nil; { 103 | if *vts(e) == *value { 104 | r := e 105 | e = e.Next() 106 | l.goList.Remove(r) 107 | rem++ 108 | if count != 0 && rem == count { 109 | break 110 | } 111 | } else { 112 | e = e.Next() 113 | } 114 | } 115 | } else if count < 0 { 116 | count = abs(count) 117 | for e := l.goList.Back(); e.Prev() != nil; { 118 | if *vts(e) == *value { 119 | r := e 120 | e = e.Prev() 121 | l.goList.Remove(r) 122 | rem++ 123 | if count != 0 && rem == count { 124 | break 125 | } 126 | } else { 127 | e = e.Prev() 128 | } 129 | } 130 | } 131 | return rem 132 | } 133 | 134 | // LSet see redis doc 135 | func (l *List) LSet(index int, value *string) error { 136 | e := atIndex(index, l.goList) 137 | if e == nil { 138 | return errors.New("index out of range") 139 | } 140 | e.Value = *value 141 | return nil 142 | } 143 | 144 | // LIndex see redis doc 145 | func (l *List) LIndex(index int) (*string, error) { 146 | e := atIndex(index, l.goList) 147 | if e == nil { 148 | return nil, errors.New("index out of range") 149 | } 150 | return vts(e), nil 151 | } 152 | 153 | // LRange see redis doc 154 | func (l *List) LRange(start int, end int) []string { 155 | values := make([]string, 0) 156 | // from index to index 157 | from, to := startEndIndexes(start, end, l.LLen()) 158 | if from > to { 159 | return values 160 | } 161 | // get start element 162 | e := atIndex(from, l.goList) 163 | if e == nil { // shouldn't happen 164 | return values 165 | } 166 | // fill with values 167 | values = append(values, *vts(e)) 168 | for i := 0; i < to; i++ { 169 | e = e.Next() 170 | values = append(values, *vts(e)) 171 | } 172 | return values 173 | } 174 | 175 | // LTrim see redis docs - returns true if list is now emptied so the key can be deleted. 176 | func (l *List) LTrim(start int, end int) bool { 177 | // from index to index 178 | from, to := startEndIndexes(start, end, l.LLen()) 179 | if from > to { 180 | l.goList.Init() 181 | return true 182 | } 183 | // trim before 184 | if from > 0 { 185 | i := 0 186 | e := l.goList.Front() 187 | for e != nil && i < from { 188 | del := e 189 | e = e.Next() 190 | l.goList.Remove(del) 191 | i++ 192 | } 193 | } 194 | // trim after 195 | if to < l.LLen() { 196 | i := l.LLen() 197 | e := l.goList.Back() 198 | for e != nil && i > to { 199 | del := e 200 | e = e.Prev() 201 | l.goList.Remove(del) 202 | i-- 203 | } 204 | } 205 | return false 206 | } 207 | 208 | func startEndIndexes(start, end int, listLen int) (int, int) { 209 | if end > listLen-1 { 210 | end = listLen - 1 211 | } 212 | return toIndex(start, listLen), toIndex(end, listLen) 213 | } 214 | 215 | // atIndex finds element at given index or nil. 216 | func atIndex(index int, list *list.List) *list.Element { 217 | index = toIndex(index, list.Len()) 218 | e, i := list.Front(), 0 219 | for ; e.Next() != nil && i < index; i++ { 220 | if e.Next() == nil { 221 | return nil 222 | } 223 | e = e.Next() 224 | } 225 | return e 226 | } 227 | 228 | // Converts to real index. 229 | // 230 | // E.g. i=5, len=10 -> returns 5 231 | // 232 | // E.g. i=-1, len=10 -> returns 10 233 | // 234 | // E.g. i=-10, len=10 -> returns 0 235 | // 236 | // E.g. i=-3, len=10 -> returns 7 237 | func toIndex(i int, len int) int { 238 | if i < 0 { 239 | if len+i > 0 { 240 | return len + i 241 | } else { 242 | return 0 243 | } 244 | } 245 | return i 246 | } 247 | 248 | // Value of a list element to string. 249 | func vts(e *list.Element) *string { 250 | v := e.Value.(string) 251 | return &v 252 | } 253 | 254 | // Return positive x 255 | func abs(x int) int { 256 | if x < 0 { 257 | return -x 258 | } 259 | return x 260 | } 261 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/options.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/go-redis/redis/internal/pool" 15 | ) 16 | 17 | type Options struct { 18 | // The network type, either tcp or unix. 19 | // Default is tcp. 20 | Network string 21 | // host:port address. 22 | Addr string 23 | 24 | // Dialer creates new network connection and has priority over 25 | // Network and Addr options. 26 | Dialer func() (net.Conn, error) 27 | 28 | // Hook that is called when new connection is established. 29 | OnConnect func(*Conn) error 30 | 31 | // Optional password. Must match the password specified in the 32 | // requirepass server configuration option. 33 | Password string 34 | // Database to be selected after connecting to the server. 35 | DB int 36 | 37 | // Maximum number of retries before giving up. 38 | // Default is to not retry failed commands. 39 | MaxRetries int 40 | // Minimum backoff between each retry. 41 | // Default is 8 milliseconds; -1 disables backoff. 42 | MinRetryBackoff time.Duration 43 | // Maximum backoff between each retry. 44 | // Default is 512 milliseconds; -1 disables backoff. 45 | MaxRetryBackoff time.Duration 46 | 47 | // Dial timeout for establishing new connections. 48 | // Default is 5 seconds. 49 | DialTimeout time.Duration 50 | // Timeout for socket reads. If reached, commands will fail 51 | // with a timeout instead of blocking. 52 | // Default is 3 seconds. 53 | ReadTimeout time.Duration 54 | // Timeout for socket writes. If reached, commands will fail 55 | // with a timeout instead of blocking. 56 | // Default is ReadTimeout. 57 | WriteTimeout time.Duration 58 | 59 | // Maximum number of socket connections. 60 | // Default is 10 connections per every CPU as reported by runtime.NumCPU. 61 | PoolSize int 62 | // Minimum number of idle connections which is useful when establishing 63 | // new connection is slow. 64 | MinIdleConns int 65 | // Connection age at which client retires (closes) the connection. 66 | // Default is to not close aged connections. 67 | MaxConnAge time.Duration 68 | // Amount of time client waits for connection if all connections 69 | // are busy before returning an error. 70 | // Default is ReadTimeout + 1 second. 71 | PoolTimeout time.Duration 72 | // Amount of time after which client closes idle connections. 73 | // Should be less than server's timeout. 74 | // Default is 5 minutes. -1 disables idle timeout check. 75 | IdleTimeout time.Duration 76 | // Frequency of idle checks made by idle connections reaper. 77 | // Default is 1 minute. -1 disables idle connections reaper, 78 | // but idle connections are still discarded by the client 79 | // if IdleTimeout is set. 80 | IdleCheckFrequency time.Duration 81 | 82 | // Enables read only queries on slave nodes. 83 | readOnly bool 84 | 85 | // TLS Config to use. When set TLS will be negotiated. 86 | TLSConfig *tls.Config 87 | } 88 | 89 | func (opt *Options) init() { 90 | if opt.Network == "" { 91 | opt.Network = "tcp" 92 | } 93 | if opt.Dialer == nil { 94 | opt.Dialer = func() (net.Conn, error) { 95 | netDialer := &net.Dialer{ 96 | Timeout: opt.DialTimeout, 97 | KeepAlive: 5 * time.Minute, 98 | } 99 | if opt.TLSConfig == nil { 100 | return netDialer.Dial(opt.Network, opt.Addr) 101 | } else { 102 | return tls.DialWithDialer(netDialer, opt.Network, opt.Addr, opt.TLSConfig) 103 | } 104 | } 105 | } 106 | if opt.PoolSize == 0 { 107 | opt.PoolSize = 10 * runtime.NumCPU() 108 | } 109 | if opt.DialTimeout == 0 { 110 | opt.DialTimeout = 5 * time.Second 111 | } 112 | switch opt.ReadTimeout { 113 | case -1: 114 | opt.ReadTimeout = 0 115 | case 0: 116 | opt.ReadTimeout = 3 * time.Second 117 | } 118 | switch opt.WriteTimeout { 119 | case -1: 120 | opt.WriteTimeout = 0 121 | case 0: 122 | opt.WriteTimeout = opt.ReadTimeout 123 | } 124 | if opt.PoolTimeout == 0 { 125 | opt.PoolTimeout = opt.ReadTimeout + time.Second 126 | } 127 | if opt.IdleTimeout == 0 { 128 | opt.IdleTimeout = 5 * time.Minute 129 | } 130 | if opt.IdleCheckFrequency == 0 { 131 | opt.IdleCheckFrequency = time.Minute 132 | } 133 | 134 | switch opt.MinRetryBackoff { 135 | case -1: 136 | opt.MinRetryBackoff = 0 137 | case 0: 138 | opt.MinRetryBackoff = 8 * time.Millisecond 139 | } 140 | switch opt.MaxRetryBackoff { 141 | case -1: 142 | opt.MaxRetryBackoff = 0 143 | case 0: 144 | opt.MaxRetryBackoff = 512 * time.Millisecond 145 | } 146 | } 147 | 148 | // ParseURL parses an URL into Options that can be used to connect to Redis. 149 | func ParseURL(redisURL string) (*Options, error) { 150 | o := &Options{Network: "tcp"} 151 | u, err := url.Parse(redisURL) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | if u.Scheme != "redis" && u.Scheme != "rediss" { 157 | return nil, errors.New("invalid redis URL scheme: " + u.Scheme) 158 | } 159 | 160 | if u.User != nil { 161 | if p, ok := u.User.Password(); ok { 162 | o.Password = p 163 | } 164 | } 165 | 166 | if len(u.Query()) > 0 { 167 | return nil, errors.New("no options supported") 168 | } 169 | 170 | h, p, err := net.SplitHostPort(u.Host) 171 | if err != nil { 172 | h = u.Host 173 | } 174 | if h == "" { 175 | h = "localhost" 176 | } 177 | if p == "" { 178 | p = "6379" 179 | } 180 | o.Addr = net.JoinHostPort(h, p) 181 | 182 | f := strings.FieldsFunc(u.Path, func(r rune) bool { 183 | return r == '/' 184 | }) 185 | switch len(f) { 186 | case 0: 187 | o.DB = 0 188 | case 1: 189 | if o.DB, err = strconv.Atoi(f[0]); err != nil { 190 | return nil, fmt.Errorf("invalid redis database number: %q", f[0]) 191 | } 192 | default: 193 | return nil, errors.New("invalid redis URL path: " + u.Path) 194 | } 195 | 196 | if u.Scheme == "rediss" { 197 | o.TLSConfig = &tls.Config{ServerName: h} 198 | } 199 | return o, nil 200 | } 201 | 202 | func newConnPool(opt *Options) *pool.ConnPool { 203 | return pool.NewConnPool(&pool.Options{ 204 | Dialer: opt.Dialer, 205 | PoolSize: opt.PoolSize, 206 | MinIdleConns: opt.MinIdleConns, 207 | MaxConnAge: opt.MaxConnAge, 208 | PoolTimeout: opt.PoolTimeout, 209 | IdleTimeout: opt.IdleTimeout, 210 | IdleCheckFrequency: opt.IdleCheckFrequency, 211 | }) 212 | } 213 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/spew.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 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/go-redis/redis/internal/proto/reader.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "strconv" 8 | 9 | "github.com/go-redis/redis/internal/util" 10 | ) 11 | 12 | const ( 13 | ErrorReply = '-' 14 | StatusReply = '+' 15 | IntReply = ':' 16 | StringReply = '$' 17 | ArrayReply = '*' 18 | ) 19 | 20 | //------------------------------------------------------------------------------ 21 | 22 | const Nil = RedisError("redis: nil") 23 | 24 | type RedisError string 25 | 26 | func (e RedisError) Error() string { return string(e) } 27 | 28 | //------------------------------------------------------------------------------ 29 | 30 | type MultiBulkParse func(*Reader, int64) (interface{}, error) 31 | 32 | type Reader struct { 33 | rd *bufio.Reader 34 | _buf []byte 35 | } 36 | 37 | func NewReader(rd io.Reader) *Reader { 38 | return &Reader{ 39 | rd: bufio.NewReader(rd), 40 | _buf: make([]byte, 64), 41 | } 42 | } 43 | 44 | func (r *Reader) Reset(rd io.Reader) { 45 | r.rd.Reset(rd) 46 | } 47 | 48 | func (r *Reader) ReadLine() ([]byte, error) { 49 | line, isPrefix, err := r.rd.ReadLine() 50 | if err != nil { 51 | return nil, err 52 | } 53 | if isPrefix { 54 | return nil, bufio.ErrBufferFull 55 | } 56 | if len(line) == 0 { 57 | return nil, fmt.Errorf("redis: reply is empty") 58 | } 59 | if isNilReply(line) { 60 | return nil, Nil 61 | } 62 | return line, nil 63 | } 64 | 65 | func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) { 66 | line, err := r.ReadLine() 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | switch line[0] { 72 | case ErrorReply: 73 | return nil, ParseErrorReply(line) 74 | case StatusReply: 75 | return string(line[1:]), nil 76 | case IntReply: 77 | return util.ParseInt(line[1:], 10, 64) 78 | case StringReply: 79 | return r.readStringReply(line) 80 | case ArrayReply: 81 | n, err := parseArrayLen(line) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return m(r, n) 86 | } 87 | return nil, fmt.Errorf("redis: can't parse %.100q", line) 88 | } 89 | 90 | func (r *Reader) ReadIntReply() (int64, error) { 91 | line, err := r.ReadLine() 92 | if err != nil { 93 | return 0, err 94 | } 95 | switch line[0] { 96 | case ErrorReply: 97 | return 0, ParseErrorReply(line) 98 | case IntReply: 99 | return util.ParseInt(line[1:], 10, 64) 100 | default: 101 | return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) 102 | } 103 | } 104 | 105 | func (r *Reader) ReadString() (string, error) { 106 | line, err := r.ReadLine() 107 | if err != nil { 108 | return "", err 109 | } 110 | switch line[0] { 111 | case ErrorReply: 112 | return "", ParseErrorReply(line) 113 | case StringReply: 114 | return r.readStringReply(line) 115 | case StatusReply: 116 | return string(line[1:]), nil 117 | case IntReply: 118 | return string(line[1:]), nil 119 | default: 120 | return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line) 121 | } 122 | } 123 | 124 | func (r *Reader) readStringReply(line []byte) (string, error) { 125 | if isNilReply(line) { 126 | return "", Nil 127 | } 128 | 129 | replyLen, err := strconv.Atoi(string(line[1:])) 130 | if err != nil { 131 | return "", err 132 | } 133 | 134 | b := make([]byte, replyLen+2) 135 | _, err = io.ReadFull(r.rd, b) 136 | if err != nil { 137 | return "", err 138 | } 139 | 140 | return util.BytesToString(b[:replyLen]), nil 141 | } 142 | 143 | func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) { 144 | line, err := r.ReadLine() 145 | if err != nil { 146 | return nil, err 147 | } 148 | switch line[0] { 149 | case ErrorReply: 150 | return nil, ParseErrorReply(line) 151 | case ArrayReply: 152 | n, err := parseArrayLen(line) 153 | if err != nil { 154 | return nil, err 155 | } 156 | return m(r, n) 157 | default: 158 | return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line) 159 | } 160 | } 161 | 162 | func (r *Reader) ReadArrayLen() (int64, error) { 163 | line, err := r.ReadLine() 164 | if err != nil { 165 | return 0, err 166 | } 167 | switch line[0] { 168 | case ErrorReply: 169 | return 0, ParseErrorReply(line) 170 | case ArrayReply: 171 | return parseArrayLen(line) 172 | default: 173 | return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line) 174 | } 175 | } 176 | 177 | func (r *Reader) ReadScanReply() ([]string, uint64, error) { 178 | n, err := r.ReadArrayLen() 179 | if err != nil { 180 | return nil, 0, err 181 | } 182 | if n != 2 { 183 | return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n) 184 | } 185 | 186 | cursor, err := r.ReadUint() 187 | if err != nil { 188 | return nil, 0, err 189 | } 190 | 191 | n, err = r.ReadArrayLen() 192 | if err != nil { 193 | return nil, 0, err 194 | } 195 | 196 | keys := make([]string, n) 197 | for i := int64(0); i < n; i++ { 198 | key, err := r.ReadString() 199 | if err != nil { 200 | return nil, 0, err 201 | } 202 | keys[i] = key 203 | } 204 | 205 | return keys, cursor, err 206 | } 207 | 208 | func (r *Reader) ReadInt() (int64, error) { 209 | b, err := r.readTmpBytesReply() 210 | if err != nil { 211 | return 0, err 212 | } 213 | return util.ParseInt(b, 10, 64) 214 | } 215 | 216 | func (r *Reader) ReadUint() (uint64, error) { 217 | b, err := r.readTmpBytesReply() 218 | if err != nil { 219 | return 0, err 220 | } 221 | return util.ParseUint(b, 10, 64) 222 | } 223 | 224 | func (r *Reader) ReadFloatReply() (float64, error) { 225 | b, err := r.readTmpBytesReply() 226 | if err != nil { 227 | return 0, err 228 | } 229 | return util.ParseFloat(b, 64) 230 | } 231 | 232 | func (r *Reader) readTmpBytesReply() ([]byte, error) { 233 | line, err := r.ReadLine() 234 | if err != nil { 235 | return nil, err 236 | } 237 | switch line[0] { 238 | case ErrorReply: 239 | return nil, ParseErrorReply(line) 240 | case StringReply: 241 | return r._readTmpBytesReply(line) 242 | case StatusReply: 243 | return line[1:], nil 244 | default: 245 | return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line) 246 | } 247 | } 248 | 249 | func (r *Reader) _readTmpBytesReply(line []byte) ([]byte, error) { 250 | if isNilReply(line) { 251 | return nil, Nil 252 | } 253 | 254 | replyLen, err := strconv.Atoi(string(line[1:])) 255 | if err != nil { 256 | return nil, err 257 | } 258 | 259 | buf := r.buf(replyLen + 2) 260 | _, err = io.ReadFull(r.rd, buf) 261 | if err != nil { 262 | return nil, err 263 | } 264 | 265 | return buf[:replyLen], nil 266 | } 267 | 268 | func (r *Reader) buf(n int) []byte { 269 | if d := n - cap(r._buf); d > 0 { 270 | r._buf = append(r._buf, make([]byte, d)...) 271 | } 272 | return r._buf[:n] 273 | } 274 | 275 | func isNilReply(b []byte) bool { 276 | return len(b) == 3 && 277 | (b[0] == StringReply || b[0] == ArrayReply) && 278 | b[1] == '-' && b[2] == '1' 279 | } 280 | 281 | func ParseErrorReply(line []byte) error { 282 | return RedisError(string(line[1:])) 283 | } 284 | 285 | func parseArrayLen(line []byte) (int64, error) { 286 | if isNilReply(line) { 287 | return 0, Nil 288 | } 289 | return util.ParseInt(line[1:], 10, 64) 290 | } 291 | -------------------------------------------------------------------------------- /redisdb.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | const ( 9 | keysMapSize = 32 10 | redisDbMapSizeDefault = 3 11 | ) 12 | 13 | // A redis database. 14 | // There can be more than one in a redis instance. 15 | type RedisDb struct { 16 | // Database id 17 | id DatabaseId 18 | 19 | // All keys in this db. 20 | keys Keys 21 | 22 | // Keys with expire timestamp. 23 | expiringKeys ExpiringKeys 24 | 25 | // TODO long long avg_ttl; /* Average TTL, just for stats */ 26 | 27 | redis *Redis 28 | } 29 | 30 | // Redis databases map 31 | type RedisDbs map[DatabaseId]*RedisDb 32 | 33 | // Database id 34 | type DatabaseId uint 35 | 36 | // Key-Item map 37 | type Keys map[string]Item 38 | 39 | // Keys with expire timestamp. 40 | type ExpiringKeys map[string]time.Time 41 | 42 | // The item interface. An item is the value of a key. 43 | type Item interface { 44 | // The pointer to the value. 45 | Value() interface{} 46 | 47 | // The id of the type of the Item. 48 | // This need to be constant for the type because it is 49 | // used when de-/serializing item from/to disk. 50 | Type() uint64 51 | // The type of the Item as readable string. 52 | TypeFancy() string 53 | 54 | // OnDelete is triggered before the key of the item is deleted. 55 | // db is the affected database. 56 | OnDelete(key *string, db *RedisDb) 57 | } 58 | 59 | // NewRedisDb creates a new db. 60 | func NewRedisDb(id DatabaseId, r *Redis) *RedisDb { 61 | return &RedisDb{ 62 | id: id, 63 | redis: r, 64 | keys: make(Keys, keysMapSize), 65 | expiringKeys: make(ExpiringKeys, keysMapSize), 66 | } 67 | } 68 | 69 | // RedisDb gets the redis database by its id or creates and returns it if not exists. 70 | func (r *Redis) RedisDb(dbId DatabaseId) *RedisDb { 71 | getDb := func() *RedisDb { // returns nil if db not exists 72 | if db, ok := r.redisDbs[dbId]; ok { 73 | return db 74 | } 75 | return nil 76 | } 77 | 78 | r.Mu().RLock() 79 | db := getDb() 80 | r.Mu().RUnlock() 81 | if db != nil { 82 | return db 83 | } 84 | 85 | // create db 86 | r.Mu().Lock() 87 | defer r.Mu().Unlock() 88 | // check if db does not exists again since 89 | // multiple "mutex readers" can come to this point 90 | db = getDb() 91 | if db != nil { 92 | return db 93 | } 94 | // now really create db of that id 95 | r.redisDbs[dbId] = NewRedisDb(dbId, r) 96 | return r.redisDbs[dbId] 97 | } 98 | 99 | // RedisDbs gets all redis databases. 100 | func (r *Redis) RedisDbs() RedisDbs { 101 | r.Mu().RLock() 102 | defer r.Mu().RUnlock() 103 | return r.redisDbs 104 | } 105 | 106 | // Redis gets the redis instance. 107 | func (db *RedisDb) Redis() *Redis { 108 | return db.redis 109 | } 110 | 111 | // Mu gets the mutex. 112 | func (db *RedisDb) Mu() *sync.RWMutex { 113 | return db.Redis().Mu() 114 | } 115 | 116 | // Id gets the db id. 117 | func (db *RedisDb) Id() DatabaseId { 118 | return db.id 119 | } 120 | 121 | // Sets a key with an item which can have an expiration time. 122 | func (db *RedisDb) Set(key *string, i Item, expires bool, expiry time.Time) { 123 | db.Mu().Lock() 124 | defer db.Mu().Unlock() 125 | db.keys[*key] = i 126 | if expires { 127 | db.expiringKeys[*key] = expiry 128 | } 129 | } 130 | 131 | // Returns the item by the key or nil if key does not exists. 132 | func (db *RedisDb) Get(key *string) Item { 133 | db.Mu().RLock() 134 | defer db.Mu().RUnlock() 135 | return db.get(key) 136 | } 137 | 138 | func (db *RedisDb) get(key *string) Item { 139 | i, _ := db.keys[*key] 140 | return i 141 | } 142 | 143 | // Deletes a key, returns number of deleted keys. 144 | func (db *RedisDb) Delete(keys ...*string) int { 145 | db.Mu().Lock() 146 | defer db.Mu().Unlock() 147 | return db.delete(keys...) 148 | } 149 | 150 | // If checkExists is false, then return bool is reprehensible. 151 | func (db *RedisDb) delete(keys ...*string) int { 152 | do := func(k *string) bool { 153 | if k == nil { 154 | return false 155 | } 156 | i := db.get(k) 157 | if i == nil { 158 | return false 159 | } 160 | i.OnDelete(k, db) 161 | delete(db.keys, *k) 162 | delete(db.expiringKeys, *k) 163 | return true 164 | } 165 | 166 | var c int 167 | for _, k := range keys { 168 | if do(k) { 169 | c++ 170 | } 171 | } 172 | 173 | return c 174 | } 175 | 176 | func (db *RedisDb) DeleteExpired(keys ...*string) int { 177 | var c int 178 | for _, k := range keys { 179 | if k != nil && db.Expired(k) && db.Delete(k) > 0 { 180 | c++ 181 | } 182 | } 183 | return c 184 | } 185 | 186 | // GetOrExpire gets the item or nil if expired or not exists. If 'deleteIfExpired' is true the key will be deleted. 187 | func (db *RedisDb) GetOrExpire(key *string, deleteIfExpired bool) Item { 188 | // TODO mutex optimize this func so that a RLock is mainly first opened 189 | db.Mu().Lock() 190 | defer db.Mu().Unlock() 191 | i, ok := db.keys[*key] 192 | if !ok { 193 | return nil 194 | } 195 | if db.expired(key) { 196 | if deleteIfExpired { 197 | db.delete(key) 198 | } 199 | return nil 200 | } 201 | return i 202 | } 203 | 204 | // IsEmpty checks if db is empty. 205 | func (db *RedisDb) IsEmpty() bool { 206 | db.Mu().RLock() 207 | defer db.Mu().RUnlock() 208 | return len(db.keys) == 0 209 | } 210 | 211 | // HasExpiringKeys checks if db has any expiring keys. 212 | func (db *RedisDb) HasExpiringKeys() bool { 213 | db.Mu().RLock() 214 | defer db.Mu().RUnlock() 215 | return len(db.expiringKeys) != 0 216 | } 217 | 218 | // Check if key exists. 219 | func (db *RedisDb) Exists(key *string) bool { 220 | db.Mu().RLock() 221 | defer db.Mu().RUnlock() 222 | return db.exists(key) 223 | } 224 | func (db *RedisDb) exists(key *string) bool { 225 | _, ok := db.keys[*key] 226 | return ok 227 | } 228 | 229 | // Check if key has an expiry set. 230 | func (db *RedisDb) Expires(key *string) bool { 231 | db.Mu().RLock() 232 | defer db.Mu().RUnlock() 233 | return db.expires(key) 234 | } 235 | func (db *RedisDb) expires(key *string) bool { 236 | _, ok := db.expiringKeys[*key] 237 | return ok 238 | } 239 | 240 | // Expired only check if a key can and is expired. 241 | func (db *RedisDb) Expired(key *string) bool { 242 | db.Mu().RLock() 243 | defer db.Mu().RUnlock() 244 | return db.expired(key) 245 | } 246 | func (db *RedisDb) expired(key *string) bool { 247 | return db.expires(key) && TimeExpired(db.expiry(key)) 248 | } 249 | 250 | // Expiry gets the expiry of the key has one. 251 | func (db *RedisDb) Expiry(key *string) time.Time { 252 | db.Mu().RLock() 253 | defer db.Mu().RUnlock() 254 | return db.expiry(key) 255 | } 256 | 257 | func (db *RedisDb) expiry(key *string) time.Time { 258 | return db.expiringKeys[*key] 259 | } 260 | 261 | // Keys gets all keys in this db. 262 | func (db *RedisDb) Keys() Keys { 263 | db.Mu().RLock() 264 | defer db.Mu().RUnlock() 265 | return db.keys 266 | } 267 | 268 | // ExpiringKeys gets keys with an expiry set and their timeout. 269 | func (db *RedisDb) ExpiringKeys() ExpiringKeys { 270 | db.Mu().RLock() 271 | defer db.Mu().RUnlock() 272 | return db.expiringKeys 273 | } 274 | 275 | // TimeExpired check if a timestamp is older than now. 276 | func TimeExpired(expireAt time.Time) bool { 277 | return time.Now().After(expireAt) 278 | } 279 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Package errors provides simple error handling primitives. 2 | // 3 | // The traditional error handling idiom in Go is roughly akin to 4 | // 5 | // if err != nil { 6 | // return err 7 | // } 8 | // 9 | // which applied recursively up the call stack results in error reports 10 | // without context or debugging information. The errors package allows 11 | // programmers to add context to the failure path in their code in a way 12 | // that does not destroy the original value of the error. 13 | // 14 | // Adding context to an error 15 | // 16 | // The errors.Wrap function returns a new error that adds context to the 17 | // original error by recording a stack trace at the point Wrap is called, 18 | // and the supplied message. For example 19 | // 20 | // _, err := ioutil.ReadAll(r) 21 | // if err != nil { 22 | // return errors.Wrap(err, "read failed") 23 | // } 24 | // 25 | // If additional control is required the errors.WithStack and errors.WithMessage 26 | // functions destructure errors.Wrap into its component operations of annotating 27 | // an error with a stack trace and an a message, respectively. 28 | // 29 | // Retrieving the cause of an error 30 | // 31 | // Using errors.Wrap constructs a stack of errors, adding context to the 32 | // preceding error. Depending on the nature of the error it may be necessary 33 | // to reverse the operation of errors.Wrap to retrieve the original error 34 | // for inspection. Any error value which implements this interface 35 | // 36 | // type causer interface { 37 | // Cause() error 38 | // } 39 | // 40 | // can be inspected by errors.Cause. errors.Cause will recursively retrieve 41 | // the topmost error which does not implement causer, which is assumed to be 42 | // the original cause. For example: 43 | // 44 | // switch err := errors.Cause(err).(type) { 45 | // case *MyError: 46 | // // handle specifically 47 | // default: 48 | // // unknown error 49 | // } 50 | // 51 | // causer interface is not exported by this package, but is considered a part 52 | // of stable public API. 53 | // 54 | // Formatted printing of errors 55 | // 56 | // All error values returned from this package implement fmt.Formatter and can 57 | // be formatted by the fmt package. The following verbs are supported 58 | // 59 | // %s print the error. If the error has a Cause it will be 60 | // printed recursively 61 | // %v see %s 62 | // %+v extended format. Each Frame of the error's StackTrace will 63 | // be printed in detail. 64 | // 65 | // Retrieving the stack trace of an error or wrapper 66 | // 67 | // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are 68 | // invoked. This information can be retrieved with the following interface. 69 | // 70 | // type stackTracer interface { 71 | // StackTrace() errors.StackTrace 72 | // } 73 | // 74 | // Where errors.StackTrace is defined as 75 | // 76 | // type StackTrace []Frame 77 | // 78 | // The Frame type represents a call site in the stack trace. Frame supports 79 | // the fmt.Formatter interface that can be used for printing information about 80 | // the stack trace of this error. For example: 81 | // 82 | // if err, ok := err.(stackTracer); ok { 83 | // for _, f := range err.StackTrace() { 84 | // fmt.Printf("%+s:%d", f) 85 | // } 86 | // } 87 | // 88 | // stackTracer interface is not exported by this package, but is considered a part 89 | // of stable public API. 90 | // 91 | // See the documentation for Frame.Format for more details. 92 | package errors 93 | 94 | import ( 95 | "fmt" 96 | "io" 97 | ) 98 | 99 | // New returns an error with the supplied message. 100 | // New also records the stack trace at the point it was called. 101 | func New(message string) error { 102 | return &fundamental{ 103 | msg: message, 104 | stack: callers(), 105 | } 106 | } 107 | 108 | // Errorf formats according to a format specifier and returns the string 109 | // as a value that satisfies error. 110 | // Errorf also records the stack trace at the point it was called. 111 | func Errorf(format string, args ...interface{}) error { 112 | return &fundamental{ 113 | msg: fmt.Sprintf(format, args...), 114 | stack: callers(), 115 | } 116 | } 117 | 118 | // fundamental is an error that has a message and a stack, but no caller. 119 | type fundamental struct { 120 | msg string 121 | *stack 122 | } 123 | 124 | func (f *fundamental) Error() string { return f.msg } 125 | 126 | func (f *fundamental) Format(s fmt.State, verb rune) { 127 | switch verb { 128 | case 'v': 129 | if s.Flag('+') { 130 | io.WriteString(s, f.msg) 131 | f.stack.Format(s, verb) 132 | return 133 | } 134 | fallthrough 135 | case 's': 136 | io.WriteString(s, f.msg) 137 | case 'q': 138 | fmt.Fprintf(s, "%q", f.msg) 139 | } 140 | } 141 | 142 | // WithStack annotates err with a stack trace at the point WithStack was called. 143 | // If err is nil, WithStack returns nil. 144 | func WithStack(err error) error { 145 | if err == nil { 146 | return nil 147 | } 148 | return &withStack{ 149 | err, 150 | callers(), 151 | } 152 | } 153 | 154 | type withStack struct { 155 | error 156 | *stack 157 | } 158 | 159 | func (w *withStack) Cause() error { return w.error } 160 | 161 | func (w *withStack) Format(s fmt.State, verb rune) { 162 | switch verb { 163 | case 'v': 164 | if s.Flag('+') { 165 | fmt.Fprintf(s, "%+v", w.Cause()) 166 | w.stack.Format(s, verb) 167 | return 168 | } 169 | fallthrough 170 | case 's': 171 | io.WriteString(s, w.Error()) 172 | case 'q': 173 | fmt.Fprintf(s, "%q", w.Error()) 174 | } 175 | } 176 | 177 | // Wrap returns an error annotating err with a stack trace 178 | // at the point Wrap is called, and the supplied message. 179 | // If err is nil, Wrap returns nil. 180 | func Wrap(err error, message string) error { 181 | if err == nil { 182 | return nil 183 | } 184 | err = &withMessage{ 185 | cause: err, 186 | msg: message, 187 | } 188 | return &withStack{ 189 | err, 190 | callers(), 191 | } 192 | } 193 | 194 | // Wrapf returns an error annotating err with a stack trace 195 | // at the point Wrapf is call, and the format specifier. 196 | // If err is nil, Wrapf returns nil. 197 | func Wrapf(err error, format string, args ...interface{}) error { 198 | if err == nil { 199 | return nil 200 | } 201 | err = &withMessage{ 202 | cause: err, 203 | msg: fmt.Sprintf(format, args...), 204 | } 205 | return &withStack{ 206 | err, 207 | callers(), 208 | } 209 | } 210 | 211 | // WithMessage annotates err with a new message. 212 | // If err is nil, WithMessage returns nil. 213 | func WithMessage(err error, message string) error { 214 | if err == nil { 215 | return nil 216 | } 217 | return &withMessage{ 218 | cause: err, 219 | msg: message, 220 | } 221 | } 222 | 223 | type withMessage struct { 224 | cause error 225 | msg string 226 | } 227 | 228 | func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } 229 | func (w *withMessage) Cause() error { return w.cause } 230 | 231 | func (w *withMessage) Format(s fmt.State, verb rune) { 232 | switch verb { 233 | case 'v': 234 | if s.Flag('+') { 235 | fmt.Fprintf(s, "%+v\n", w.Cause()) 236 | io.WriteString(s, w.msg) 237 | return 238 | } 239 | fallthrough 240 | case 's', 'q': 241 | io.WriteString(s, w.Error()) 242 | } 243 | } 244 | 245 | // Cause returns the underlying cause of the error, if possible. 246 | // An error value has a cause if it implements the following 247 | // interface: 248 | // 249 | // type causer interface { 250 | // Cause() error 251 | // } 252 | // 253 | // If the error does not implement Cause, the original error will 254 | // be returned. If the error is nil, nil will be returned without further 255 | // investigation. 256 | func Cause(err error) error { 257 | type causer interface { 258 | Cause() error 259 | } 260 | 261 | for err != nil { 262 | cause, ok := err.(causer) 263 | if !ok { 264 | break 265 | } 266 | err = cause.Cause() 267 | } 268 | return err 269 | } 270 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/sentinel.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "net" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/go-redis/redis/internal" 12 | "github.com/go-redis/redis/internal/pool" 13 | ) 14 | 15 | //------------------------------------------------------------------------------ 16 | 17 | // FailoverOptions are used to configure a failover client and should 18 | // be passed to NewFailoverClient. 19 | type FailoverOptions struct { 20 | // The master name. 21 | MasterName string 22 | // A seed list of host:port addresses of sentinel nodes. 23 | SentinelAddrs []string 24 | 25 | // Following options are copied from Options struct. 26 | 27 | OnConnect func(*Conn) error 28 | 29 | Password string 30 | DB int 31 | 32 | MaxRetries int 33 | MinRetryBackoff time.Duration 34 | MaxRetryBackoff time.Duration 35 | 36 | DialTimeout time.Duration 37 | ReadTimeout time.Duration 38 | WriteTimeout time.Duration 39 | 40 | PoolSize int 41 | MinIdleConns int 42 | MaxConnAge time.Duration 43 | PoolTimeout time.Duration 44 | IdleTimeout time.Duration 45 | IdleCheckFrequency time.Duration 46 | 47 | TLSConfig *tls.Config 48 | } 49 | 50 | func (opt *FailoverOptions) options() *Options { 51 | return &Options{ 52 | Addr: "FailoverClient", 53 | 54 | OnConnect: opt.OnConnect, 55 | 56 | DB: opt.DB, 57 | Password: opt.Password, 58 | 59 | MaxRetries: opt.MaxRetries, 60 | 61 | DialTimeout: opt.DialTimeout, 62 | ReadTimeout: opt.ReadTimeout, 63 | WriteTimeout: opt.WriteTimeout, 64 | 65 | PoolSize: opt.PoolSize, 66 | PoolTimeout: opt.PoolTimeout, 67 | IdleTimeout: opt.IdleTimeout, 68 | IdleCheckFrequency: opt.IdleCheckFrequency, 69 | 70 | TLSConfig: opt.TLSConfig, 71 | } 72 | } 73 | 74 | // NewFailoverClient returns a Redis client that uses Redis Sentinel 75 | // for automatic failover. It's safe for concurrent use by multiple 76 | // goroutines. 77 | func NewFailoverClient(failoverOpt *FailoverOptions) *Client { 78 | opt := failoverOpt.options() 79 | opt.init() 80 | 81 | failover := &sentinelFailover{ 82 | masterName: failoverOpt.MasterName, 83 | sentinelAddrs: failoverOpt.SentinelAddrs, 84 | 85 | opt: opt, 86 | } 87 | 88 | c := Client{ 89 | baseClient: baseClient{ 90 | opt: opt, 91 | connPool: failover.Pool(), 92 | 93 | onClose: func() error { 94 | return failover.Close() 95 | }, 96 | }, 97 | } 98 | c.baseClient.init() 99 | c.cmdable.setProcessor(c.Process) 100 | 101 | return &c 102 | } 103 | 104 | //------------------------------------------------------------------------------ 105 | 106 | type SentinelClient struct { 107 | baseClient 108 | } 109 | 110 | func NewSentinelClient(opt *Options) *SentinelClient { 111 | opt.init() 112 | c := &SentinelClient{ 113 | baseClient: baseClient{ 114 | opt: opt, 115 | connPool: newConnPool(opt), 116 | }, 117 | } 118 | c.baseClient.init() 119 | return c 120 | } 121 | 122 | func (c *SentinelClient) PubSub() *PubSub { 123 | pubsub := &PubSub{ 124 | opt: c.opt, 125 | 126 | newConn: func(channels []string) (*pool.Conn, error) { 127 | return c.newConn() 128 | }, 129 | closeConn: c.connPool.CloseConn, 130 | } 131 | pubsub.init() 132 | return pubsub 133 | } 134 | 135 | func (c *SentinelClient) GetMasterAddrByName(name string) *StringSliceCmd { 136 | cmd := NewStringSliceCmd("SENTINEL", "get-master-addr-by-name", name) 137 | c.Process(cmd) 138 | return cmd 139 | } 140 | 141 | func (c *SentinelClient) Sentinels(name string) *SliceCmd { 142 | cmd := NewSliceCmd("SENTINEL", "sentinels", name) 143 | c.Process(cmd) 144 | return cmd 145 | } 146 | 147 | type sentinelFailover struct { 148 | sentinelAddrs []string 149 | 150 | opt *Options 151 | 152 | pool *pool.ConnPool 153 | poolOnce sync.Once 154 | 155 | mu sync.RWMutex 156 | masterName string 157 | _masterAddr string 158 | sentinel *SentinelClient 159 | } 160 | 161 | func (d *sentinelFailover) Close() error { 162 | return d.resetSentinel() 163 | } 164 | 165 | func (d *sentinelFailover) Pool() *pool.ConnPool { 166 | d.poolOnce.Do(func() { 167 | d.opt.Dialer = d.dial 168 | d.pool = newConnPool(d.opt) 169 | }) 170 | return d.pool 171 | } 172 | 173 | func (d *sentinelFailover) dial() (net.Conn, error) { 174 | addr, err := d.MasterAddr() 175 | if err != nil { 176 | return nil, err 177 | } 178 | return net.DialTimeout("tcp", addr, d.opt.DialTimeout) 179 | } 180 | 181 | func (d *sentinelFailover) MasterAddr() (string, error) { 182 | d.mu.Lock() 183 | defer d.mu.Unlock() 184 | 185 | addr, err := d.masterAddr() 186 | if err != nil { 187 | return "", err 188 | } 189 | d._switchMaster(addr) 190 | 191 | return addr, nil 192 | } 193 | 194 | func (d *sentinelFailover) masterAddr() (string, error) { 195 | // Try last working sentinel. 196 | if d.sentinel != nil { 197 | addr, err := d.sentinel.GetMasterAddrByName(d.masterName).Result() 198 | if err == nil { 199 | addr := net.JoinHostPort(addr[0], addr[1]) 200 | return addr, nil 201 | } 202 | 203 | internal.Logf("sentinel: GetMasterAddrByName name=%q failed: %s", 204 | d.masterName, err) 205 | d._resetSentinel() 206 | } 207 | 208 | for i, sentinelAddr := range d.sentinelAddrs { 209 | sentinel := NewSentinelClient(&Options{ 210 | Addr: sentinelAddr, 211 | 212 | DialTimeout: d.opt.DialTimeout, 213 | ReadTimeout: d.opt.ReadTimeout, 214 | WriteTimeout: d.opt.WriteTimeout, 215 | 216 | PoolSize: d.opt.PoolSize, 217 | PoolTimeout: d.opt.PoolTimeout, 218 | IdleTimeout: d.opt.IdleTimeout, 219 | }) 220 | 221 | masterAddr, err := sentinel.GetMasterAddrByName(d.masterName).Result() 222 | if err != nil { 223 | internal.Logf("sentinel: GetMasterAddrByName master=%q failed: %s", 224 | d.masterName, err) 225 | sentinel.Close() 226 | continue 227 | } 228 | 229 | // Push working sentinel to the top. 230 | d.sentinelAddrs[0], d.sentinelAddrs[i] = d.sentinelAddrs[i], d.sentinelAddrs[0] 231 | d.setSentinel(sentinel) 232 | 233 | addr := net.JoinHostPort(masterAddr[0], masterAddr[1]) 234 | return addr, nil 235 | } 236 | 237 | return "", errors.New("redis: all sentinels are unreachable") 238 | } 239 | 240 | func (c *sentinelFailover) switchMaster(addr string) { 241 | c.mu.Lock() 242 | c._switchMaster(addr) 243 | c.mu.Unlock() 244 | } 245 | 246 | func (c *sentinelFailover) _switchMaster(addr string) { 247 | if c._masterAddr == addr { 248 | return 249 | } 250 | 251 | internal.Logf("sentinel: new master=%q addr=%q", 252 | c.masterName, addr) 253 | _ = c.Pool().Filter(func(cn *pool.Conn) bool { 254 | return cn.RemoteAddr().String() != addr 255 | }) 256 | c._masterAddr = addr 257 | } 258 | 259 | func (d *sentinelFailover) setSentinel(sentinel *SentinelClient) { 260 | d.discoverSentinels(sentinel) 261 | d.sentinel = sentinel 262 | go d.listen(sentinel) 263 | } 264 | 265 | func (d *sentinelFailover) resetSentinel() error { 266 | var err error 267 | d.mu.Lock() 268 | if d.sentinel != nil { 269 | err = d._resetSentinel() 270 | } 271 | d.mu.Unlock() 272 | return err 273 | } 274 | 275 | func (d *sentinelFailover) _resetSentinel() error { 276 | err := d.sentinel.Close() 277 | d.sentinel = nil 278 | return err 279 | } 280 | 281 | func (d *sentinelFailover) discoverSentinels(sentinel *SentinelClient) { 282 | sentinels, err := sentinel.Sentinels(d.masterName).Result() 283 | if err != nil { 284 | internal.Logf("sentinel: Sentinels master=%q failed: %s", d.masterName, err) 285 | return 286 | } 287 | for _, sentinel := range sentinels { 288 | vals := sentinel.([]interface{}) 289 | for i := 0; i < len(vals); i += 2 { 290 | key := vals[i].(string) 291 | if key == "name" { 292 | sentinelAddr := vals[i+1].(string) 293 | if !contains(d.sentinelAddrs, sentinelAddr) { 294 | internal.Logf( 295 | "sentinel: discovered new sentinel=%q for master=%q", 296 | sentinelAddr, d.masterName, 297 | ) 298 | d.sentinelAddrs = append(d.sentinelAddrs, sentinelAddr) 299 | } 300 | } 301 | } 302 | } 303 | } 304 | 305 | func (d *sentinelFailover) listen(sentinel *SentinelClient) { 306 | pubsub := sentinel.PubSub() 307 | defer pubsub.Close() 308 | 309 | err := pubsub.Subscribe("+switch-master") 310 | if err != nil { 311 | internal.Logf("sentinel: Subscribe failed: %s", err) 312 | d.resetSentinel() 313 | return 314 | } 315 | 316 | for { 317 | msg, err := pubsub.ReceiveMessage() 318 | if err != nil { 319 | if err == pool.ErrClosed { 320 | d.resetSentinel() 321 | return 322 | } 323 | internal.Logf("sentinel: ReceiveMessage failed: %s", err) 324 | continue 325 | } 326 | 327 | switch msg.Channel { 328 | case "+switch-master": 329 | parts := strings.Split(msg.Payload, " ") 330 | if parts[0] != d.masterName { 331 | internal.Logf("sentinel: ignore addr for master=%q", parts[0]) 332 | continue 333 | } 334 | addr := net.JoinHostPort(parts[3], parts[4]) 335 | d.switchMaster(addr) 336 | } 337 | } 338 | } 339 | 340 | func contains(slice []string, str string) bool { 341 | for _, s := range slice { 342 | if s == str { 343 | return true 344 | } 345 | } 346 | return false 347 | } 348 | -------------------------------------------------------------------------------- /vendor/github.com/davecgh/go-spew/spew/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013-2016 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 | * DisablePointerAddresses 95 | DisablePointerAddresses specifies whether to disable the printing of 96 | pointer addresses. This is useful when diffing data structures in tests. 97 | 98 | * DisableCapacities 99 | DisableCapacities specifies whether to disable the printing of 100 | capacities for arrays, slices, maps and channels. This is useful when 101 | diffing data structures in tests. 102 | 103 | * ContinueOnMethod 104 | Enables recursion into types after invoking error and Stringer interface 105 | methods. Recursion after method invocation is disabled by default. 106 | 107 | * SortKeys 108 | Specifies map keys should be sorted before being printed. Use 109 | this to have a more deterministic, diffable output. Note that 110 | only native types (bool, int, uint, floats, uintptr and string) 111 | and types which implement error or Stringer interfaces are 112 | supported with other types sorted according to the 113 | reflect.Value.String() output which guarantees display 114 | stability. Natural map order is used by default. 115 | 116 | * SpewKeys 117 | Specifies that, as a last resort attempt, map keys should be 118 | spewed to strings and sorted by those strings. This is only 119 | considered if SortKeys is true. 120 | 121 | Dump Usage 122 | 123 | Simply call spew.Dump with a list of variables you want to dump: 124 | 125 | spew.Dump(myVar1, myVar2, ...) 126 | 127 | You may also call spew.Fdump if you would prefer to output to an arbitrary 128 | io.Writer. For example, to dump to standard error: 129 | 130 | spew.Fdump(os.Stderr, myVar1, myVar2, ...) 131 | 132 | A third option is to call spew.Sdump to get the formatted output as a string: 133 | 134 | str := spew.Sdump(myVar1, myVar2, ...) 135 | 136 | Sample Dump Output 137 | 138 | See the Dump example for details on the setup of the types and variables being 139 | shown here. 140 | 141 | (main.Foo) { 142 | unexportedField: (*main.Bar)(0xf84002e210)({ 143 | flag: (main.Flag) flagTwo, 144 | data: (uintptr) 145 | }), 146 | ExportedField: (map[interface {}]interface {}) (len=1) { 147 | (string) (len=3) "one": (bool) true 148 | } 149 | } 150 | 151 | Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C 152 | command as shown. 153 | ([]uint8) (len=32 cap=32) { 154 | 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | 155 | 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| 156 | 00000020 31 32 |12| 157 | } 158 | 159 | Custom Formatter 160 | 161 | Spew provides a custom formatter that implements the fmt.Formatter interface 162 | so that it integrates cleanly with standard fmt package printing functions. The 163 | formatter is useful for inline printing of smaller data types similar to the 164 | standard %v format specifier. 165 | 166 | The custom formatter only responds to the %v (most compact), %+v (adds pointer 167 | addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb 168 | combinations. Any other verbs such as %x and %q will be sent to the the 169 | standard fmt package for formatting. In addition, the custom formatter ignores 170 | the width and precision arguments (however they will still work on the format 171 | specifiers not handled by the custom formatter). 172 | 173 | Custom Formatter Usage 174 | 175 | The simplest way to make use of the spew custom formatter is to call one of the 176 | convenience functions such as spew.Printf, spew.Println, or spew.Printf. The 177 | functions have syntax you are most likely already familiar with: 178 | 179 | spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) 180 | spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 181 | spew.Println(myVar, myVar2) 182 | spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) 183 | spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) 184 | 185 | See the Index for the full list convenience functions. 186 | 187 | Sample Formatter Output 188 | 189 | Double pointer to a uint8: 190 | %v: <**>5 191 | %+v: <**>(0xf8400420d0->0xf8400420c8)5 192 | %#v: (**uint8)5 193 | %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 194 | 195 | Pointer to circular struct with a uint8 field and a pointer to itself: 196 | %v: <*>{1 <*>} 197 | %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} 198 | %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} 199 | %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} 200 | 201 | See the Printf example for details on the setup of variables being shown 202 | here. 203 | 204 | Errors 205 | 206 | Since it is possible for custom Stringer/error interfaces to panic, spew 207 | detects them and handles them internally by printing the panic information 208 | inline with the output. Since spew is intended to provide deep pretty printing 209 | capabilities on structures, it intentionally does not return any errors. 210 | */ 211 | package spew 212 | -------------------------------------------------------------------------------- /vendor/github.com/redis-go/redcon/append.go: -------------------------------------------------------------------------------- 1 | package redcon 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // Kind is the kind of command 9 | type Kind int 10 | 11 | const ( 12 | // Redis is returned for Redis protocol commands 13 | Redis Kind = iota 14 | // Tile38 is returnd for Tile38 native protocol commands 15 | Tile38 16 | // Telnet is returnd for plain telnet commands 17 | Telnet 18 | ) 19 | 20 | var errInvalidMessage = &errProtocol{"invalid message"} 21 | 22 | // ReadNextCommand reads the next command from the provided packet. It's 23 | // possible that the packet contains multiple commands, or zero commands 24 | // when the packet is incomplete. 25 | // 'argsbuf' is an optional reusable buffer and it can be nil. 26 | // 'complete' indicates that a command was read. false means no more commands. 27 | // 'args' are the output arguments for the command. 28 | // 'kind' is the type of command that was read. 29 | // 'leftover' is any remaining unused bytes which belong to the next command. 30 | // 'err' is returned when a protocol error was encountered. 31 | func ReadNextCommand(packet []byte, argsbuf [][]byte) ( 32 | complete bool, args [][]byte, kind Kind, leftover []byte, err error, 33 | ) { 34 | args = argsbuf[:0] 35 | if len(packet) > 0 { 36 | if packet[0] != '*' { 37 | if packet[0] == '$' { 38 | return readTile38Command(packet, args) 39 | } 40 | return readTelnetCommand(packet, args) 41 | } 42 | // standard redis command 43 | for s, i := 1, 1; i < len(packet); i++ { 44 | if packet[i] == '\n' { 45 | if packet[i-1] != '\r' { 46 | return false, args[:0], Redis, packet, errInvalidMultiBulkLength 47 | } 48 | count, ok := parseInt(packet[s : i-1]) 49 | if !ok || count < 0 { 50 | return false, args[:0], Redis, packet, errInvalidMultiBulkLength 51 | } 52 | i++ 53 | if count == 0 { 54 | return true, args[:0], Redis, packet[i:], nil 55 | } 56 | nextArg: 57 | for j := 0; j < count; j++ { 58 | if i == len(packet) { 59 | break 60 | } 61 | if packet[i] != '$' { 62 | return false, args[:0], Redis, packet, 63 | &errProtocol{"expected '$', got '" + 64 | string(packet[i]) + "'"} 65 | } 66 | for s := i + 1; i < len(packet); i++ { 67 | if packet[i] == '\n' { 68 | if packet[i-1] != '\r' { 69 | return false, args[:0], Redis, packet, errInvalidBulkLength 70 | } 71 | n, ok := parseInt(packet[s : i-1]) 72 | if !ok || count <= 0 { 73 | return false, args[:0], Redis, packet, errInvalidBulkLength 74 | } 75 | i++ 76 | if len(packet)-i >= n+2 { 77 | if packet[i+n] != '\r' || packet[i+n+1] != '\n' { 78 | return false, args[:0], Redis, packet, errInvalidBulkLength 79 | } 80 | args = append(args, packet[i:i+n]) 81 | i += n + 2 82 | if j == count-1 { 83 | // done reading 84 | return true, args, Redis, packet[i:], nil 85 | } 86 | continue nextArg 87 | } 88 | break 89 | } 90 | } 91 | break 92 | } 93 | break 94 | } 95 | } 96 | } 97 | return false, args[:0], Redis, packet, nil 98 | } 99 | 100 | func readTile38Command(packet []byte, argsbuf [][]byte) ( 101 | complete bool, args [][]byte, kind Kind, leftover []byte, err error, 102 | ) { 103 | for i := 1; i < len(packet); i++ { 104 | if packet[i] == ' ' { 105 | n, ok := parseInt(packet[1:i]) 106 | if !ok || n < 0 { 107 | return false, args[:0], Tile38, packet, errInvalidMessage 108 | } 109 | i++ 110 | if len(packet) >= i+n+2 { 111 | if packet[i+n] != '\r' || packet[i+n+1] != '\n' { 112 | return false, args[:0], Tile38, packet, errInvalidMessage 113 | } 114 | line := packet[i : i+n] 115 | reading: 116 | for len(line) != 0 { 117 | if line[0] == '{' { 118 | // The native protocol cannot understand json boundaries so it assumes that 119 | // a json element must be at the end of the line. 120 | args = append(args, line) 121 | break 122 | } 123 | if line[0] == '"' && line[len(line)-1] == '"' { 124 | if len(args) > 0 && 125 | strings.ToLower(string(args[0])) == "set" && 126 | strings.ToLower(string(args[len(args)-1])) == "string" { 127 | // Setting a string value that is contained inside double quotes. 128 | // This is only because of the boundary issues of the native protocol. 129 | args = append(args, line[1:len(line)-1]) 130 | break 131 | } 132 | } 133 | i := 0 134 | for ; i < len(line); i++ { 135 | if line[i] == ' ' { 136 | value := line[:i] 137 | if len(value) > 0 { 138 | args = append(args, value) 139 | } 140 | line = line[i+1:] 141 | continue reading 142 | } 143 | } 144 | args = append(args, line) 145 | break 146 | } 147 | return true, args, Tile38, packet[i+n+2:], nil 148 | } 149 | break 150 | } 151 | } 152 | return false, args[:0], Tile38, packet, nil 153 | } 154 | func readTelnetCommand(packet []byte, argsbuf [][]byte) ( 155 | complete bool, args [][]byte, kind Kind, leftover []byte, err error, 156 | ) { 157 | // just a plain text command 158 | for i := 0; i < len(packet); i++ { 159 | if packet[i] == '\n' { 160 | var line []byte 161 | if i > 0 && packet[i-1] == '\r' { 162 | line = packet[:i-1] 163 | } else { 164 | line = packet[:i] 165 | } 166 | var quote bool 167 | var quotech byte 168 | var escape bool 169 | outer: 170 | for { 171 | nline := make([]byte, 0, len(line)) 172 | for i := 0; i < len(line); i++ { 173 | c := line[i] 174 | if !quote { 175 | if c == ' ' { 176 | if len(nline) > 0 { 177 | args = append(args, nline) 178 | } 179 | line = line[i+1:] 180 | continue outer 181 | } 182 | if c == '"' || c == '\'' { 183 | if i != 0 { 184 | return false, args[:0], Telnet, packet, errUnbalancedQuotes 185 | } 186 | quotech = c 187 | quote = true 188 | line = line[i+1:] 189 | continue outer 190 | } 191 | } else { 192 | if escape { 193 | escape = false 194 | switch c { 195 | case 'n': 196 | c = '\n' 197 | case 'r': 198 | c = '\r' 199 | case 't': 200 | c = '\t' 201 | } 202 | } else if c == quotech { 203 | quote = false 204 | quotech = 0 205 | args = append(args, nline) 206 | line = line[i+1:] 207 | if len(line) > 0 && line[0] != ' ' { 208 | return false, args[:0], Telnet, packet, errUnbalancedQuotes 209 | } 210 | continue outer 211 | } else if c == '\\' { 212 | escape = true 213 | continue 214 | } 215 | } 216 | nline = append(nline, c) 217 | } 218 | if quote { 219 | return false, args[:0], Telnet, packet, errUnbalancedQuotes 220 | } 221 | if len(line) > 0 { 222 | args = append(args, line) 223 | } 224 | break 225 | } 226 | return true, args, Telnet, packet[i+1:], nil 227 | } 228 | } 229 | return false, args[:0], Telnet, packet, nil 230 | } 231 | 232 | // appendPrefix will append a "$3\r\n" style redis prefix for a message. 233 | func appendPrefix(b []byte, c byte, n int64) []byte { 234 | if n >= 0 && n <= 9 { 235 | return append(b, c, byte('0'+n), '\r', '\n') 236 | } 237 | b = append(b, c) 238 | b = strconv.AppendInt(b, n, 10) 239 | return append(b, '\r', '\n') 240 | } 241 | 242 | // AppendUint appends a Redis protocol uint64 to the input bytes. 243 | func AppendUint(b []byte, n uint64) []byte { 244 | b = append(b, ':') 245 | b = strconv.AppendUint(b, n, 10) 246 | return append(b, '\r', '\n') 247 | } 248 | 249 | // AppendInt appends a Redis protocol int64 to the input bytes. 250 | func AppendInt(b []byte, n int64) []byte { 251 | return appendPrefix(b, ':', n) 252 | } 253 | 254 | // AppendArray appends a Redis protocol array to the input bytes. 255 | func AppendArray(b []byte, n int) []byte { 256 | return appendPrefix(b, '*', int64(n)) 257 | } 258 | 259 | // AppendBulk appends a Redis protocol bulk byte slice to the input bytes. 260 | func AppendBulk(b []byte, bulk []byte) []byte { 261 | b = appendPrefix(b, '$', int64(len(bulk))) 262 | b = append(b, bulk...) 263 | return append(b, '\r', '\n') 264 | } 265 | 266 | // AppendBulkString appends a Redis protocol bulk string to the input bytes. 267 | func AppendBulkString(b []byte, bulk string) []byte { 268 | b = appendPrefix(b, '$', int64(len(bulk))) 269 | b = append(b, bulk...) 270 | return append(b, '\r', '\n') 271 | } 272 | 273 | // AppendString appends a Redis protocol string to the input bytes. 274 | func AppendString(b []byte, s string) []byte { 275 | b = append(b, '+') 276 | b = append(b, stripNewlines(s)...) 277 | return append(b, '\r', '\n') 278 | } 279 | 280 | // AppendError appends a Redis protocol error to the input bytes. 281 | func AppendError(b []byte, s string) []byte { 282 | b = append(b, '-') 283 | b = append(b, stripNewlines(s)...) 284 | return append(b, '\r', '\n') 285 | } 286 | 287 | // AppendOK appends a Redis protocol OK to the input bytes. 288 | func AppendOK(b []byte) []byte { 289 | return append(b, '+', 'O', 'K', '\r', '\n') 290 | } 291 | func stripNewlines(s string) string { 292 | for i := 0; i < len(s); i++ { 293 | if s[i] == '\r' || s[i] == '\n' { 294 | s = strings.Replace(s, "\r", " ", -1) 295 | s = strings.Replace(s, "\n", " ", -1) 296 | break 297 | } 298 | } 299 | return s 300 | } 301 | 302 | // AppendTile38 appends a Tile38 message to the input bytes. 303 | func AppendTile38(b []byte, data []byte) []byte { 304 | b = append(b, '$') 305 | b = strconv.AppendInt(b, int64(len(data)), 10) 306 | b = append(b, ' ') 307 | b = append(b, data...) 308 | return append(b, '\r', '\n') 309 | } 310 | 311 | // AppendNull appends a Redis protocol null to the input bytes. 312 | func AppendNull(b []byte) []byte { 313 | return append(b, '$', '-', '1', '\r', '\n') 314 | } 315 | -------------------------------------------------------------------------------- /vendor/github.com/go-redis/redis/internal/pool/pool.go: -------------------------------------------------------------------------------- 1 | package pool 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/go-redis/redis/internal" 11 | ) 12 | 13 | var ErrClosed = errors.New("redis: client is closed") 14 | var ErrPoolTimeout = errors.New("redis: connection pool timeout") 15 | 16 | var timers = sync.Pool{ 17 | New: func() interface{} { 18 | t := time.NewTimer(time.Hour) 19 | t.Stop() 20 | return t 21 | }, 22 | } 23 | 24 | // Stats contains pool state information and accumulated stats. 25 | type Stats struct { 26 | Hits uint32 // number of times free connection was found in the pool 27 | Misses uint32 // number of times free connection was NOT found in the pool 28 | Timeouts uint32 // number of times a wait timeout occurred 29 | 30 | TotalConns uint32 // number of total connections in the pool 31 | IdleConns uint32 // number of idle connections in the pool 32 | StaleConns uint32 // number of stale connections removed from the pool 33 | } 34 | 35 | type Pooler interface { 36 | NewConn() (*Conn, error) 37 | CloseConn(*Conn) error 38 | 39 | Get() (*Conn, error) 40 | Put(*Conn) 41 | Remove(*Conn) 42 | 43 | Len() int 44 | IdleLen() int 45 | Stats() *Stats 46 | 47 | Close() error 48 | } 49 | 50 | type Options struct { 51 | Dialer func() (net.Conn, error) 52 | OnClose func(*Conn) error 53 | 54 | PoolSize int 55 | MinIdleConns int 56 | MaxConnAge time.Duration 57 | PoolTimeout time.Duration 58 | IdleTimeout time.Duration 59 | IdleCheckFrequency time.Duration 60 | } 61 | 62 | type ConnPool struct { 63 | opt *Options 64 | 65 | dialErrorsNum uint32 // atomic 66 | 67 | lastDialErrorMu sync.RWMutex 68 | lastDialError error 69 | 70 | queue chan struct{} 71 | 72 | connsMu sync.Mutex 73 | conns []*Conn 74 | idleConns []*Conn 75 | poolSize int 76 | idleConnsLen int 77 | 78 | stats Stats 79 | 80 | _closed uint32 // atomic 81 | } 82 | 83 | var _ Pooler = (*ConnPool)(nil) 84 | 85 | func NewConnPool(opt *Options) *ConnPool { 86 | p := &ConnPool{ 87 | opt: opt, 88 | 89 | queue: make(chan struct{}, opt.PoolSize), 90 | conns: make([]*Conn, 0, opt.PoolSize), 91 | idleConns: make([]*Conn, 0, opt.PoolSize), 92 | } 93 | 94 | for i := 0; i < opt.MinIdleConns; i++ { 95 | p.checkMinIdleConns() 96 | } 97 | 98 | if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 { 99 | go p.reaper(opt.IdleCheckFrequency) 100 | } 101 | 102 | return p 103 | } 104 | 105 | func (p *ConnPool) checkMinIdleConns() { 106 | if p.opt.MinIdleConns == 0 { 107 | return 108 | } 109 | if p.poolSize < p.opt.PoolSize && p.idleConnsLen < p.opt.MinIdleConns { 110 | p.poolSize++ 111 | p.idleConnsLen++ 112 | go p.addIdleConn() 113 | } 114 | } 115 | 116 | func (p *ConnPool) addIdleConn() { 117 | cn, err := p.newConn(true) 118 | if err != nil { 119 | return 120 | } 121 | 122 | p.connsMu.Lock() 123 | p.conns = append(p.conns, cn) 124 | p.idleConns = append(p.idleConns, cn) 125 | p.connsMu.Unlock() 126 | } 127 | 128 | func (p *ConnPool) NewConn() (*Conn, error) { 129 | return p._NewConn(false) 130 | } 131 | 132 | func (p *ConnPool) _NewConn(pooled bool) (*Conn, error) { 133 | cn, err := p.newConn(pooled) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | p.connsMu.Lock() 139 | p.conns = append(p.conns, cn) 140 | if pooled { 141 | if p.poolSize < p.opt.PoolSize { 142 | p.poolSize++ 143 | } else { 144 | cn.pooled = false 145 | } 146 | } 147 | p.connsMu.Unlock() 148 | return cn, nil 149 | } 150 | 151 | func (p *ConnPool) newConn(pooled bool) (*Conn, error) { 152 | if p.closed() { 153 | return nil, ErrClosed 154 | } 155 | 156 | if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) { 157 | return nil, p.getLastDialError() 158 | } 159 | 160 | netConn, err := p.opt.Dialer() 161 | if err != nil { 162 | p.setLastDialError(err) 163 | if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) { 164 | go p.tryDial() 165 | } 166 | return nil, err 167 | } 168 | 169 | cn := NewConn(netConn) 170 | cn.pooled = pooled 171 | return cn, nil 172 | } 173 | 174 | func (p *ConnPool) tryDial() { 175 | for { 176 | if p.closed() { 177 | return 178 | } 179 | 180 | conn, err := p.opt.Dialer() 181 | if err != nil { 182 | p.setLastDialError(err) 183 | time.Sleep(time.Second) 184 | continue 185 | } 186 | 187 | atomic.StoreUint32(&p.dialErrorsNum, 0) 188 | _ = conn.Close() 189 | return 190 | } 191 | } 192 | 193 | func (p *ConnPool) setLastDialError(err error) { 194 | p.lastDialErrorMu.Lock() 195 | p.lastDialError = err 196 | p.lastDialErrorMu.Unlock() 197 | } 198 | 199 | func (p *ConnPool) getLastDialError() error { 200 | p.lastDialErrorMu.RLock() 201 | err := p.lastDialError 202 | p.lastDialErrorMu.RUnlock() 203 | return err 204 | } 205 | 206 | // Get returns existed connection from the pool or creates a new one. 207 | func (p *ConnPool) Get() (*Conn, error) { 208 | if p.closed() { 209 | return nil, ErrClosed 210 | } 211 | 212 | err := p.waitTurn() 213 | if err != nil { 214 | return nil, err 215 | } 216 | 217 | for { 218 | p.connsMu.Lock() 219 | cn := p.popIdle() 220 | p.connsMu.Unlock() 221 | 222 | if cn == nil { 223 | break 224 | } 225 | 226 | if p.isStaleConn(cn) { 227 | _ = p.CloseConn(cn) 228 | continue 229 | } 230 | 231 | atomic.AddUint32(&p.stats.Hits, 1) 232 | return cn, nil 233 | } 234 | 235 | atomic.AddUint32(&p.stats.Misses, 1) 236 | 237 | newcn, err := p._NewConn(true) 238 | if err != nil { 239 | p.freeTurn() 240 | return nil, err 241 | } 242 | 243 | return newcn, nil 244 | } 245 | 246 | func (p *ConnPool) getTurn() { 247 | p.queue <- struct{}{} 248 | } 249 | 250 | func (p *ConnPool) waitTurn() error { 251 | select { 252 | case p.queue <- struct{}{}: 253 | return nil 254 | default: 255 | timer := timers.Get().(*time.Timer) 256 | timer.Reset(p.opt.PoolTimeout) 257 | 258 | select { 259 | case p.queue <- struct{}{}: 260 | if !timer.Stop() { 261 | <-timer.C 262 | } 263 | timers.Put(timer) 264 | return nil 265 | case <-timer.C: 266 | timers.Put(timer) 267 | atomic.AddUint32(&p.stats.Timeouts, 1) 268 | return ErrPoolTimeout 269 | } 270 | } 271 | } 272 | 273 | func (p *ConnPool) freeTurn() { 274 | <-p.queue 275 | } 276 | 277 | func (p *ConnPool) popIdle() *Conn { 278 | if len(p.idleConns) == 0 { 279 | return nil 280 | } 281 | 282 | idx := len(p.idleConns) - 1 283 | cn := p.idleConns[idx] 284 | p.idleConns = p.idleConns[:idx] 285 | p.idleConnsLen-- 286 | p.checkMinIdleConns() 287 | return cn 288 | } 289 | 290 | func (p *ConnPool) Put(cn *Conn) { 291 | if !cn.pooled { 292 | p.Remove(cn) 293 | return 294 | } 295 | 296 | p.connsMu.Lock() 297 | p.idleConns = append(p.idleConns, cn) 298 | p.idleConnsLen++ 299 | p.connsMu.Unlock() 300 | p.freeTurn() 301 | } 302 | 303 | func (p *ConnPool) Remove(cn *Conn) { 304 | p.removeConn(cn) 305 | p.freeTurn() 306 | _ = p.closeConn(cn) 307 | } 308 | 309 | func (p *ConnPool) CloseConn(cn *Conn) error { 310 | p.removeConn(cn) 311 | return p.closeConn(cn) 312 | } 313 | 314 | func (p *ConnPool) removeConn(cn *Conn) { 315 | p.connsMu.Lock() 316 | for i, c := range p.conns { 317 | if c == cn { 318 | p.conns = append(p.conns[:i], p.conns[i+1:]...) 319 | if cn.pooled { 320 | p.poolSize-- 321 | p.checkMinIdleConns() 322 | } 323 | break 324 | } 325 | } 326 | p.connsMu.Unlock() 327 | } 328 | 329 | func (p *ConnPool) closeConn(cn *Conn) error { 330 | if p.opt.OnClose != nil { 331 | _ = p.opt.OnClose(cn) 332 | } 333 | return cn.Close() 334 | } 335 | 336 | // Len returns total number of connections. 337 | func (p *ConnPool) Len() int { 338 | p.connsMu.Lock() 339 | n := len(p.conns) 340 | p.connsMu.Unlock() 341 | return n 342 | } 343 | 344 | // IdleLen returns number of idle connections. 345 | func (p *ConnPool) IdleLen() int { 346 | p.connsMu.Lock() 347 | n := p.idleConnsLen 348 | p.connsMu.Unlock() 349 | return n 350 | } 351 | 352 | func (p *ConnPool) Stats() *Stats { 353 | idleLen := p.IdleLen() 354 | return &Stats{ 355 | Hits: atomic.LoadUint32(&p.stats.Hits), 356 | Misses: atomic.LoadUint32(&p.stats.Misses), 357 | Timeouts: atomic.LoadUint32(&p.stats.Timeouts), 358 | 359 | TotalConns: uint32(p.Len()), 360 | IdleConns: uint32(idleLen), 361 | StaleConns: atomic.LoadUint32(&p.stats.StaleConns), 362 | } 363 | } 364 | 365 | func (p *ConnPool) closed() bool { 366 | return atomic.LoadUint32(&p._closed) == 1 367 | } 368 | 369 | func (p *ConnPool) Filter(fn func(*Conn) bool) error { 370 | var firstErr error 371 | p.connsMu.Lock() 372 | for _, cn := range p.conns { 373 | if fn(cn) { 374 | if err := p.closeConn(cn); err != nil && firstErr == nil { 375 | firstErr = err 376 | } 377 | } 378 | } 379 | p.connsMu.Unlock() 380 | return firstErr 381 | } 382 | 383 | func (p *ConnPool) Close() error { 384 | if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) { 385 | return ErrClosed 386 | } 387 | 388 | var firstErr error 389 | p.connsMu.Lock() 390 | for _, cn := range p.conns { 391 | if err := p.closeConn(cn); err != nil && firstErr == nil { 392 | firstErr = err 393 | } 394 | } 395 | p.conns = nil 396 | p.poolSize = 0 397 | p.idleConns = nil 398 | p.idleConnsLen = 0 399 | p.connsMu.Unlock() 400 | 401 | return firstErr 402 | } 403 | 404 | func (p *ConnPool) reapStaleConn() *Conn { 405 | if len(p.idleConns) == 0 { 406 | return nil 407 | } 408 | 409 | cn := p.idleConns[0] 410 | if !p.isStaleConn(cn) { 411 | return nil 412 | } 413 | 414 | p.idleConns = append(p.idleConns[:0], p.idleConns[1:]...) 415 | p.idleConnsLen-- 416 | 417 | return cn 418 | } 419 | 420 | func (p *ConnPool) ReapStaleConns() (int, error) { 421 | var n int 422 | for { 423 | p.getTurn() 424 | 425 | p.connsMu.Lock() 426 | cn := p.reapStaleConn() 427 | p.connsMu.Unlock() 428 | 429 | if cn != nil { 430 | p.removeConn(cn) 431 | } 432 | 433 | p.freeTurn() 434 | 435 | if cn != nil { 436 | p.closeConn(cn) 437 | n++ 438 | } else { 439 | break 440 | } 441 | } 442 | return n, nil 443 | } 444 | 445 | func (p *ConnPool) reaper(frequency time.Duration) { 446 | ticker := time.NewTicker(frequency) 447 | defer ticker.Stop() 448 | 449 | for range ticker.C { 450 | if p.closed() { 451 | break 452 | } 453 | n, err := p.ReapStaleConns() 454 | if err != nil { 455 | internal.Logf("ReapStaleConns failed: %s", err) 456 | continue 457 | } 458 | atomic.AddUint32(&p.stats.StaleConns, uint32(n)) 459 | } 460 | } 461 | 462 | func (p *ConnPool) isStaleConn(cn *Conn) bool { 463 | if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 { 464 | return false 465 | } 466 | 467 | now := time.Now() 468 | if p.opt.IdleTimeout > 0 && now.Sub(cn.UsedAt()) >= p.opt.IdleTimeout { 469 | return true 470 | } 471 | if p.opt.MaxConnAge > 0 && now.Sub(cn.InitedAt) >= p.opt.MaxConnAge { 472 | return true 473 | } 474 | 475 | return false 476 | } 477 | --------------------------------------------------------------------------------