add redis
This commit is contained in:
parent
4fd4c7018e
commit
6419162471
7
main.go
7
main.go
@ -4,6 +4,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.trj.tw/golang/go-gallery/models"
|
"git.trj.tw/golang/go-gallery/models"
|
||||||
|
"git.trj.tw/golang/go-gallery/modules/memstore"
|
||||||
"git.trj.tw/golang/go-gallery/routers/routes"
|
"git.trj.tw/golang/go-gallery/routers/routes"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -11,8 +12,12 @@ import (
|
|||||||
var server *gin.Engine
|
var server *gin.Engine
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
err := memstore.InitClient()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
server = routes.NewServ()
|
server = routes.NewServ()
|
||||||
_, err := models.NewDB()
|
_, err = models.NewDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,13 @@ import (
|
|||||||
|
|
||||||
// Account - Account table struct
|
// Account - Account table struct
|
||||||
type Account struct {
|
type Account struct {
|
||||||
ID string `xorm:"id"`
|
ID string `xorm:"id" cc:"id"`
|
||||||
Account string `xorm:"account"`
|
Account string `xorm:"account" cc:"account"`
|
||||||
Password string `xorm:"password"`
|
Password string `xorm:"password" cc:"-"`
|
||||||
Nick string `xorm:"nick"`
|
Nick string `xorm:"nick" cc:"nick"`
|
||||||
Email string `xorm:"email"`
|
Email string `xorm:"email" cc:"email"`
|
||||||
Ctime time.Time `xorm:"ctime"`
|
Ctime time.Time `xorm:"ctime" cc:"ctime"`
|
||||||
Mtime time.Time `xorm:"mtime"`
|
Mtime time.Time `xorm:"mtime" cc:"mtime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllAccount - all account
|
// GetAllAccount - all account
|
||||||
|
@ -3,6 +3,7 @@ package context
|
|||||||
import (
|
import (
|
||||||
"git.trj.tw/golang/go-gallery/modules/apimsg"
|
"git.trj.tw/golang/go-gallery/modules/apimsg"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Context custom patch context
|
// Context custom patch context
|
||||||
@ -15,16 +16,22 @@ type Context struct {
|
|||||||
type CustomMiddle func(*Context)
|
type CustomMiddle func(*Context)
|
||||||
|
|
||||||
// PatchContext func
|
// PatchContext func
|
||||||
func PatchContext(handler CustomMiddle) gin.HandlerFunc {
|
func PatchContext(handler func(*Context)) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
ctx := &Context{
|
ctx := &Context{
|
||||||
Context: c,
|
Context: c,
|
||||||
|
C: make(map[string]interface{}),
|
||||||
}
|
}
|
||||||
ctx.C = make(map[string]interface{})
|
|
||||||
handler(ctx)
|
handler(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindData -
|
||||||
|
func (c *Context) BindData(i interface{}) error {
|
||||||
|
b := binding.Default(c.Request.Method, c.ContentType())
|
||||||
|
return c.ShouldBindWith(i, b)
|
||||||
|
}
|
||||||
|
|
||||||
// NotFound response
|
// NotFound response
|
||||||
func (c *Context) NotFound(msg interface{}) {
|
func (c *Context) NotFound(msg interface{}) {
|
||||||
obj := apimsg.GetRes("NotFound", msg)
|
obj := apimsg.GetRes("NotFound", msg)
|
||||||
|
66
modules/memstore/redis.go
Normal file
66
modules/memstore/redis.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package memstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
var redisStore *redis.Client
|
||||||
|
|
||||||
|
// InitClient -
|
||||||
|
func InitClient() error {
|
||||||
|
redisStore = redis.NewClient(&redis.Options{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
Password: "",
|
||||||
|
DB: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := redisStore.Ping().Result()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisSet -
|
||||||
|
func RedisSet(namespace, key string, val string, expire int) error {
|
||||||
|
var t time.Duration
|
||||||
|
if expire > 0 {
|
||||||
|
t = time.Second * time.Duration(expire)
|
||||||
|
}
|
||||||
|
var n string
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
n += namespace
|
||||||
|
n += ":"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(key) == 0 {
|
||||||
|
return errors.New("key empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
n += key
|
||||||
|
|
||||||
|
err := redisStore.Set(n, val, t).Err()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisGet -
|
||||||
|
func RedisGet(namespace, key string) (string, error) {
|
||||||
|
var n string
|
||||||
|
if len(namespace) > 0 {
|
||||||
|
n += namespace
|
||||||
|
n += ":"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(key) == 0 {
|
||||||
|
return "", errors.New("key empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(n) > 0 {
|
||||||
|
n += ":"
|
||||||
|
}
|
||||||
|
|
||||||
|
n += key
|
||||||
|
|
||||||
|
val, err := redisStore.Get(key).Result()
|
||||||
|
return val, err
|
||||||
|
}
|
@ -1,16 +1,28 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "reflect"
|
import (
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
// ToMap struct to map[string]interface{}
|
// ToMap struct to map[string]interface{}
|
||||||
func ToMap(ss interface{}) map[string]interface{} {
|
func ToMap(ss interface{}) map[string]interface{} {
|
||||||
t := reflect.ValueOf(ss).Elem()
|
t := reflect.ValueOf(ss).Elem()
|
||||||
|
|
||||||
smap := make(map[string]interface{})
|
smap := make(map[string]interface{})
|
||||||
|
mtag := regexp.MustCompile(`cc:\"(.+)\"`)
|
||||||
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
f := t.Field(i)
|
f := t.Field(i)
|
||||||
smap[t.Type().Field(i).Name] = f.Interface()
|
tag := string(t.Type().Field(i).Tag)
|
||||||
|
str := mtag.FindStringSubmatch(tag)
|
||||||
|
name := t.Type().Field(i).Name
|
||||||
|
if len(str) > 1 {
|
||||||
|
name = str[1]
|
||||||
|
}
|
||||||
|
if name != "-" {
|
||||||
|
smap[name] = f.Interface()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return smap
|
return smap
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
package account
|
package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.trj.tw/golang/go-gallery/modules/memstore"
|
||||||
|
|
||||||
"git.trj.tw/golang/go-gallery/models"
|
"git.trj.tw/golang/go-gallery/models"
|
||||||
"git.trj.tw/golang/go-gallery/modules/context"
|
"git.trj.tw/golang/go-gallery/modules/context"
|
||||||
"git.trj.tw/golang/go-gallery/modules/utils"
|
"git.trj.tw/golang/go-gallery/modules/utils"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserLogin route
|
// UserLogin route
|
||||||
@ -17,7 +26,7 @@ func UserLogin(c *context.Context) {
|
|||||||
Account: "",
|
Account: "",
|
||||||
Password: "",
|
Password: "",
|
||||||
}
|
}
|
||||||
err := c.ShouldBind(&loginArg)
|
err := c.BindData(&loginArg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.DataFormat(nil)
|
c.DataFormat(nil)
|
||||||
return
|
return
|
||||||
@ -35,5 +44,54 @@ func UserLogin(c *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Success(utils.ToMap(acc))
|
strs := strings.Split(acc.Password, ".")
|
||||||
|
if len(strs) != 2 {
|
||||||
|
c.ServerError("store pass format error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b, err := hex.DecodeString(strs[0])
|
||||||
|
if err != nil {
|
||||||
|
c.ServerError(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hashPass, err := hex.DecodeString(strs[1])
|
||||||
|
if err != nil {
|
||||||
|
c.ServerError(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := pbkdf2.Key([]byte(loginArg.Password), b, 2048, 64, sha512.New)
|
||||||
|
|
||||||
|
if enc == nil || !reflect.DeepEqual(enc, hashPass) {
|
||||||
|
c.DataFormat("password error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := utils.ToMap(acc)
|
||||||
|
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
|
||||||
|
m["user"] = res
|
||||||
|
|
||||||
|
jsonStr, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
c.ServerError(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tByte := make([]byte, 20)
|
||||||
|
_, err = rand.Read(tByte)
|
||||||
|
if err != nil {
|
||||||
|
c.ServerError(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = memstore.RedisSet("golang", hex.EncodeToString(tByte), string(jsonStr), 600)
|
||||||
|
if err != nil {
|
||||||
|
c.ServerError(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
m["token"] = hex.EncodeToString(tByte)
|
||||||
|
|
||||||
|
c.Success(m)
|
||||||
}
|
}
|
||||||
|
25
vendor/github.com/go-redis/redis/LICENSE
generated
vendored
Normal file
25
vendor/github.com/go-redis/redis/LICENSE
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2013 The github.com/go-redis/redis Authors.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
20
vendor/github.com/go-redis/redis/Makefile
generated
vendored
Normal file
20
vendor/github.com/go-redis/redis/Makefile
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
all: testdeps
|
||||||
|
go test ./...
|
||||||
|
go test ./... -short -race
|
||||||
|
env GOOS=linux GOARCH=386 go test ./...
|
||||||
|
go vet
|
||||||
|
|
||||||
|
testdeps: testdata/redis/src/redis-server
|
||||||
|
|
||||||
|
bench: testdeps
|
||||||
|
go test ./... -test.run=NONE -test.bench=. -test.benchmem
|
||||||
|
|
||||||
|
.PHONY: all test testdeps bench
|
||||||
|
|
||||||
|
testdata/redis:
|
||||||
|
mkdir -p $@
|
||||||
|
wget -qO- https://github.com/antirez/redis/archive/unstable.tar.gz | tar xvz --strip-components=1 -C $@
|
||||||
|
|
||||||
|
testdata/redis/src/redis-server: testdata/redis
|
||||||
|
sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $</src/Makefile
|
||||||
|
cd $< && make all
|
143
vendor/github.com/go-redis/redis/README.md
generated
vendored
Normal file
143
vendor/github.com/go-redis/redis/README.md
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# Redis client for Golang
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/go-redis/redis.png?branch=master)](https://travis-ci.org/go-redis/redis)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/go-redis/redis?status.svg)](https://godoc.org/github.com/go-redis/redis)
|
||||||
|
[![Airbrake](https://img.shields.io/badge/kudos-airbrake.io-orange.svg)](https://airbrake.io)
|
||||||
|
|
||||||
|
Supports:
|
||||||
|
|
||||||
|
- Redis 3 commands except QUIT, MONITOR, SLOWLOG and SYNC.
|
||||||
|
- Automatic connection pooling with [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support.
|
||||||
|
- [Pub/Sub](https://godoc.org/github.com/go-redis/redis#PubSub).
|
||||||
|
- [Transactions](https://godoc.org/github.com/go-redis/redis#Multi).
|
||||||
|
- [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).
|
||||||
|
- [Scripting](https://godoc.org/github.com/go-redis/redis#Script).
|
||||||
|
- [Timeouts](https://godoc.org/github.com/go-redis/redis#Options).
|
||||||
|
- [Redis Sentinel](https://godoc.org/github.com/go-redis/redis#NewFailoverClient).
|
||||||
|
- [Redis Cluster](https://godoc.org/github.com/go-redis/redis#NewClusterClient).
|
||||||
|
- [Ring](https://godoc.org/github.com/go-redis/redis#NewRing).
|
||||||
|
- [Instrumentation](https://godoc.org/github.com/go-redis/redis#ex-package--Instrumentation).
|
||||||
|
- [Cache friendly](https://github.com/go-redis/cache).
|
||||||
|
- [Rate limiting](https://github.com/go-redis/redis_rate).
|
||||||
|
- [Distributed Locks](https://github.com/bsm/redis-lock).
|
||||||
|
|
||||||
|
API docs: https://godoc.org/github.com/go-redis/redis.
|
||||||
|
Examples: https://godoc.org/github.com/go-redis/redis#pkg-examples.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Install:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get -u github.com/go-redis/redis
|
||||||
|
```
|
||||||
|
|
||||||
|
Import:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/go-redis/redis"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ExampleNewClient() {
|
||||||
|
client := redis.NewClient(&redis.Options{
|
||||||
|
Addr: "localhost:6379",
|
||||||
|
Password: "", // no password set
|
||||||
|
DB: 0, // use default DB
|
||||||
|
})
|
||||||
|
|
||||||
|
pong, err := client.Ping().Result()
|
||||||
|
fmt.Println(pong, err)
|
||||||
|
// Output: PONG <nil>
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleClient() {
|
||||||
|
err := client.Set("key", "value", 0).Err()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := client.Get("key").Result()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("key", val)
|
||||||
|
|
||||||
|
val2, err := client.Get("key2").Result()
|
||||||
|
if err == redis.Nil {
|
||||||
|
fmt.Println("key2 does not exist")
|
||||||
|
} else if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("key2", val2)
|
||||||
|
}
|
||||||
|
// Output: key value
|
||||||
|
// key2 does not exist
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Howto
|
||||||
|
|
||||||
|
Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package.
|
||||||
|
|
||||||
|
## Look and feel
|
||||||
|
|
||||||
|
Some corner cases:
|
||||||
|
|
||||||
|
SET key value EX 10 NX
|
||||||
|
set, err := client.SetNX("key", "value", 10*time.Second).Result()
|
||||||
|
|
||||||
|
SORT list LIMIT 0 2 ASC
|
||||||
|
vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()
|
||||||
|
|
||||||
|
ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
|
||||||
|
vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{
|
||||||
|
Min: "-inf",
|
||||||
|
Max: "+inf",
|
||||||
|
Offset: 0,
|
||||||
|
Count: 2,
|
||||||
|
}).Result()
|
||||||
|
|
||||||
|
ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
|
||||||
|
vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result()
|
||||||
|
|
||||||
|
EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
|
||||||
|
vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result()
|
||||||
|
|
||||||
|
## Benchmark
|
||||||
|
|
||||||
|
go-redis vs redigo:
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op
|
||||||
|
BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op
|
||||||
|
BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op
|
||||||
|
BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op
|
||||||
|
BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op
|
||||||
|
BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op
|
||||||
|
BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op
|
||||||
|
BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op
|
||||||
|
BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op
|
||||||
|
BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op
|
||||||
|
BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op
|
||||||
|
BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op
|
||||||
|
BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op
|
||||||
|
BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op
|
||||||
|
BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op
|
||||||
|
BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
Redis Cluster:
|
||||||
|
|
||||||
|
```
|
||||||
|
BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op
|
||||||
|
BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [Golang PostgreSQL ORM](https://github.com/go-pg/pg)
|
||||||
|
- [Golang msgpack](https://github.com/vmihailenco/msgpack)
|
||||||
|
- [Golang message task queue](https://github.com/go-msgqueue/msgqueue)
|
1439
vendor/github.com/go-redis/redis/cluster.go
generated
vendored
Normal file
1439
vendor/github.com/go-redis/redis/cluster.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
vendor/github.com/go-redis/redis/cluster_commands.go
generated
vendored
Normal file
22
vendor/github.com/go-redis/redis/cluster_commands.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
|
func (c *ClusterClient) DBSize() *IntCmd {
|
||||||
|
cmd := NewIntCmd("dbsize")
|
||||||
|
var size int64
|
||||||
|
err := c.ForEachMaster(func(master *Client) error {
|
||||||
|
n, err := master.DBSize().Result()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
atomic.AddInt64(&size, n)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
cmd.setErr(err)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
cmd.val = size
|
||||||
|
return cmd
|
||||||
|
}
|
1048
vendor/github.com/go-redis/redis/command.go
generated
vendored
Normal file
1048
vendor/github.com/go-redis/redis/command.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2175
vendor/github.com/go-redis/redis/commands.go
generated
vendored
Normal file
2175
vendor/github.com/go-redis/redis/commands.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4
vendor/github.com/go-redis/redis/doc.go
generated
vendored
Normal file
4
vendor/github.com/go-redis/redis/doc.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Package redis implements a Redis client.
|
||||||
|
*/
|
||||||
|
package redis
|
81
vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go
generated
vendored
Normal file
81
vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package consistenthash provides an implementation of a ring hash.
|
||||||
|
package consistenthash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/crc32"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hash func(data []byte) uint32
|
||||||
|
|
||||||
|
type Map struct {
|
||||||
|
hash Hash
|
||||||
|
replicas int
|
||||||
|
keys []int // Sorted
|
||||||
|
hashMap map[int]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(replicas int, fn Hash) *Map {
|
||||||
|
m := &Map{
|
||||||
|
replicas: replicas,
|
||||||
|
hash: fn,
|
||||||
|
hashMap: make(map[int]string),
|
||||||
|
}
|
||||||
|
if m.hash == nil {
|
||||||
|
m.hash = crc32.ChecksumIEEE
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if there are no items available.
|
||||||
|
func (m *Map) IsEmpty() bool {
|
||||||
|
return len(m.keys) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds some keys to the hash.
|
||||||
|
func (m *Map) Add(keys ...string) {
|
||||||
|
for _, key := range keys {
|
||||||
|
for i := 0; i < m.replicas; i++ {
|
||||||
|
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
|
||||||
|
m.keys = append(m.keys, hash)
|
||||||
|
m.hashMap[hash] = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Ints(m.keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the closest item in the hash to the provided key.
|
||||||
|
func (m *Map) Get(key string) string {
|
||||||
|
if m.IsEmpty() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := int(m.hash([]byte(key)))
|
||||||
|
|
||||||
|
// Binary search for appropriate replica.
|
||||||
|
idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash })
|
||||||
|
|
||||||
|
// Means we have cycled back to the first replica.
|
||||||
|
if idx == len(m.keys) {
|
||||||
|
idx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.hashMap[m.keys[idx]]
|
||||||
|
}
|
84
vendor/github.com/go-redis/redis/internal/error.go
generated
vendored
Normal file
84
vendor/github.com/go-redis/redis/internal/error.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsRetryableError(err error, retryNetError bool) bool {
|
||||||
|
if IsNetworkError(err) {
|
||||||
|
return retryNetError
|
||||||
|
}
|
||||||
|
s := err.Error()
|
||||||
|
if s == "ERR max number of clients reached" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "LOADING ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRedisError(err error) bool {
|
||||||
|
_, ok := err.(proto.RedisError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsNetworkError(err error) bool {
|
||||||
|
if err == io.EOF {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, ok := err.(net.Error)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsReadOnlyError(err error) bool {
|
||||||
|
return strings.HasPrefix(err.Error(), "READONLY ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBadConn(err error, allowTimeout bool) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if IsRedisError(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if allowTimeout {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMovedError(err error) (moved bool, ask bool, addr string) {
|
||||||
|
if !IsRedisError(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := err.Error()
|
||||||
|
if strings.HasPrefix(s, "MOVED ") {
|
||||||
|
moved = true
|
||||||
|
} else if strings.HasPrefix(s, "ASK ") {
|
||||||
|
ask = true
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ind := strings.LastIndex(s, " ")
|
||||||
|
if ind == -1 {
|
||||||
|
return false, false, ""
|
||||||
|
}
|
||||||
|
addr = s[ind+1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsLoadingError(err error) bool {
|
||||||
|
return strings.HasPrefix(err.Error(), "LOADING ")
|
||||||
|
}
|
77
vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go
generated
vendored
Normal file
77
vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package hashtag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SlotNumber = 16384
|
||||||
|
|
||||||
|
// CRC16 implementation according to CCITT standards.
|
||||||
|
// Copyright 2001-2010 Georges Menie (www.menie.org)
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c
|
||||||
|
var crc16tab = [256]uint16{
|
||||||
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
||||||
|
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
||||||
|
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
||||||
|
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
||||||
|
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
|
||||||
|
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
||||||
|
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
|
||||||
|
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
||||||
|
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||||
|
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
||||||
|
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
|
||||||
|
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
||||||
|
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
|
||||||
|
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
||||||
|
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
||||||
|
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
||||||
|
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
|
||||||
|
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||||
|
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
|
||||||
|
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||||
|
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
||||||
|
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||||
|
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
|
||||||
|
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||||
|
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
|
||||||
|
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
||||||
|
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
||||||
|
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
||||||
|
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
|
||||||
|
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
||||||
|
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
|
||||||
|
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Key(key string) string {
|
||||||
|
if s := strings.IndexByte(key, '{'); s > -1 {
|
||||||
|
if e := strings.IndexByte(key[s+1:], '}'); e > 0 {
|
||||||
|
return key[s+1 : s+e+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandomSlot() int {
|
||||||
|
return rand.Intn(SlotNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashSlot returns a consistent slot number between 0 and 16383
|
||||||
|
// for any given string key.
|
||||||
|
func Slot(key string) int {
|
||||||
|
if key == "" {
|
||||||
|
return RandomSlot()
|
||||||
|
}
|
||||||
|
key = Key(key)
|
||||||
|
return int(crc16sum(key)) % SlotNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
func crc16sum(key string) (crc uint16) {
|
||||||
|
for i := 0; i < len(key); i++ {
|
||||||
|
crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
24
vendor/github.com/go-redis/redis/internal/internal.go
generated
vendored
Normal file
24
vendor/github.com/go-redis/redis/internal/internal.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Retry backoff with jitter sleep to prevent overloaded conditions during intervals
|
||||||
|
// https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||||
|
func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration {
|
||||||
|
if retry < 0 {
|
||||||
|
retry = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
backoff := minBackoff << uint(retry)
|
||||||
|
if backoff > maxBackoff || backoff < minBackoff {
|
||||||
|
backoff = maxBackoff
|
||||||
|
}
|
||||||
|
|
||||||
|
if backoff == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return time.Duration(rand.Int63n(int64(backoff)))
|
||||||
|
}
|
15
vendor/github.com/go-redis/redis/internal/log.go
generated
vendored
Normal file
15
vendor/github.com/go-redis/redis/internal/log.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Logger *log.Logger
|
||||||
|
|
||||||
|
func Logf(s string, args ...interface{}) {
|
||||||
|
if Logger == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Logger.Output(2, fmt.Sprintf(s, args...))
|
||||||
|
}
|
60
vendor/github.com/go-redis/redis/internal/once.go
generated
vendored
Normal file
60
vendor/github.com/go-redis/redis/internal/once.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Camlistore Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Once will perform a successful action exactly once.
|
||||||
|
//
|
||||||
|
// Unlike a sync.Once, this Once's func returns an error
|
||||||
|
// and is re-armed on failure.
|
||||||
|
type Once struct {
|
||||||
|
m sync.Mutex
|
||||||
|
done uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do calls the function f if and only if Do has not been invoked
|
||||||
|
// without error for this instance of Once. In other words, given
|
||||||
|
// var once Once
|
||||||
|
// if once.Do(f) is called multiple times, only the first call will
|
||||||
|
// invoke f, even if f has a different value in each invocation unless
|
||||||
|
// f returns an error. A new instance of Once is required for each
|
||||||
|
// function to execute.
|
||||||
|
//
|
||||||
|
// Do is intended for initialization that must be run exactly once. Since f
|
||||||
|
// is niladic, it may be necessary to use a function literal to capture the
|
||||||
|
// arguments to a function to be invoked by Do:
|
||||||
|
// err := config.once.Do(func() error { return config.init(filename) })
|
||||||
|
func (o *Once) Do(f func() error) error {
|
||||||
|
if atomic.LoadUint32(&o.done) == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Slow-path.
|
||||||
|
o.m.Lock()
|
||||||
|
defer o.m.Unlock()
|
||||||
|
var err error
|
||||||
|
if o.done == 0 {
|
||||||
|
err = f()
|
||||||
|
if err == nil {
|
||||||
|
atomic.StoreUint32(&o.done, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
78
vendor/github.com/go-redis/redis/internal/pool/conn.go
generated
vendored
Normal file
78
vendor/github.com/go-redis/redis/internal/pool/conn.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var noDeadline = time.Time{}
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
netConn net.Conn
|
||||||
|
|
||||||
|
Rd *proto.Reader
|
||||||
|
Wb *proto.WriteBuffer
|
||||||
|
|
||||||
|
Inited bool
|
||||||
|
usedAt atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(netConn net.Conn) *Conn {
|
||||||
|
cn := &Conn{
|
||||||
|
netConn: netConn,
|
||||||
|
Wb: proto.NewWriteBuffer(),
|
||||||
|
}
|
||||||
|
cn.Rd = proto.NewReader(cn.netConn)
|
||||||
|
cn.SetUsedAt(time.Now())
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) UsedAt() time.Time {
|
||||||
|
return cn.usedAt.Load().(time.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetUsedAt(tm time.Time) {
|
||||||
|
cn.usedAt.Store(tm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetNetConn(netConn net.Conn) {
|
||||||
|
cn.netConn = netConn
|
||||||
|
cn.Rd.Reset(netConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) IsStale(timeout time.Duration) bool {
|
||||||
|
return timeout > 0 && time.Since(cn.UsedAt()) > timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetReadTimeout(timeout time.Duration) error {
|
||||||
|
now := time.Now()
|
||||||
|
cn.SetUsedAt(now)
|
||||||
|
if timeout > 0 {
|
||||||
|
return cn.netConn.SetReadDeadline(now.Add(timeout))
|
||||||
|
}
|
||||||
|
return cn.netConn.SetReadDeadline(noDeadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) SetWriteTimeout(timeout time.Duration) error {
|
||||||
|
now := time.Now()
|
||||||
|
cn.SetUsedAt(now)
|
||||||
|
if timeout > 0 {
|
||||||
|
return cn.netConn.SetWriteDeadline(now.Add(timeout))
|
||||||
|
}
|
||||||
|
return cn.netConn.SetWriteDeadline(noDeadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) Write(b []byte) (int, error) {
|
||||||
|
return cn.netConn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) RemoteAddr() net.Addr {
|
||||||
|
return cn.netConn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cn *Conn) Close() error {
|
||||||
|
return cn.netConn.Close()
|
||||||
|
}
|
377
vendor/github.com/go-redis/redis/internal/pool/pool.go
generated
vendored
Normal file
377
vendor/github.com/go-redis/redis/internal/pool/pool.go
generated
vendored
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrClosed = errors.New("redis: client is closed")
|
||||||
|
var ErrPoolTimeout = errors.New("redis: connection pool timeout")
|
||||||
|
|
||||||
|
var timers = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
t := time.NewTimer(time.Hour)
|
||||||
|
t.Stop()
|
||||||
|
return t
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats contains pool state information and accumulated stats.
|
||||||
|
type Stats struct {
|
||||||
|
Hits uint32 // number of times free connection was found in the pool
|
||||||
|
Misses uint32 // number of times free connection was NOT found in the pool
|
||||||
|
Timeouts uint32 // number of times a wait timeout occurred
|
||||||
|
|
||||||
|
TotalConns uint32 // number of total connections in the pool
|
||||||
|
FreeConns uint32 // number of free connections in the pool
|
||||||
|
StaleConns uint32 // number of stale connections removed from the pool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pooler interface {
|
||||||
|
NewConn() (*Conn, error)
|
||||||
|
CloseConn(*Conn) error
|
||||||
|
|
||||||
|
Get() (*Conn, bool, error)
|
||||||
|
Put(*Conn) error
|
||||||
|
Remove(*Conn) error
|
||||||
|
|
||||||
|
Len() int
|
||||||
|
FreeLen() int
|
||||||
|
Stats() *Stats
|
||||||
|
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Dialer func() (net.Conn, error)
|
||||||
|
OnClose func(*Conn) error
|
||||||
|
|
||||||
|
PoolSize int
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnPool struct {
|
||||||
|
opt *Options
|
||||||
|
|
||||||
|
dialErrorsNum uint32 // atomic
|
||||||
|
|
||||||
|
lastDialError error
|
||||||
|
lastDialErrorMu sync.RWMutex
|
||||||
|
|
||||||
|
queue chan struct{}
|
||||||
|
|
||||||
|
connsMu sync.Mutex
|
||||||
|
conns []*Conn
|
||||||
|
|
||||||
|
freeConnsMu sync.Mutex
|
||||||
|
freeConns []*Conn
|
||||||
|
|
||||||
|
stats Stats
|
||||||
|
|
||||||
|
_closed uint32 // atomic
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Pooler = (*ConnPool)(nil)
|
||||||
|
|
||||||
|
func NewConnPool(opt *Options) *ConnPool {
|
||||||
|
p := &ConnPool{
|
||||||
|
opt: opt,
|
||||||
|
|
||||||
|
queue: make(chan struct{}, opt.PoolSize),
|
||||||
|
conns: make([]*Conn, 0, opt.PoolSize),
|
||||||
|
freeConns: make([]*Conn, 0, opt.PoolSize),
|
||||||
|
}
|
||||||
|
if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
|
||||||
|
go p.reaper(opt.IdleCheckFrequency)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) NewConn() (*Conn, error) {
|
||||||
|
if p.closed() {
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) {
|
||||||
|
return nil, p.getLastDialError()
|
||||||
|
}
|
||||||
|
|
||||||
|
netConn, err := p.opt.Dialer()
|
||||||
|
if err != nil {
|
||||||
|
p.setLastDialError(err)
|
||||||
|
if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) {
|
||||||
|
go p.tryDial()
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := NewConn(netConn)
|
||||||
|
p.connsMu.Lock()
|
||||||
|
p.conns = append(p.conns, cn)
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) tryDial() {
|
||||||
|
for {
|
||||||
|
if p.closed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := p.opt.Dialer()
|
||||||
|
if err != nil {
|
||||||
|
p.setLastDialError(err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.StoreUint32(&p.dialErrorsNum, 0)
|
||||||
|
_ = conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) setLastDialError(err error) {
|
||||||
|
p.lastDialErrorMu.Lock()
|
||||||
|
p.lastDialError = err
|
||||||
|
p.lastDialErrorMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) getLastDialError() error {
|
||||||
|
p.lastDialErrorMu.RLock()
|
||||||
|
err := p.lastDialError
|
||||||
|
p.lastDialErrorMu.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns existed connection from the pool or creates a new one.
|
||||||
|
func (p *ConnPool) Get() (*Conn, bool, error) {
|
||||||
|
if p.closed() {
|
||||||
|
return nil, false, ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p.queue <- struct{}{}:
|
||||||
|
default:
|
||||||
|
timer := timers.Get().(*time.Timer)
|
||||||
|
timer.Reset(p.opt.PoolTimeout)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p.queue <- struct{}{}:
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
timers.Put(timer)
|
||||||
|
case <-timer.C:
|
||||||
|
timers.Put(timer)
|
||||||
|
atomic.AddUint32(&p.stats.Timeouts, 1)
|
||||||
|
return nil, false, ErrPoolTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
p.freeConnsMu.Lock()
|
||||||
|
cn := p.popFree()
|
||||||
|
p.freeConnsMu.Unlock()
|
||||||
|
|
||||||
|
if cn == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if cn.IsStale(p.opt.IdleTimeout) {
|
||||||
|
p.CloseConn(cn)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddUint32(&p.stats.Hits, 1)
|
||||||
|
return cn, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddUint32(&p.stats.Misses, 1)
|
||||||
|
|
||||||
|
newcn, err := p.NewConn()
|
||||||
|
if err != nil {
|
||||||
|
<-p.queue
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newcn, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) popFree() *Conn {
|
||||||
|
if len(p.freeConns) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := len(p.freeConns) - 1
|
||||||
|
cn := p.freeConns[idx]
|
||||||
|
p.freeConns = p.freeConns[:idx]
|
||||||
|
return cn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Put(cn *Conn) error {
|
||||||
|
if data := cn.Rd.PeekBuffered(); data != nil {
|
||||||
|
internal.Logf("connection has unread data: %q", data)
|
||||||
|
return p.Remove(cn)
|
||||||
|
}
|
||||||
|
p.freeConnsMu.Lock()
|
||||||
|
p.freeConns = append(p.freeConns, cn)
|
||||||
|
p.freeConnsMu.Unlock()
|
||||||
|
<-p.queue
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Remove(cn *Conn) error {
|
||||||
|
_ = p.CloseConn(cn)
|
||||||
|
<-p.queue
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) CloseConn(cn *Conn) error {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
for i, c := range p.conns {
|
||||||
|
if c == cn {
|
||||||
|
p.conns = append(p.conns[:i], p.conns[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
|
||||||
|
return p.closeConn(cn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) closeConn(cn *Conn) error {
|
||||||
|
if p.opt.OnClose != nil {
|
||||||
|
_ = p.opt.OnClose(cn)
|
||||||
|
}
|
||||||
|
return cn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns total number of connections.
|
||||||
|
func (p *ConnPool) Len() int {
|
||||||
|
p.connsMu.Lock()
|
||||||
|
l := len(p.conns)
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeLen returns number of free connections.
|
||||||
|
func (p *ConnPool) FreeLen() int {
|
||||||
|
p.freeConnsMu.Lock()
|
||||||
|
l := len(p.freeConns)
|
||||||
|
p.freeConnsMu.Unlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Stats() *Stats {
|
||||||
|
return &Stats{
|
||||||
|
Hits: atomic.LoadUint32(&p.stats.Hits),
|
||||||
|
Misses: atomic.LoadUint32(&p.stats.Misses),
|
||||||
|
Timeouts: atomic.LoadUint32(&p.stats.Timeouts),
|
||||||
|
|
||||||
|
TotalConns: uint32(p.Len()),
|
||||||
|
FreeConns: uint32(p.FreeLen()),
|
||||||
|
StaleConns: atomic.LoadUint32(&p.stats.StaleConns),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) closed() bool {
|
||||||
|
return atomic.LoadUint32(&p._closed) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Filter(fn func(*Conn) bool) error {
|
||||||
|
var firstErr error
|
||||||
|
p.connsMu.Lock()
|
||||||
|
for _, cn := range p.conns {
|
||||||
|
if fn(cn) {
|
||||||
|
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) Close() error {
|
||||||
|
if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
p.connsMu.Lock()
|
||||||
|
for _, cn := range p.conns {
|
||||||
|
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.conns = nil
|
||||||
|
p.connsMu.Unlock()
|
||||||
|
|
||||||
|
p.freeConnsMu.Lock()
|
||||||
|
p.freeConns = nil
|
||||||
|
p.freeConnsMu.Unlock()
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) reapStaleConn() bool {
|
||||||
|
if len(p.freeConns) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := p.freeConns[0]
|
||||||
|
if !cn.IsStale(p.opt.IdleTimeout) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.CloseConn(cn)
|
||||||
|
p.freeConns = append(p.freeConns[:0], p.freeConns[1:]...)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) ReapStaleConns() (int, error) {
|
||||||
|
var n int
|
||||||
|
for {
|
||||||
|
p.queue <- struct{}{}
|
||||||
|
p.freeConnsMu.Lock()
|
||||||
|
|
||||||
|
reaped := p.reapStaleConn()
|
||||||
|
|
||||||
|
p.freeConnsMu.Unlock()
|
||||||
|
<-p.queue
|
||||||
|
|
||||||
|
if reaped {
|
||||||
|
n++
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ConnPool) reaper(frequency time.Duration) {
|
||||||
|
ticker := time.NewTicker(frequency)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
if p.closed() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n, err := p.ReapStaleConns()
|
||||||
|
if err != nil {
|
||||||
|
internal.Logf("ReapStaleConns failed: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
atomic.AddUint32(&p.stats.StaleConns, uint32(n))
|
||||||
|
}
|
||||||
|
}
|
55
vendor/github.com/go-redis/redis/internal/pool/pool_single.go
generated
vendored
Normal file
55
vendor/github.com/go-redis/redis/internal/pool/pool_single.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
type SingleConnPool struct {
|
||||||
|
cn *Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Pooler = (*SingleConnPool)(nil)
|
||||||
|
|
||||||
|
func NewSingleConnPool(cn *Conn) *SingleConnPool {
|
||||||
|
return &SingleConnPool{
|
||||||
|
cn: cn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) NewConn() (*Conn, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) CloseConn(*Conn) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) Get() (*Conn, bool, error) {
|
||||||
|
return p.cn, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) Put(cn *Conn) error {
|
||||||
|
if p.cn != cn {
|
||||||
|
panic("p.cn != cn")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) Remove(cn *Conn) error {
|
||||||
|
if p.cn != cn {
|
||||||
|
panic("p.cn != cn")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) Len() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) FreeLen() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) Stats() *Stats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *SingleConnPool) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
123
vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go
generated
vendored
Normal file
123
vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package pool
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type StickyConnPool struct {
|
||||||
|
pool *ConnPool
|
||||||
|
reusable bool
|
||||||
|
|
||||||
|
cn *Conn
|
||||||
|
closed bool
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Pooler = (*StickyConnPool)(nil)
|
||||||
|
|
||||||
|
func NewStickyConnPool(pool *ConnPool, reusable bool) *StickyConnPool {
|
||||||
|
return &StickyConnPool{
|
||||||
|
pool: pool,
|
||||||
|
reusable: reusable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) NewConn() (*Conn, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) CloseConn(*Conn) error {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Get() (*Conn, bool, error) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.closed {
|
||||||
|
return nil, false, ErrClosed
|
||||||
|
}
|
||||||
|
if p.cn != nil {
|
||||||
|
return p.cn, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, _, err := p.pool.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
p.cn = cn
|
||||||
|
return cn, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) putUpstream() (err error) {
|
||||||
|
err = p.pool.Put(p.cn)
|
||||||
|
p.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Put(cn *Conn) error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.closed {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) removeUpstream() error {
|
||||||
|
err := p.pool.Remove(p.cn)
|
||||||
|
p.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Remove(cn *Conn) error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.removeUpstream()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Len() int {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.cn == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) FreeLen() int {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.cn == nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Stats() *Stats {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *StickyConnPool) Close() error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
if p.closed {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
p.closed = true
|
||||||
|
var err error
|
||||||
|
if p.cn != nil {
|
||||||
|
if p.reusable {
|
||||||
|
err = p.putUpstream()
|
||||||
|
} else {
|
||||||
|
err = p.removeUpstream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
328
vendor/github.com/go-redis/redis/internal/proto/reader.go
generated
vendored
Normal file
328
vendor/github.com/go-redis/redis/internal/proto/reader.go
generated
vendored
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bytesAllocLimit = 1024 * 1024 // 1mb
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorReply = '-'
|
||||||
|
StatusReply = '+'
|
||||||
|
IntReply = ':'
|
||||||
|
StringReply = '$'
|
||||||
|
ArrayReply = '*'
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const Nil = RedisError("redis: nil")
|
||||||
|
|
||||||
|
type RedisError string
|
||||||
|
|
||||||
|
func (e RedisError) Error() string { return string(e) }
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type MultiBulkParse func(*Reader, int64) (interface{}, error)
|
||||||
|
|
||||||
|
type Reader struct {
|
||||||
|
src *bufio.Reader
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReader(rd io.Reader) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
src: bufio.NewReader(rd),
|
||||||
|
buf: make([]byte, 4096),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) Reset(rd io.Reader) {
|
||||||
|
r.src.Reset(rd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) PeekBuffered() []byte {
|
||||||
|
if n := r.src.Buffered(); n != 0 {
|
||||||
|
b, _ := r.src.Peek(n)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadN(n int) ([]byte, error) {
|
||||||
|
b, err := readN(r.src, r.buf, n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.buf = b
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadLine() ([]byte, error) {
|
||||||
|
line, isPrefix, err := r.src.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isPrefix {
|
||||||
|
return nil, bufio.ErrBufferFull
|
||||||
|
}
|
||||||
|
if len(line) == 0 {
|
||||||
|
return nil, fmt.Errorf("redis: reply is empty")
|
||||||
|
}
|
||||||
|
if isNilReply(line) {
|
||||||
|
return nil, Nil
|
||||||
|
}
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return nil, ParseErrorReply(line)
|
||||||
|
case StatusReply:
|
||||||
|
return parseStatusValue(line), nil
|
||||||
|
case IntReply:
|
||||||
|
return util.ParseInt(line[1:], 10, 64)
|
||||||
|
case StringReply:
|
||||||
|
return r.readTmpBytesValue(line)
|
||||||
|
case ArrayReply:
|
||||||
|
n, err := parseArrayLen(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m(r, n)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("redis: can't parse %.100q", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadIntReply() (int64, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return 0, ParseErrorReply(line)
|
||||||
|
case IntReply:
|
||||||
|
return util.ParseInt(line[1:], 10, 64)
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadTmpBytesReply() ([]byte, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return nil, ParseErrorReply(line)
|
||||||
|
case StringReply:
|
||||||
|
return r.readTmpBytesValue(line)
|
||||||
|
case StatusReply:
|
||||||
|
return parseStatusValue(line), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadBytesReply() ([]byte, error) {
|
||||||
|
b, err := r.ReadTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cp := make([]byte, len(b))
|
||||||
|
copy(cp, b)
|
||||||
|
return cp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadStringReply() (string, error) {
|
||||||
|
b, err := r.ReadTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadFloatReply() (float64, error) {
|
||||||
|
b, err := r.ReadTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return util.ParseFloat(b, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return nil, ParseErrorReply(line)
|
||||||
|
case ArrayReply:
|
||||||
|
n, err := parseArrayLen(line)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m(r, n)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadArrayLen() (int64, error) {
|
||||||
|
line, err := r.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch line[0] {
|
||||||
|
case ErrorReply:
|
||||||
|
return 0, ParseErrorReply(line)
|
||||||
|
case ArrayReply:
|
||||||
|
return parseArrayLen(line)
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadScanReply() ([]string, uint64, error) {
|
||||||
|
n, err := r.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := r.ReadUint()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = r.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
key, err := r.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
keys[i] = key
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys, cursor, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readTmpBytesValue(line []byte) ([]byte, error) {
|
||||||
|
if isNilReply(line) {
|
||||||
|
return nil, Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
replyLen, err := strconv.Atoi(string(line[1:]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := r.ReadN(replyLen + 2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b[:replyLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadInt() (int64, error) {
|
||||||
|
b, err := r.ReadTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return util.ParseInt(b, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) ReadUint() (uint64, error) {
|
||||||
|
b, err := r.ReadTmpBytesReply()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return util.ParseUint(b, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
func readN(r io.Reader, b []byte, n int) ([]byte, error) {
|
||||||
|
if n == 0 && b == nil {
|
||||||
|
return make([]byte, 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(b) >= n {
|
||||||
|
b = b[:n]
|
||||||
|
_, err := io.ReadFull(r, b)
|
||||||
|
return b, err
|
||||||
|
}
|
||||||
|
b = b[:cap(b)]
|
||||||
|
|
||||||
|
pos := 0
|
||||||
|
for pos < n {
|
||||||
|
diff := n - len(b)
|
||||||
|
if diff > bytesAllocLimit {
|
||||||
|
diff = bytesAllocLimit
|
||||||
|
}
|
||||||
|
b = append(b, make([]byte, diff)...)
|
||||||
|
|
||||||
|
nn, err := io.ReadFull(r, b[pos:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pos += nn
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatInt(n int64) string {
|
||||||
|
return strconv.FormatInt(n, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatUint(u uint64) string {
|
||||||
|
return strconv.FormatUint(u, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatFloat(f float64) string {
|
||||||
|
return strconv.FormatFloat(f, 'f', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNilReply(b []byte) bool {
|
||||||
|
return len(b) == 3 &&
|
||||||
|
(b[0] == StringReply || b[0] == ArrayReply) &&
|
||||||
|
b[1] == '-' && b[2] == '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseErrorReply(line []byte) error {
|
||||||
|
return RedisError(string(line[1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseStatusValue(line []byte) []byte {
|
||||||
|
return line[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseArrayLen(line []byte) (int64, error) {
|
||||||
|
if isNilReply(line) {
|
||||||
|
return 0, Nil
|
||||||
|
}
|
||||||
|
return util.ParseInt(line[1:], 10, 64)
|
||||||
|
}
|
166
vendor/github.com/go-redis/redis/internal/proto/scan.go
generated
vendored
Normal file
166
vendor/github.com/go-redis/redis/internal/proto/scan.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Scan(b []byte, v interface{}) error {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case nil:
|
||||||
|
return fmt.Errorf("redis: Scan(nil)")
|
||||||
|
case *string:
|
||||||
|
*v = util.BytesToString(b)
|
||||||
|
return nil
|
||||||
|
case *[]byte:
|
||||||
|
*v = b
|
||||||
|
return nil
|
||||||
|
case *int:
|
||||||
|
var err error
|
||||||
|
*v, err = util.Atoi(b)
|
||||||
|
return err
|
||||||
|
case *int8:
|
||||||
|
n, err := util.ParseInt(b, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = int8(n)
|
||||||
|
return nil
|
||||||
|
case *int16:
|
||||||
|
n, err := util.ParseInt(b, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = int16(n)
|
||||||
|
return nil
|
||||||
|
case *int32:
|
||||||
|
n, err := util.ParseInt(b, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = int32(n)
|
||||||
|
return nil
|
||||||
|
case *int64:
|
||||||
|
n, err := util.ParseInt(b, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = n
|
||||||
|
return nil
|
||||||
|
case *uint:
|
||||||
|
n, err := util.ParseUint(b, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint(n)
|
||||||
|
return nil
|
||||||
|
case *uint8:
|
||||||
|
n, err := util.ParseUint(b, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint8(n)
|
||||||
|
return nil
|
||||||
|
case *uint16:
|
||||||
|
n, err := util.ParseUint(b, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint16(n)
|
||||||
|
return nil
|
||||||
|
case *uint32:
|
||||||
|
n, err := util.ParseUint(b, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = uint32(n)
|
||||||
|
return nil
|
||||||
|
case *uint64:
|
||||||
|
n, err := util.ParseUint(b, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = n
|
||||||
|
return nil
|
||||||
|
case *float32:
|
||||||
|
n, err := util.ParseFloat(b, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = float32(n)
|
||||||
|
return err
|
||||||
|
case *float64:
|
||||||
|
var err error
|
||||||
|
*v, err = util.ParseFloat(b, 64)
|
||||||
|
return err
|
||||||
|
case *bool:
|
||||||
|
*v = len(b) == 1 && b[0] == '1'
|
||||||
|
return nil
|
||||||
|
case encoding.BinaryUnmarshaler:
|
||||||
|
return v.UnmarshalBinary(b)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScanSlice(data []string, slice interface{}) error {
|
||||||
|
v := reflect.ValueOf(slice)
|
||||||
|
if !v.IsValid() {
|
||||||
|
return fmt.Errorf("redis: ScanSlice(nil)")
|
||||||
|
}
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice)
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
if v.Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
next := makeSliceNextElemFunc(v)
|
||||||
|
for i, s := range data {
|
||||||
|
elem := next()
|
||||||
|
if err := Scan([]byte(s), elem.Addr().Interface()); err != nil {
|
||||||
|
err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %s", i, s, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value {
|
||||||
|
elemType := v.Type().Elem()
|
||||||
|
|
||||||
|
if elemType.Kind() == reflect.Ptr {
|
||||||
|
elemType = elemType.Elem()
|
||||||
|
return func() reflect.Value {
|
||||||
|
if v.Len() < v.Cap() {
|
||||||
|
v.Set(v.Slice(0, v.Len()+1))
|
||||||
|
elem := v.Index(v.Len() - 1)
|
||||||
|
if elem.IsNil() {
|
||||||
|
elem.Set(reflect.New(elemType))
|
||||||
|
}
|
||||||
|
return elem.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := reflect.New(elemType)
|
||||||
|
v.Set(reflect.Append(v, elem))
|
||||||
|
return elem.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zero := reflect.Zero(elemType)
|
||||||
|
return func() reflect.Value {
|
||||||
|
if v.Len() < v.Cap() {
|
||||||
|
v.Set(v.Slice(0, v.Len()+1))
|
||||||
|
return v.Index(v.Len() - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Set(reflect.Append(v, zero))
|
||||||
|
return v.Index(v.Len() - 1)
|
||||||
|
}
|
||||||
|
}
|
101
vendor/github.com/go-redis/redis/internal/proto/write_buffer.go
generated
vendored
Normal file
101
vendor/github.com/go-redis/redis/internal/proto/write_buffer.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WriteBuffer struct {
|
||||||
|
b []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriteBuffer() *WriteBuffer {
|
||||||
|
return &WriteBuffer{
|
||||||
|
b: make([]byte, 0, 4096),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteBuffer) Len() int { return len(w.b) }
|
||||||
|
func (w *WriteBuffer) Bytes() []byte { return w.b }
|
||||||
|
func (w *WriteBuffer) Reset() { w.b = w.b[:0] }
|
||||||
|
|
||||||
|
func (w *WriteBuffer) Append(args []interface{}) error {
|
||||||
|
w.b = append(w.b, ArrayReply)
|
||||||
|
w.b = strconv.AppendUint(w.b, uint64(len(args)), 10)
|
||||||
|
w.b = append(w.b, '\r', '\n')
|
||||||
|
|
||||||
|
for _, arg := range args {
|
||||||
|
if err := w.append(arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteBuffer) append(val interface{}) error {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case nil:
|
||||||
|
w.AppendString("")
|
||||||
|
case string:
|
||||||
|
w.AppendString(v)
|
||||||
|
case []byte:
|
||||||
|
w.AppendBytes(v)
|
||||||
|
case int:
|
||||||
|
w.AppendString(formatInt(int64(v)))
|
||||||
|
case int8:
|
||||||
|
w.AppendString(formatInt(int64(v)))
|
||||||
|
case int16:
|
||||||
|
w.AppendString(formatInt(int64(v)))
|
||||||
|
case int32:
|
||||||
|
w.AppendString(formatInt(int64(v)))
|
||||||
|
case int64:
|
||||||
|
w.AppendString(formatInt(v))
|
||||||
|
case uint:
|
||||||
|
w.AppendString(formatUint(uint64(v)))
|
||||||
|
case uint8:
|
||||||
|
w.AppendString(formatUint(uint64(v)))
|
||||||
|
case uint16:
|
||||||
|
w.AppendString(formatUint(uint64(v)))
|
||||||
|
case uint32:
|
||||||
|
w.AppendString(formatUint(uint64(v)))
|
||||||
|
case uint64:
|
||||||
|
w.AppendString(formatUint(v))
|
||||||
|
case float32:
|
||||||
|
w.AppendString(formatFloat(float64(v)))
|
||||||
|
case float64:
|
||||||
|
w.AppendString(formatFloat(v))
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
w.AppendString("1")
|
||||||
|
} else {
|
||||||
|
w.AppendString("0")
|
||||||
|
}
|
||||||
|
case encoding.BinaryMarshaler:
|
||||||
|
b, err := v.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.AppendBytes(b)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"redis: can't marshal %T (consider implementing encoding.BinaryMarshaler)", val)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteBuffer) AppendString(s string) {
|
||||||
|
w.b = append(w.b, StringReply)
|
||||||
|
w.b = strconv.AppendUint(w.b, uint64(len(s)), 10)
|
||||||
|
w.b = append(w.b, '\r', '\n')
|
||||||
|
w.b = append(w.b, s...)
|
||||||
|
w.b = append(w.b, '\r', '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WriteBuffer) AppendBytes(p []byte) {
|
||||||
|
w.b = append(w.b, StringReply)
|
||||||
|
w.b = strconv.AppendUint(w.b, uint64(len(p)), 10)
|
||||||
|
w.b = append(w.b, '\r', '\n')
|
||||||
|
w.b = append(w.b, p...)
|
||||||
|
w.b = append(w.b, '\r', '\n')
|
||||||
|
}
|
64
vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go
generated
vendored
Normal file
64
vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Google Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package singleflight provides a duplicate function call suppression
|
||||||
|
// mechanism.
|
||||||
|
package singleflight
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// call is an in-flight or completed Do call
|
||||||
|
type call struct {
|
||||||
|
wg sync.WaitGroup
|
||||||
|
val interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group represents a class of work and forms a namespace in which
|
||||||
|
// units of work can be executed with duplicate suppression.
|
||||||
|
type Group struct {
|
||||||
|
mu sync.Mutex // protects m
|
||||||
|
m map[string]*call // lazily initialized
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do executes and returns the results of the given function, making
|
||||||
|
// sure that only one execution is in-flight for a given key at a
|
||||||
|
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||||
|
// original to complete and receives the same results.
|
||||||
|
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
|
||||||
|
g.mu.Lock()
|
||||||
|
if g.m == nil {
|
||||||
|
g.m = make(map[string]*call)
|
||||||
|
}
|
||||||
|
if c, ok := g.m[key]; ok {
|
||||||
|
g.mu.Unlock()
|
||||||
|
c.wg.Wait()
|
||||||
|
return c.val, c.err
|
||||||
|
}
|
||||||
|
c := new(call)
|
||||||
|
c.wg.Add(1)
|
||||||
|
g.m[key] = c
|
||||||
|
g.mu.Unlock()
|
||||||
|
|
||||||
|
c.val, c.err = fn()
|
||||||
|
c.wg.Done()
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
delete(g.m, key)
|
||||||
|
g.mu.Unlock()
|
||||||
|
|
||||||
|
return c.val, c.err
|
||||||
|
}
|
29
vendor/github.com/go-redis/redis/internal/util.go
generated
vendored
Normal file
29
vendor/github.com/go-redis/redis/internal/util.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import "github.com/go-redis/redis/internal/util"
|
||||||
|
|
||||||
|
func ToLower(s string) string {
|
||||||
|
if isLower(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, len(s))
|
||||||
|
for i := range b {
|
||||||
|
c := s[i]
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
c += 'a' - 'A'
|
||||||
|
}
|
||||||
|
b[i] = c
|
||||||
|
}
|
||||||
|
return util.BytesToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLower(s string) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
7
vendor/github.com/go-redis/redis/internal/util/safe.go
generated
vendored
Normal file
7
vendor/github.com/go-redis/redis/internal/util/safe.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
return string(b)
|
||||||
|
}
|
19
vendor/github.com/go-redis/redis/internal/util/strconv.go
generated
vendored
Normal file
19
vendor/github.com/go-redis/redis/internal/util/strconv.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func Atoi(b []byte) (int, error) {
|
||||||
|
return strconv.Atoi(BytesToString(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseInt(b []byte, base int, bitSize int) (int64, error) {
|
||||||
|
return strconv.ParseInt(BytesToString(b), base, bitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUint(b []byte, base int, bitSize int) (uint64, error) {
|
||||||
|
return strconv.ParseUint(BytesToString(b), base, bitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseFloat(b []byte, bitSize int) (float64, error) {
|
||||||
|
return strconv.ParseFloat(BytesToString(b), bitSize)
|
||||||
|
}
|
12
vendor/github.com/go-redis/redis/internal/util/unsafe.go
generated
vendored
Normal file
12
vendor/github.com/go-redis/redis/internal/util/unsafe.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BytesToString converts byte slice to string.
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
return *(*string)(unsafe.Pointer(&b))
|
||||||
|
}
|
73
vendor/github.com/go-redis/redis/iterator.go
generated
vendored
Normal file
73
vendor/github.com/go-redis/redis/iterator.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// ScanIterator is used to incrementally iterate over a collection of elements.
|
||||||
|
// It's safe for concurrent use by multiple goroutines.
|
||||||
|
type ScanIterator struct {
|
||||||
|
mu sync.Mutex // protects Scanner and pos
|
||||||
|
cmd *ScanCmd
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the last iterator error, if any.
|
||||||
|
func (it *ScanIterator) Err() error {
|
||||||
|
it.mu.Lock()
|
||||||
|
err := it.cmd.Err()
|
||||||
|
it.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next advances the cursor and returns true if more values can be read.
|
||||||
|
func (it *ScanIterator) Next() bool {
|
||||||
|
it.mu.Lock()
|
||||||
|
defer it.mu.Unlock()
|
||||||
|
|
||||||
|
// Instantly return on errors.
|
||||||
|
if it.cmd.Err() != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance cursor, check if we are still within range.
|
||||||
|
if it.pos < len(it.cmd.page) {
|
||||||
|
it.pos++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Return if there is no more data to fetch.
|
||||||
|
if it.cmd.cursor == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch next page.
|
||||||
|
if it.cmd._args[0] == "scan" {
|
||||||
|
it.cmd._args[1] = it.cmd.cursor
|
||||||
|
} else {
|
||||||
|
it.cmd._args[2] = it.cmd.cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
err := it.cmd.process(it.cmd)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
it.pos = 1
|
||||||
|
|
||||||
|
// Redis can occasionally return empty page.
|
||||||
|
if len(it.cmd.page) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Val returns the key/field at the current cursor position.
|
||||||
|
func (it *ScanIterator) Val() string {
|
||||||
|
var v string
|
||||||
|
it.mu.Lock()
|
||||||
|
if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) {
|
||||||
|
v = it.cmd.page[it.pos-1]
|
||||||
|
}
|
||||||
|
it.mu.Unlock()
|
||||||
|
return v
|
||||||
|
}
|
200
vendor/github.com/go-redis/redis/options.go
generated
vendored
Normal file
200
vendor/github.com/go-redis/redis/options.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
// The network type, either tcp or unix.
|
||||||
|
// Default is tcp.
|
||||||
|
Network string
|
||||||
|
// host:port address.
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
// Dialer creates new network connection and has priority over
|
||||||
|
// Network and Addr options.
|
||||||
|
Dialer func() (net.Conn, error)
|
||||||
|
|
||||||
|
// Hook that is called when new connection is established.
|
||||||
|
OnConnect func(*Conn) error
|
||||||
|
|
||||||
|
// Optional password. Must match the password specified in the
|
||||||
|
// requirepass server configuration option.
|
||||||
|
Password string
|
||||||
|
// Database to be selected after connecting to the server.
|
||||||
|
DB int
|
||||||
|
|
||||||
|
// Maximum number of retries before giving up.
|
||||||
|
// Default is to not retry failed commands.
|
||||||
|
MaxRetries int
|
||||||
|
// Minimum backoff between each retry.
|
||||||
|
// Default is 8 milliseconds; -1 disables backoff.
|
||||||
|
MinRetryBackoff time.Duration
|
||||||
|
// Maximum backoff between each retry.
|
||||||
|
// Default is 512 milliseconds; -1 disables backoff.
|
||||||
|
MaxRetryBackoff time.Duration
|
||||||
|
|
||||||
|
// Dial timeout for establishing new connections.
|
||||||
|
// Default is 5 seconds.
|
||||||
|
DialTimeout time.Duration
|
||||||
|
// Timeout for socket reads. If reached, commands will fail
|
||||||
|
// with a timeout instead of blocking.
|
||||||
|
// Default is 3 seconds.
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
// Timeout for socket writes. If reached, commands will fail
|
||||||
|
// with a timeout instead of blocking.
|
||||||
|
// Default is ReadTimeout.
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
|
||||||
|
// Maximum number of socket connections.
|
||||||
|
// Default is 10 connections per every CPU as reported by runtime.NumCPU.
|
||||||
|
PoolSize int
|
||||||
|
// Amount of time client waits for connection if all connections
|
||||||
|
// are busy before returning an error.
|
||||||
|
// Default is ReadTimeout + 1 second.
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
// Amount of time after which client closes idle connections.
|
||||||
|
// Should be less than server's timeout.
|
||||||
|
// Default is 5 minutes.
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
// Frequency of idle checks.
|
||||||
|
// Default is 1 minute.
|
||||||
|
// When minus value is set, then idle check is disabled.
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
|
||||||
|
// Enables read only queries on slave nodes.
|
||||||
|
readOnly bool
|
||||||
|
|
||||||
|
// TLS Config to use. When set TLS will be negotiated.
|
||||||
|
TLSConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *Options) init() {
|
||||||
|
if opt.Network == "" {
|
||||||
|
opt.Network = "tcp"
|
||||||
|
}
|
||||||
|
if opt.Dialer == nil {
|
||||||
|
opt.Dialer = func() (net.Conn, error) {
|
||||||
|
conn, err := net.DialTimeout(opt.Network, opt.Addr, opt.DialTimeout)
|
||||||
|
if opt.TLSConfig == nil || err != nil {
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
t := tls.Client(conn, opt.TLSConfig)
|
||||||
|
return t, t.Handshake()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if opt.PoolSize == 0 {
|
||||||
|
opt.PoolSize = 10 * runtime.NumCPU()
|
||||||
|
}
|
||||||
|
if opt.DialTimeout == 0 {
|
||||||
|
opt.DialTimeout = 5 * time.Second
|
||||||
|
}
|
||||||
|
switch opt.ReadTimeout {
|
||||||
|
case -1:
|
||||||
|
opt.ReadTimeout = 0
|
||||||
|
case 0:
|
||||||
|
opt.ReadTimeout = 3 * time.Second
|
||||||
|
}
|
||||||
|
switch opt.WriteTimeout {
|
||||||
|
case -1:
|
||||||
|
opt.WriteTimeout = 0
|
||||||
|
case 0:
|
||||||
|
opt.WriteTimeout = opt.ReadTimeout
|
||||||
|
}
|
||||||
|
if opt.PoolTimeout == 0 {
|
||||||
|
opt.PoolTimeout = opt.ReadTimeout + time.Second
|
||||||
|
}
|
||||||
|
if opt.IdleTimeout == 0 {
|
||||||
|
opt.IdleTimeout = 5 * time.Minute
|
||||||
|
}
|
||||||
|
if opt.IdleCheckFrequency == 0 {
|
||||||
|
opt.IdleCheckFrequency = time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
switch opt.MinRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MinRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MinRetryBackoff = 8 * time.Millisecond
|
||||||
|
}
|
||||||
|
switch opt.MaxRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MaxRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MaxRetryBackoff = 512 * time.Millisecond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseURL parses an URL into Options that can be used to connect to Redis.
|
||||||
|
func ParseURL(redisURL string) (*Options, error) {
|
||||||
|
o := &Options{Network: "tcp"}
|
||||||
|
u, err := url.Parse(redisURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme != "redis" && u.Scheme != "rediss" {
|
||||||
|
return nil, errors.New("invalid redis URL scheme: " + u.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
if p, ok := u.User.Password(); ok {
|
||||||
|
o.Password = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(u.Query()) > 0 {
|
||||||
|
return nil, errors.New("no options supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
h, p, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
h = u.Host
|
||||||
|
}
|
||||||
|
if h == "" {
|
||||||
|
h = "localhost"
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
p = "6379"
|
||||||
|
}
|
||||||
|
o.Addr = net.JoinHostPort(h, p)
|
||||||
|
|
||||||
|
f := strings.FieldsFunc(u.Path, func(r rune) bool {
|
||||||
|
return r == '/'
|
||||||
|
})
|
||||||
|
switch len(f) {
|
||||||
|
case 0:
|
||||||
|
o.DB = 0
|
||||||
|
case 1:
|
||||||
|
if o.DB, err = strconv.Atoi(f[0]); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid redis database number: %q", f[0])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid redis URL path: " + u.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "rediss" {
|
||||||
|
o.TLSConfig = &tls.Config{ServerName: h}
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnPool(opt *Options) *pool.ConnPool {
|
||||||
|
return pool.NewConnPool(&pool.Options{
|
||||||
|
Dialer: opt.Dialer,
|
||||||
|
PoolSize: opt.PoolSize,
|
||||||
|
PoolTimeout: opt.PoolTimeout,
|
||||||
|
IdleTimeout: opt.IdleTimeout,
|
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency,
|
||||||
|
})
|
||||||
|
}
|
394
vendor/github.com/go-redis/redis/parser.go
generated
vendored
Normal file
394
vendor/github.com/go-redis/redis/parser.go
generated
vendored
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func sliceParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
vals := make([]interface{}, 0, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
v, err := rd.ReadReply(sliceParser)
|
||||||
|
if err != nil {
|
||||||
|
if err == Nil {
|
||||||
|
vals = append(vals, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err, ok := err.(proto.RedisError); ok {
|
||||||
|
vals = append(vals, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := v.(type) {
|
||||||
|
case []byte:
|
||||||
|
vals = append(vals, string(v))
|
||||||
|
default:
|
||||||
|
vals = append(vals, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vals, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func boolSliceParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
bools := make([]bool, 0, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
n, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bools = append(bools, n == 1)
|
||||||
|
}
|
||||||
|
return bools, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func stringSliceParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
ss := make([]string, 0, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
s, err := rd.ReadStringReply()
|
||||||
|
if err == Nil {
|
||||||
|
ss = append(ss, "")
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
ss = append(ss, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ss, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func stringStringMapParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
m := make(map[string]string, n/2)
|
||||||
|
for i := int64(0); i < n; i += 2 {
|
||||||
|
key, err := rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m[key] = value
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func stringIntMapParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
m := make(map[string]int64, n/2)
|
||||||
|
for i := int64(0); i < n; i += 2 {
|
||||||
|
key, err := rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m[key] = n
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func stringStructMapParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
m := make(map[string]struct{}, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
key, err := rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m[key] = struct{}{}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func zSliceParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
zz := make([]Z, n/2)
|
||||||
|
for i := int64(0); i < n; i += 2 {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
z := &zz[i/2]
|
||||||
|
|
||||||
|
z.Member, err = rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
z.Score, err = rd.ReadFloatReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func clusterSlotsParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
slots := make([]ClusterSlot, n)
|
||||||
|
for i := 0; i < len(slots); i++ {
|
||||||
|
n, err := rd.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n < 2 {
|
||||||
|
err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := make([]ClusterNode, n-2)
|
||||||
|
for j := 0; j < len(nodes); j++ {
|
||||||
|
n, err := rd.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != 2 && n != 3 {
|
||||||
|
err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
port, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes[j].Addr = net.JoinHostPort(ip, strconv.FormatInt(port, 10))
|
||||||
|
|
||||||
|
if n == 3 {
|
||||||
|
id, err := rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes[j].Id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slots[i] = ClusterSlot{
|
||||||
|
Start: int(start),
|
||||||
|
End: int(end),
|
||||||
|
Nodes: nodes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse {
|
||||||
|
return func(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
var loc GeoLocation
|
||||||
|
var err error
|
||||||
|
|
||||||
|
loc.Name, err = rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if q.WithDist {
|
||||||
|
loc.Dist, err = rd.ReadFloatReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if q.WithGeoHash {
|
||||||
|
loc.GeoHash, err = rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if q.WithCoord {
|
||||||
|
n, err := rd.ReadArrayLen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
return nil, fmt.Errorf("got %d coordinates, expected 2", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
loc.Longitude, err = rd.ReadFloatReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
loc.Latitude, err = rd.ReadFloatReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &loc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse {
|
||||||
|
return func(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
locs := make([]GeoLocation, 0, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
v, err := rd.ReadReply(newGeoLocationParser(q))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case []byte:
|
||||||
|
locs = append(locs, GeoLocation{
|
||||||
|
Name: string(vv),
|
||||||
|
})
|
||||||
|
case *GeoLocation:
|
||||||
|
locs = append(locs, *vv)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return locs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func geoPosParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
var pos GeoPos
|
||||||
|
var err error
|
||||||
|
|
||||||
|
pos.Longitude, err = rd.ReadFloatReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pos.Latitude, err = rd.ReadFloatReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func geoPosSliceParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
positions := make([]*GeoPos, 0, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
v, err := rd.ReadReply(geoPosParser)
|
||||||
|
if err != nil {
|
||||||
|
if err == Nil {
|
||||||
|
positions = append(positions, nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case *GeoPos:
|
||||||
|
positions = append(positions, v)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("got %T, expected *GeoPos", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return positions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
var cmd CommandInfo
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if n != 6 {
|
||||||
|
return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 6", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Name, err = rd.ReadStringReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arity, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.Arity = int8(arity)
|
||||||
|
|
||||||
|
flags, err := rd.ReadReply(stringSliceParser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.Flags = flags.([]string)
|
||||||
|
|
||||||
|
firstKeyPos, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.FirstKeyPos = int8(firstKeyPos)
|
||||||
|
|
||||||
|
lastKeyPos, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.LastKeyPos = int8(lastKeyPos)
|
||||||
|
|
||||||
|
stepCount, err := rd.ReadIntReply()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.StepCount = int8(stepCount)
|
||||||
|
|
||||||
|
for _, flag := range cmd.Flags {
|
||||||
|
if flag == "readonly" {
|
||||||
|
cmd.ReadOnly = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func commandInfoSliceParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
m := make(map[string]*CommandInfo, n)
|
||||||
|
for i := int64(0); i < n; i++ {
|
||||||
|
v, err := rd.ReadReply(commandInfoParser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vv := v.(*CommandInfo)
|
||||||
|
m[vv.Name] = vv
|
||||||
|
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements proto.MultiBulkParse
|
||||||
|
func timeParser(rd *proto.Reader, n int64) (interface{}, error) {
|
||||||
|
if n != 2 {
|
||||||
|
return nil, fmt.Errorf("got %d elements, expected 2", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
sec, err := rd.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
microsec, err := rd.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Unix(sec, microsec*1000), nil
|
||||||
|
}
|
112
vendor/github.com/go-redis/redis/pipeline.go
generated
vendored
Normal file
112
vendor/github.com/go-redis/redis/pipeline.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pipelineExecer func([]Cmder) error
|
||||||
|
|
||||||
|
type Pipeliner interface {
|
||||||
|
StatefulCmdable
|
||||||
|
Process(cmd Cmder) error
|
||||||
|
Close() error
|
||||||
|
Discard() error
|
||||||
|
Exec() ([]Cmder, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Pipeliner = (*Pipeline)(nil)
|
||||||
|
|
||||||
|
// Pipeline implements pipelining as described in
|
||||||
|
// http://redis.io/topics/pipelining. It's safe for concurrent use
|
||||||
|
// by multiple goroutines.
|
||||||
|
type Pipeline struct {
|
||||||
|
statefulCmdable
|
||||||
|
|
||||||
|
exec pipelineExecer
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
cmds []Cmder
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) Process(cmd Cmder) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.cmds = append(c.cmds, cmd)
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the pipeline, releasing any open resources.
|
||||||
|
func (c *Pipeline) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
c.discard()
|
||||||
|
c.closed = true
|
||||||
|
c.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discard resets the pipeline and discards queued commands.
|
||||||
|
func (c *Pipeline) Discard() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.discard()
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) discard() error {
|
||||||
|
if c.closed {
|
||||||
|
return pool.ErrClosed
|
||||||
|
}
|
||||||
|
c.cmds = c.cmds[:0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec executes all previously queued commands using one
|
||||||
|
// client-server roundtrip.
|
||||||
|
//
|
||||||
|
// Exec always returns list of commands and error of the first failed
|
||||||
|
// command if any.
|
||||||
|
func (c *Pipeline) Exec() ([]Cmder, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return nil, pool.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.cmds) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds := c.cmds
|
||||||
|
c.cmds = nil
|
||||||
|
|
||||||
|
return cmds, c.exec(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
if err := fn(c); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmds, err := c.Exec()
|
||||||
|
_ = c.Close()
|
||||||
|
return cmds, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) Pipeline() Pipeliner {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Pipeline) TxPipeline() Pipeliner {
|
||||||
|
return c
|
||||||
|
}
|
399
vendor/github.com/go-redis/redis/pubsub.go
generated
vendored
Normal file
399
vendor/github.com/go-redis/redis/pubsub.go
generated
vendored
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal"
|
||||||
|
"github.com/go-redis/redis/internal/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PubSub implements Pub/Sub commands as described in
|
||||||
|
// http://redis.io/topics/pubsub. It's NOT safe for concurrent use by
|
||||||
|
// multiple goroutines.
|
||||||
|
//
|
||||||
|
// PubSub automatically resubscribes to the channels and patterns
|
||||||
|
// when Redis becomes unavailable.
|
||||||
|
type PubSub struct {
|
||||||
|
opt *Options
|
||||||
|
|
||||||
|
newConn func([]string) (*pool.Conn, error)
|
||||||
|
closeConn func(*pool.Conn) error
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
cn *pool.Conn
|
||||||
|
channels map[string]struct{}
|
||||||
|
patterns map[string]struct{}
|
||||||
|
closed bool
|
||||||
|
|
||||||
|
cmd *Cmd
|
||||||
|
|
||||||
|
chOnce sync.Once
|
||||||
|
ch chan *Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) conn() (*pool.Conn, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
cn, err := c._conn(nil)
|
||||||
|
c.mu.Unlock()
|
||||||
|
return cn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) _conn(channels []string) (*pool.Conn, error) {
|
||||||
|
if c.closed {
|
||||||
|
return nil, pool.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.cn != nil {
|
||||||
|
return c.cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, err := c.newConn(channels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.resubscribe(cn); err != nil {
|
||||||
|
_ = c.closeConn(cn)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cn = cn
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) resubscribe(cn *pool.Conn) error {
|
||||||
|
var firstErr error
|
||||||
|
if len(c.channels) > 0 {
|
||||||
|
channels := make([]string, len(c.channels))
|
||||||
|
i := 0
|
||||||
|
for channel := range c.channels {
|
||||||
|
channels[i] = channel
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if err := c._subscribe(cn, "subscribe", channels...); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(c.patterns) > 0 {
|
||||||
|
patterns := make([]string, len(c.patterns))
|
||||||
|
i := 0
|
||||||
|
for pattern := range c.patterns {
|
||||||
|
patterns[i] = pattern
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if err := c._subscribe(cn, "psubscribe", patterns...); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) _subscribe(cn *pool.Conn, redisCmd string, channels ...string) error {
|
||||||
|
args := make([]interface{}, 1+len(channels))
|
||||||
|
args[0] = redisCmd
|
||||||
|
for i, channel := range channels {
|
||||||
|
args[1+i] = channel
|
||||||
|
}
|
||||||
|
cmd := NewSliceCmd(args...)
|
||||||
|
|
||||||
|
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||||
|
return writeCmd(cn, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) releaseConn(cn *pool.Conn, err error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
c._releaseConn(cn, err)
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) _releaseConn(cn *pool.Conn, err error) {
|
||||||
|
if c.cn != cn {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if internal.IsBadConn(err, true) {
|
||||||
|
_ = c.closeTheCn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) closeTheCn() error {
|
||||||
|
err := c.closeConn(c.cn)
|
||||||
|
c.cn = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return pool.ErrClosed
|
||||||
|
}
|
||||||
|
c.closed = true
|
||||||
|
|
||||||
|
if c.cn != nil {
|
||||||
|
return c.closeTheCn()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe the client to the specified channels. It returns
|
||||||
|
// empty subscription if there are no channels.
|
||||||
|
func (c *PubSub) Subscribe(channels ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.subscribe("subscribe", channels...)
|
||||||
|
if c.channels == nil {
|
||||||
|
c.channels = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
for _, channel := range channels {
|
||||||
|
c.channels[channel] = struct{}{}
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSubscribe the client to the given patterns. It returns
|
||||||
|
// empty subscription if there are no patterns.
|
||||||
|
func (c *PubSub) PSubscribe(patterns ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.subscribe("psubscribe", patterns...)
|
||||||
|
if c.patterns == nil {
|
||||||
|
c.patterns = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
c.patterns[pattern] = struct{}{}
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe the client from the given channels, or from all of
|
||||||
|
// them if none is given.
|
||||||
|
func (c *PubSub) Unsubscribe(channels ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.subscribe("unsubscribe", channels...)
|
||||||
|
for _, channel := range channels {
|
||||||
|
delete(c.channels, channel)
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUnsubscribe the client from the given patterns, or from all of
|
||||||
|
// them if none is given.
|
||||||
|
func (c *PubSub) PUnsubscribe(patterns ...string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
err := c.subscribe("punsubscribe", patterns...)
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
delete(c.patterns, pattern)
|
||||||
|
}
|
||||||
|
c.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) subscribe(redisCmd string, channels ...string) error {
|
||||||
|
cn, err := c._conn(channels)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c._subscribe(cn, redisCmd, channels...)
|
||||||
|
c._releaseConn(cn, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) Ping(payload ...string) error {
|
||||||
|
args := []interface{}{"ping"}
|
||||||
|
if len(payload) == 1 {
|
||||||
|
args = append(args, payload[0])
|
||||||
|
}
|
||||||
|
cmd := NewCmd(args...)
|
||||||
|
|
||||||
|
cn, err := c.conn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||||
|
err = writeCmd(cn, cmd)
|
||||||
|
c.releaseConn(cn, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscription received after a successful subscription to channel.
|
||||||
|
type Subscription struct {
|
||||||
|
// Can be "subscribe", "unsubscribe", "psubscribe" or "punsubscribe".
|
||||||
|
Kind string
|
||||||
|
// Channel name we have subscribed to.
|
||||||
|
Channel string
|
||||||
|
// Number of channels we are currently subscribed to.
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Subscription) String() string {
|
||||||
|
return fmt.Sprintf("%s: %s", m.Kind, m.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message received as result of a PUBLISH command issued by another client.
|
||||||
|
type Message struct {
|
||||||
|
Channel string
|
||||||
|
Pattern string
|
||||||
|
Payload string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Message) String() string {
|
||||||
|
return fmt.Sprintf("Message<%s: %s>", m.Channel, m.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pong received as result of a PING command issued by another client.
|
||||||
|
type Pong struct {
|
||||||
|
Payload string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pong) String() string {
|
||||||
|
if p.Payload != "" {
|
||||||
|
return fmt.Sprintf("Pong<%s>", p.Payload)
|
||||||
|
}
|
||||||
|
return "Pong"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) newMessage(reply interface{}) (interface{}, error) {
|
||||||
|
switch reply := reply.(type) {
|
||||||
|
case string:
|
||||||
|
return &Pong{
|
||||||
|
Payload: reply,
|
||||||
|
}, nil
|
||||||
|
case []interface{}:
|
||||||
|
switch kind := reply[0].(string); kind {
|
||||||
|
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe":
|
||||||
|
return &Subscription{
|
||||||
|
Kind: kind,
|
||||||
|
Channel: reply[1].(string),
|
||||||
|
Count: int(reply[2].(int64)),
|
||||||
|
}, nil
|
||||||
|
case "message":
|
||||||
|
return &Message{
|
||||||
|
Channel: reply[1].(string),
|
||||||
|
Payload: reply[2].(string),
|
||||||
|
}, nil
|
||||||
|
case "pmessage":
|
||||||
|
return &Message{
|
||||||
|
Pattern: reply[1].(string),
|
||||||
|
Channel: reply[2].(string),
|
||||||
|
Payload: reply[3].(string),
|
||||||
|
}, nil
|
||||||
|
case "pong":
|
||||||
|
return &Pong{
|
||||||
|
Payload: reply[1].(string),
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: unsupported pubsub message: %q", kind)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: unsupported pubsub message: %#v", reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveTimeout acts like Receive but returns an error if message
|
||||||
|
// is not received in time. This is low-level API and most clients
|
||||||
|
// should use ReceiveMessage.
|
||||||
|
func (c *PubSub) ReceiveTimeout(timeout time.Duration) (interface{}, error) {
|
||||||
|
if c.cmd == nil {
|
||||||
|
c.cmd = NewCmd()
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, err := c.conn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn.SetReadTimeout(timeout)
|
||||||
|
err = c.cmd.readReply(cn)
|
||||||
|
c.releaseConn(cn, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.newMessage(c.cmd.Val())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive returns a message as a Subscription, Message, Pong or error.
|
||||||
|
// See PubSub example for details. This is low-level API and most clients
|
||||||
|
// should use ReceiveMessage.
|
||||||
|
func (c *PubSub) Receive() (interface{}, error) {
|
||||||
|
return c.ReceiveTimeout(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReceiveMessage returns a Message or error ignoring Subscription or Pong
|
||||||
|
// messages. It automatically reconnects to Redis Server and resubscribes
|
||||||
|
// to channels in case of network errors.
|
||||||
|
func (c *PubSub) ReceiveMessage() (*Message, error) {
|
||||||
|
return c.receiveMessage(5 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PubSub) receiveMessage(timeout time.Duration) (*Message, error) {
|
||||||
|
var errNum uint
|
||||||
|
for {
|
||||||
|
msgi, err := c.ReceiveTimeout(timeout)
|
||||||
|
if err != nil {
|
||||||
|
if !internal.IsNetworkError(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
errNum++
|
||||||
|
if errNum < 3 {
|
||||||
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||||
|
err := c.Ping()
|
||||||
|
if err != nil {
|
||||||
|
internal.Logf("PubSub.Ping failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 3 consequent errors - connection is broken or
|
||||||
|
// Redis Server is down.
|
||||||
|
// Sleep to not exceed max number of open connections.
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset error number, because we received a message.
|
||||||
|
errNum = 0
|
||||||
|
|
||||||
|
switch msg := msgi.(type) {
|
||||||
|
case *Subscription:
|
||||||
|
// Ignore.
|
||||||
|
case *Pong:
|
||||||
|
// Ignore.
|
||||||
|
case *Message:
|
||||||
|
return msg, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("redis: unknown message: %T", msgi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel returns a Go channel for concurrently receiving messages.
|
||||||
|
// The channel is closed with PubSub. Receive or ReceiveMessage APIs
|
||||||
|
// can not be used after channel is created.
|
||||||
|
func (c *PubSub) Channel() <-chan *Message {
|
||||||
|
c.chOnce.Do(func() {
|
||||||
|
c.ch = make(chan *Message, 100)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
msg, err := c.ReceiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
if err == pool.ErrClosed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.ch <- msg
|
||||||
|
}
|
||||||
|
close(c.ch)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
return c.ch
|
||||||
|
}
|
499
vendor/github.com/go-redis/redis/redis.go
generated
vendored
Normal file
499
vendor/github.com/go-redis/redis/redis.go
generated
vendored
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal"
|
||||||
|
"github.com/go-redis/redis/internal/pool"
|
||||||
|
"github.com/go-redis/redis/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Nil reply Redis returns when key does not exist.
|
||||||
|
const Nil = proto.Nil
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetLogger(log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetLogger(logger *log.Logger) {
|
||||||
|
internal.Logger = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseClient struct {
|
||||||
|
opt *Options
|
||||||
|
connPool pool.Pooler
|
||||||
|
|
||||||
|
process func(Cmder) error
|
||||||
|
processPipeline func([]Cmder) error
|
||||||
|
processTxPipeline func([]Cmder) error
|
||||||
|
|
||||||
|
onClose func() error // hook called when client is closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) init() {
|
||||||
|
c.process = c.defaultProcess
|
||||||
|
c.processPipeline = c.defaultProcessPipeline
|
||||||
|
c.processTxPipeline = c.defaultProcessTxPipeline
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) String() string {
|
||||||
|
return fmt.Sprintf("Redis<%s db:%d>", c.getAddr(), c.opt.DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) newConn() (*pool.Conn, error) {
|
||||||
|
cn, err := c.connPool.NewConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cn.Inited {
|
||||||
|
if err := c.initConn(cn); err != nil {
|
||||||
|
_ = c.connPool.CloseConn(cn)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) getConn() (*pool.Conn, bool, error) {
|
||||||
|
cn, isNew, err := c.connPool.Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cn.Inited {
|
||||||
|
if err := c.initConn(cn); err != nil {
|
||||||
|
_ = c.connPool.Remove(cn)
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cn, isNew, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) releaseConn(cn *pool.Conn, err error) bool {
|
||||||
|
if internal.IsBadConn(err, false) {
|
||||||
|
_ = c.connPool.Remove(cn)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = c.connPool.Put(cn)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) initConn(cn *pool.Conn) error {
|
||||||
|
cn.Inited = true
|
||||||
|
|
||||||
|
if c.opt.Password == "" &&
|
||||||
|
c.opt.DB == 0 &&
|
||||||
|
!c.opt.readOnly &&
|
||||||
|
c.opt.OnConnect == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := newConn(c.opt, cn)
|
||||||
|
_, err := conn.Pipelined(func(pipe Pipeliner) error {
|
||||||
|
if c.opt.Password != "" {
|
||||||
|
pipe.Auth(c.opt.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opt.DB > 0 {
|
||||||
|
pipe.Select(c.opt.DB)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opt.readOnly {
|
||||||
|
pipe.ReadOnly()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opt.OnConnect != nil {
|
||||||
|
return c.opt.OnConnect(conn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapProcess wraps function that processes Redis commands.
|
||||||
|
func (c *baseClient) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) {
|
||||||
|
c.process = fn(c.process)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) Process(cmd Cmder) error {
|
||||||
|
return c.process(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) defaultProcess(cmd Cmder) error {
|
||||||
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
|
if attempt > 0 {
|
||||||
|
time.Sleep(c.retryBackoff(attempt))
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, _, err := c.getConn()
|
||||||
|
if err != nil {
|
||||||
|
cmd.setErr(err)
|
||||||
|
if internal.IsRetryableError(err, true) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||||
|
if err := writeCmd(cn, cmd); err != nil {
|
||||||
|
c.releaseConn(cn, err)
|
||||||
|
cmd.setErr(err)
|
||||||
|
if internal.IsRetryableError(err, true) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cn.SetReadTimeout(c.cmdTimeout(cmd))
|
||||||
|
err = cmd.readReply(cn)
|
||||||
|
c.releaseConn(cn, err)
|
||||||
|
if err != nil && internal.IsRetryableError(err, cmd.readTimeout() == nil) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) retryBackoff(attempt int) time.Duration {
|
||||||
|
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) cmdTimeout(cmd Cmder) time.Duration {
|
||||||
|
if timeout := cmd.readTimeout(); timeout != nil {
|
||||||
|
return *timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.opt.ReadTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the client, releasing any open resources.
|
||||||
|
//
|
||||||
|
// It is rare to Close a Client, as the Client is meant to be
|
||||||
|
// long-lived and shared between many goroutines.
|
||||||
|
func (c *baseClient) Close() error {
|
||||||
|
var firstErr error
|
||||||
|
if c.onClose != nil {
|
||||||
|
if err := c.onClose(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.connPool.Close(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) getAddr() string {
|
||||||
|
return c.opt.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) WrapProcessPipeline(
|
||||||
|
fn func(oldProcess func([]Cmder) error) func([]Cmder) error,
|
||||||
|
) {
|
||||||
|
c.processPipeline = fn(c.processPipeline)
|
||||||
|
c.processTxPipeline = fn(c.processTxPipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) defaultProcessPipeline(cmds []Cmder) error {
|
||||||
|
return c.generalProcessPipeline(cmds, c.pipelineProcessCmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) defaultProcessTxPipeline(cmds []Cmder) error {
|
||||||
|
return c.generalProcessPipeline(cmds, c.txPipelineProcessCmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipelineProcessor func(*pool.Conn, []Cmder) (bool, error)
|
||||||
|
|
||||||
|
func (c *baseClient) generalProcessPipeline(cmds []Cmder, p pipelineProcessor) error {
|
||||||
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
|
if attempt > 0 {
|
||||||
|
time.Sleep(c.retryBackoff(attempt))
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, _, err := c.getConn()
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
canRetry, err := p(cn, cmds)
|
||||||
|
|
||||||
|
if err == nil || internal.IsRedisError(err) {
|
||||||
|
_ = c.connPool.Put(cn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ = c.connPool.Remove(cn)
|
||||||
|
|
||||||
|
if !canRetry || !internal.IsRetryableError(err, true) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstCmdsErr(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) pipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) {
|
||||||
|
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||||
|
if err := writeCmd(cn, cmds...); err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set read timeout for all commands.
|
||||||
|
cn.SetReadTimeout(c.opt.ReadTimeout)
|
||||||
|
return true, pipelineReadCmds(cn, cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pipelineReadCmds(cn *pool.Conn, cmds []Cmder) error {
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
err := cmd.readReply(cn)
|
||||||
|
if err != nil && !internal.IsRedisError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) txPipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) {
|
||||||
|
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||||
|
if err := txPipelineWriteMulti(cn, cmds); err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set read timeout for all commands.
|
||||||
|
cn.SetReadTimeout(c.opt.ReadTimeout)
|
||||||
|
|
||||||
|
if err := c.txPipelineReadQueued(cn, cmds); err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, pipelineReadCmds(cn, cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func txPipelineWriteMulti(cn *pool.Conn, cmds []Cmder) error {
|
||||||
|
multiExec := make([]Cmder, 0, len(cmds)+2)
|
||||||
|
multiExec = append(multiExec, NewStatusCmd("MULTI"))
|
||||||
|
multiExec = append(multiExec, cmds...)
|
||||||
|
multiExec = append(multiExec, NewSliceCmd("EXEC"))
|
||||||
|
return writeCmd(cn, multiExec...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) txPipelineReadQueued(cn *pool.Conn, cmds []Cmder) error {
|
||||||
|
// Parse queued replies.
|
||||||
|
var statusCmd StatusCmd
|
||||||
|
if err := statusCmd.readReply(cn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ = range cmds {
|
||||||
|
err := statusCmd.readReply(cn)
|
||||||
|
if err != nil && !internal.IsRedisError(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse number of replies.
|
||||||
|
line, err := cn.Rd.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err == Nil {
|
||||||
|
err = TxFailedErr
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch line[0] {
|
||||||
|
case proto.ErrorReply:
|
||||||
|
return proto.ParseErrorReply(line)
|
||||||
|
case proto.ArrayReply:
|
||||||
|
// ok
|
||||||
|
default:
|
||||||
|
err := fmt.Errorf("redis: expected '*', but got line %q", line)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Client is a Redis client representing a pool of zero or more
|
||||||
|
// underlying connections. It's safe for concurrent use by multiple
|
||||||
|
// goroutines.
|
||||||
|
type Client struct {
|
||||||
|
baseClient
|
||||||
|
cmdable
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a client to the Redis Server specified by Options.
|
||||||
|
func NewClient(opt *Options) *Client {
|
||||||
|
opt.init()
|
||||||
|
|
||||||
|
c := Client{
|
||||||
|
baseClient: baseClient{
|
||||||
|
opt: opt,
|
||||||
|
connPool: newConnPool(opt),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.baseClient.init()
|
||||||
|
c.init()
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) init() {
|
||||||
|
c.cmdable.setProcessor(c.Process)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Context() context.Context {
|
||||||
|
if c.ctx != nil {
|
||||||
|
return c.ctx
|
||||||
|
}
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) WithContext(ctx context.Context) *Client {
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
c2 := c.copy()
|
||||||
|
c2.ctx = ctx
|
||||||
|
return c2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) copy() *Client {
|
||||||
|
cp := *c
|
||||||
|
cp.init()
|
||||||
|
return &cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options returns read-only Options that were used to create the client.
|
||||||
|
func (c *Client) Options() *Options {
|
||||||
|
return c.opt
|
||||||
|
}
|
||||||
|
|
||||||
|
type PoolStats pool.Stats
|
||||||
|
|
||||||
|
// PoolStats returns connection pool stats.
|
||||||
|
func (c *Client) PoolStats() *PoolStats {
|
||||||
|
stats := c.connPool.Stats()
|
||||||
|
return (*PoolStats)(stats)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
exec: c.processPipeline,
|
||||||
|
}
|
||||||
|
pipe.statefulCmdable.setProcessor(pipe.Process)
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.TxPipeline().Pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
||||||
|
func (c *Client) TxPipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
exec: c.processTxPipeline,
|
||||||
|
}
|
||||||
|
pipe.statefulCmdable.setProcessor(pipe.Process)
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) pubSub() *PubSub {
|
||||||
|
return &PubSub{
|
||||||
|
opt: c.opt,
|
||||||
|
|
||||||
|
newConn: func(channels []string) (*pool.Conn, error) {
|
||||||
|
return c.newConn()
|
||||||
|
},
|
||||||
|
closeConn: c.connPool.CloseConn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes the client to the specified channels.
|
||||||
|
// Channels can be omitted to create empty subscription.
|
||||||
|
func (c *Client) Subscribe(channels ...string) *PubSub {
|
||||||
|
pubsub := c.pubSub()
|
||||||
|
if len(channels) > 0 {
|
||||||
|
_ = pubsub.Subscribe(channels...)
|
||||||
|
}
|
||||||
|
return pubsub
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSubscribe subscribes the client to the given patterns.
|
||||||
|
// Patterns can be omitted to create empty subscription.
|
||||||
|
func (c *Client) PSubscribe(channels ...string) *PubSub {
|
||||||
|
pubsub := c.pubSub()
|
||||||
|
if len(channels) > 0 {
|
||||||
|
_ = pubsub.PSubscribe(channels...)
|
||||||
|
}
|
||||||
|
return pubsub
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Conn is like Client, but its pool contains single connection.
|
||||||
|
type Conn struct {
|
||||||
|
baseClient
|
||||||
|
statefulCmdable
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConn(opt *Options, cn *pool.Conn) *Conn {
|
||||||
|
c := Conn{
|
||||||
|
baseClient: baseClient{
|
||||||
|
opt: opt,
|
||||||
|
connPool: pool.NewSingleConnPool(cn),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.baseClient.init()
|
||||||
|
c.statefulCmdable.setProcessor(c.Process)
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
exec: c.processPipeline,
|
||||||
|
}
|
||||||
|
pipe.statefulCmdable.setProcessor(pipe.Process)
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.TxPipeline().Pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
||||||
|
func (c *Conn) TxPipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
exec: c.processTxPipeline,
|
||||||
|
}
|
||||||
|
pipe.statefulCmdable.setProcessor(pipe.Process)
|
||||||
|
return &pipe
|
||||||
|
}
|
140
vendor/github.com/go-redis/redis/result.go
generated
vendored
Normal file
140
vendor/github.com/go-redis/redis/result.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// NewCmdResult returns a Cmd initialised with val and err for testing
|
||||||
|
func NewCmdResult(val interface{}, err error) *Cmd {
|
||||||
|
var cmd Cmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSliceResult returns a SliceCmd initialised with val and err for testing
|
||||||
|
func NewSliceResult(val []interface{}, err error) *SliceCmd {
|
||||||
|
var cmd SliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatusResult returns a StatusCmd initialised with val and err for testing
|
||||||
|
func NewStatusResult(val string, err error) *StatusCmd {
|
||||||
|
var cmd StatusCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIntResult returns an IntCmd initialised with val and err for testing
|
||||||
|
func NewIntResult(val int64, err error) *IntCmd {
|
||||||
|
var cmd IntCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDurationResult returns a DurationCmd initialised with val and err for testing
|
||||||
|
func NewDurationResult(val time.Duration, err error) *DurationCmd {
|
||||||
|
var cmd DurationCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBoolResult returns a BoolCmd initialised with val and err for testing
|
||||||
|
func NewBoolResult(val bool, err error) *BoolCmd {
|
||||||
|
var cmd BoolCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringResult returns a StringCmd initialised with val and err for testing
|
||||||
|
func NewStringResult(val string, err error) *StringCmd {
|
||||||
|
var cmd StringCmd
|
||||||
|
cmd.val = []byte(val)
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFloatResult returns a FloatCmd initialised with val and err for testing
|
||||||
|
func NewFloatResult(val float64, err error) *FloatCmd {
|
||||||
|
var cmd FloatCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringSliceResult returns a StringSliceCmd initialised with val and err for testing
|
||||||
|
func NewStringSliceResult(val []string, err error) *StringSliceCmd {
|
||||||
|
var cmd StringSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBoolSliceResult returns a BoolSliceCmd initialised with val and err for testing
|
||||||
|
func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd {
|
||||||
|
var cmd BoolSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringStringMapResult returns a StringStringMapCmd initialised with val and err for testing
|
||||||
|
func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd {
|
||||||
|
var cmd StringStringMapCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringIntMapCmdResult returns a StringIntMapCmd initialised with val and err for testing
|
||||||
|
func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd {
|
||||||
|
var cmd StringIntMapCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing
|
||||||
|
func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
|
||||||
|
var cmd ZSliceCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScanCmdResult returns a ScanCmd initialised with val and err for testing
|
||||||
|
func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd {
|
||||||
|
var cmd ScanCmd
|
||||||
|
cmd.page = keys
|
||||||
|
cmd.cursor = cursor
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClusterSlotsCmdResult returns a ClusterSlotsCmd initialised with val and err for testing
|
||||||
|
func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd {
|
||||||
|
var cmd ClusterSlotsCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGeoLocationCmdResult returns a GeoLocationCmd initialised with val and err for testing
|
||||||
|
func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd {
|
||||||
|
var cmd GeoLocationCmd
|
||||||
|
cmd.locations = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCommandsInfoCmdResult returns a CommandsInfoCmd initialised with val and err for testing
|
||||||
|
func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd {
|
||||||
|
var cmd CommandsInfoCmd
|
||||||
|
cmd.val = val
|
||||||
|
cmd.setErr(err)
|
||||||
|
return &cmd
|
||||||
|
}
|
569
vendor/github.com/go-redis/redis/ring.go
generated
vendored
Normal file
569
vendor/github.com/go-redis/redis/ring.go
generated
vendored
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal"
|
||||||
|
"github.com/go-redis/redis/internal/consistenthash"
|
||||||
|
"github.com/go-redis/redis/internal/hashtag"
|
||||||
|
"github.com/go-redis/redis/internal/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
const nreplicas = 100
|
||||||
|
|
||||||
|
var errRingShardsDown = errors.New("redis: all ring shards are down")
|
||||||
|
|
||||||
|
// RingOptions are used to configure a ring client and should be
|
||||||
|
// passed to NewRing.
|
||||||
|
type RingOptions struct {
|
||||||
|
// Map of name => host:port addresses of ring shards.
|
||||||
|
Addrs map[string]string
|
||||||
|
|
||||||
|
// Frequency of PING commands sent to check shards availability.
|
||||||
|
// Shard is considered down after 3 subsequent failed checks.
|
||||||
|
HeartbeatFrequency time.Duration
|
||||||
|
|
||||||
|
// Following options are copied from Options struct.
|
||||||
|
|
||||||
|
OnConnect func(*Conn) error
|
||||||
|
|
||||||
|
DB int
|
||||||
|
Password string
|
||||||
|
|
||||||
|
MaxRetries int
|
||||||
|
MinRetryBackoff time.Duration
|
||||||
|
MaxRetryBackoff time.Duration
|
||||||
|
|
||||||
|
DialTimeout time.Duration
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
|
||||||
|
PoolSize int
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *RingOptions) init() {
|
||||||
|
if opt.HeartbeatFrequency == 0 {
|
||||||
|
opt.HeartbeatFrequency = 500 * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
switch opt.MinRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MinRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MinRetryBackoff = 8 * time.Millisecond
|
||||||
|
}
|
||||||
|
switch opt.MaxRetryBackoff {
|
||||||
|
case -1:
|
||||||
|
opt.MaxRetryBackoff = 0
|
||||||
|
case 0:
|
||||||
|
opt.MaxRetryBackoff = 512 * time.Millisecond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *RingOptions) clientOptions() *Options {
|
||||||
|
return &Options{
|
||||||
|
OnConnect: opt.OnConnect,
|
||||||
|
|
||||||
|
DB: opt.DB,
|
||||||
|
Password: opt.Password,
|
||||||
|
|
||||||
|
DialTimeout: opt.DialTimeout,
|
||||||
|
ReadTimeout: opt.ReadTimeout,
|
||||||
|
WriteTimeout: opt.WriteTimeout,
|
||||||
|
|
||||||
|
PoolSize: opt.PoolSize,
|
||||||
|
PoolTimeout: opt.PoolTimeout,
|
||||||
|
IdleTimeout: opt.IdleTimeout,
|
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ringShard struct {
|
||||||
|
Client *Client
|
||||||
|
down int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *ringShard) String() string {
|
||||||
|
var state string
|
||||||
|
if shard.IsUp() {
|
||||||
|
state = "up"
|
||||||
|
} else {
|
||||||
|
state = "down"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s is %s", shard.Client, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *ringShard) IsDown() bool {
|
||||||
|
const threshold = 3
|
||||||
|
return atomic.LoadInt32(&shard.down) >= threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
func (shard *ringShard) IsUp() bool {
|
||||||
|
return !shard.IsDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vote votes to set shard state and returns true if state was changed.
|
||||||
|
func (shard *ringShard) Vote(up bool) bool {
|
||||||
|
if up {
|
||||||
|
changed := shard.IsDown()
|
||||||
|
atomic.StoreInt32(&shard.down, 0)
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
|
||||||
|
if shard.IsDown() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddInt32(&shard.down, 1)
|
||||||
|
return shard.IsDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type ringShards struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
hash *consistenthash.Map
|
||||||
|
shards map[string]*ringShard // read only
|
||||||
|
list []*ringShard // read only
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRingShards() *ringShards {
|
||||||
|
return &ringShards{
|
||||||
|
hash: consistenthash.New(nreplicas, nil),
|
||||||
|
shards: make(map[string]*ringShard),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Add(name string, cl *Client) {
|
||||||
|
shard := &ringShard{Client: cl}
|
||||||
|
c.hash.Add(name)
|
||||||
|
c.shards[name] = shard
|
||||||
|
c.list = append(c.list, shard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) List() []*ringShard {
|
||||||
|
c.mu.RLock()
|
||||||
|
list := c.list
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Hash(key string) string {
|
||||||
|
c.mu.RLock()
|
||||||
|
hash := c.hash.Get(key)
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) GetByKey(key string) (*ringShard, error) {
|
||||||
|
key = hashtag.Key(key)
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return nil, pool.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := c.hash.Get(key)
|
||||||
|
if hash == "" {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return nil, errRingShardsDown
|
||||||
|
}
|
||||||
|
|
||||||
|
shard := c.shards[hash]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
return shard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) GetByHash(name string) (*ringShard, error) {
|
||||||
|
if name == "" {
|
||||||
|
return c.Random()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
shard := c.shards[name]
|
||||||
|
c.mu.RUnlock()
|
||||||
|
return shard, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Random() (*ringShard, error) {
|
||||||
|
return c.GetByKey(strconv.Itoa(rand.Int()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// heartbeat monitors state of each shard in the ring.
|
||||||
|
func (c *ringShards) Heartbeat(frequency time.Duration) {
|
||||||
|
ticker := time.NewTicker(frequency)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for range ticker.C {
|
||||||
|
var rebalance bool
|
||||||
|
|
||||||
|
c.mu.RLock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
c.mu.RUnlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
shards := c.list
|
||||||
|
c.mu.RUnlock()
|
||||||
|
|
||||||
|
for _, shard := range shards {
|
||||||
|
err := shard.Client.Ping().Err()
|
||||||
|
if shard.Vote(err == nil || err == pool.ErrPoolTimeout) {
|
||||||
|
internal.Logf("ring shard state changed: %s", shard)
|
||||||
|
rebalance = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rebalance {
|
||||||
|
c.rebalance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rebalance removes dead shards from the Ring.
|
||||||
|
func (c *ringShards) rebalance() {
|
||||||
|
hash := consistenthash.New(nreplicas, nil)
|
||||||
|
for name, shard := range c.shards {
|
||||||
|
if shard.IsUp() {
|
||||||
|
hash.Add(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
c.hash = hash
|
||||||
|
c.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ringShards) Close() error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
if c.closed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.closed = true
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
for _, shard := range c.shards {
|
||||||
|
if err := shard.Client.Close(); err != nil && firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.hash = nil
|
||||||
|
c.shards = nil
|
||||||
|
c.list = nil
|
||||||
|
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Ring is a Redis client that uses constistent hashing to distribute
|
||||||
|
// keys across multiple Redis servers (shards). It's safe for
|
||||||
|
// concurrent use by multiple goroutines.
|
||||||
|
//
|
||||||
|
// Ring monitors the state of each shard and removes dead shards from
|
||||||
|
// the ring. When shard comes online it is added back to the ring. This
|
||||||
|
// gives you maximum availability and partition tolerance, but no
|
||||||
|
// consistency between different shards or even clients. Each client
|
||||||
|
// uses shards that are available to the client and does not do any
|
||||||
|
// coordination when shard state is changed.
|
||||||
|
//
|
||||||
|
// Ring should be used when you need multiple Redis servers for caching
|
||||||
|
// and can tolerate losing data when one of the servers dies.
|
||||||
|
// Otherwise you should use Redis Cluster.
|
||||||
|
type Ring struct {
|
||||||
|
cmdable
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
opt *RingOptions
|
||||||
|
shards *ringShards
|
||||||
|
cmdsInfoCache *cmdsInfoCache
|
||||||
|
|
||||||
|
processPipeline func([]Cmder) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRing(opt *RingOptions) *Ring {
|
||||||
|
opt.init()
|
||||||
|
|
||||||
|
ring := &Ring{
|
||||||
|
opt: opt,
|
||||||
|
shards: newRingShards(),
|
||||||
|
cmdsInfoCache: newCmdsInfoCache(),
|
||||||
|
}
|
||||||
|
ring.processPipeline = ring.defaultProcessPipeline
|
||||||
|
ring.cmdable.setProcessor(ring.Process)
|
||||||
|
|
||||||
|
for name, addr := range opt.Addrs {
|
||||||
|
clopt := opt.clientOptions()
|
||||||
|
clopt.Addr = addr
|
||||||
|
ring.shards.Add(name, NewClient(clopt))
|
||||||
|
}
|
||||||
|
|
||||||
|
go ring.shards.Heartbeat(opt.HeartbeatFrequency)
|
||||||
|
|
||||||
|
return ring
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Context() context.Context {
|
||||||
|
if c.ctx != nil {
|
||||||
|
return c.ctx
|
||||||
|
}
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) WithContext(ctx context.Context) *Ring {
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
c2 := c.copy()
|
||||||
|
c2.ctx = ctx
|
||||||
|
return c2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) copy() *Ring {
|
||||||
|
cp := *c
|
||||||
|
return &cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options returns read-only Options that were used to create the client.
|
||||||
|
func (c *Ring) Options() *RingOptions {
|
||||||
|
return c.opt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) retryBackoff(attempt int) time.Duration {
|
||||||
|
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolStats returns accumulated connection pool stats.
|
||||||
|
func (c *Ring) PoolStats() *PoolStats {
|
||||||
|
shards := c.shards.List()
|
||||||
|
var acc PoolStats
|
||||||
|
for _, shard := range shards {
|
||||||
|
s := shard.Client.connPool.Stats()
|
||||||
|
acc.Hits += s.Hits
|
||||||
|
acc.Misses += s.Misses
|
||||||
|
acc.Timeouts += s.Timeouts
|
||||||
|
acc.TotalConns += s.TotalConns
|
||||||
|
acc.FreeConns += s.FreeConns
|
||||||
|
}
|
||||||
|
return &acc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe subscribes the client to the specified channels.
|
||||||
|
func (c *Ring) Subscribe(channels ...string) *PubSub {
|
||||||
|
if len(channels) == 0 {
|
||||||
|
panic("at least one channel is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
shard, err := c.shards.GetByKey(channels[0])
|
||||||
|
if err != nil {
|
||||||
|
// TODO: return PubSub with sticky error
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return shard.Client.Subscribe(channels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSubscribe subscribes the client to the given patterns.
|
||||||
|
func (c *Ring) PSubscribe(channels ...string) *PubSub {
|
||||||
|
if len(channels) == 0 {
|
||||||
|
panic("at least one channel is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
shard, err := c.shards.GetByKey(channels[0])
|
||||||
|
if err != nil {
|
||||||
|
// TODO: return PubSub with sticky error
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return shard.Client.PSubscribe(channels...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEachShard concurrently calls the fn on each live shard in the ring.
|
||||||
|
// It returns the first error if any.
|
||||||
|
func (c *Ring) ForEachShard(fn func(client *Client) error) error {
|
||||||
|
shards := c.shards.List()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
for _, shard := range shards {
|
||||||
|
if shard.IsDown() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(shard *ringShard) {
|
||||||
|
defer wg.Done()
|
||||||
|
err := fn(shard.Client)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case errCh <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(shard)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) cmdInfo(name string) *CommandInfo {
|
||||||
|
cmdsInfo, err := c.cmdsInfoCache.Do(func() (map[string]*CommandInfo, error) {
|
||||||
|
shards := c.shards.List()
|
||||||
|
firstErr := errRingShardsDown
|
||||||
|
for _, shard := range shards {
|
||||||
|
cmdsInfo, err := shard.Client.Command().Result()
|
||||||
|
if err == nil {
|
||||||
|
return cmdsInfo, nil
|
||||||
|
}
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, firstErr
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
info := cmdsInfo[name]
|
||||||
|
if info == nil {
|
||||||
|
internal.Logf("info for cmd=%s not found", name)
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
|
||||||
|
cmdInfo := c.cmdInfo(cmd.Name())
|
||||||
|
pos := cmdFirstKeyPos(cmd, cmdInfo)
|
||||||
|
if pos == 0 {
|
||||||
|
return c.shards.Random()
|
||||||
|
}
|
||||||
|
firstKey := cmd.stringArg(pos)
|
||||||
|
return c.shards.GetByKey(firstKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) {
|
||||||
|
c.ForEachShard(func(c *Client) error {
|
||||||
|
c.WrapProcess(fn)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Process(cmd Cmder) error {
|
||||||
|
shard, err := c.cmdShard(cmd)
|
||||||
|
if err != nil {
|
||||||
|
cmd.setErr(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return shard.Client.Process(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
exec: c.processPipeline,
|
||||||
|
}
|
||||||
|
pipe.cmdable.setProcessor(pipe.Process)
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) WrapProcessPipeline(
|
||||||
|
fn func(oldProcess func([]Cmder) error) func([]Cmder) error,
|
||||||
|
) {
|
||||||
|
c.processPipeline = fn(c.processPipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) defaultProcessPipeline(cmds []Cmder) error {
|
||||||
|
cmdsMap := make(map[string][]Cmder)
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
cmdInfo := c.cmdInfo(cmd.Name())
|
||||||
|
hash := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo))
|
||||||
|
if hash != "" {
|
||||||
|
hash = c.shards.Hash(hashtag.Key(hash))
|
||||||
|
}
|
||||||
|
cmdsMap[hash] = append(cmdsMap[hash], cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
|
if attempt > 0 {
|
||||||
|
time.Sleep(c.retryBackoff(attempt))
|
||||||
|
}
|
||||||
|
|
||||||
|
var failedCmdsMap map[string][]Cmder
|
||||||
|
|
||||||
|
for hash, cmds := range cmdsMap {
|
||||||
|
shard, err := c.shards.GetByHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cn, _, err := shard.Client.getConn()
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
canRetry, err := shard.Client.pipelineProcessCmds(cn, cmds)
|
||||||
|
if err == nil || internal.IsRedisError(err) {
|
||||||
|
_ = shard.Client.connPool.Put(cn)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_ = shard.Client.connPool.Remove(cn)
|
||||||
|
|
||||||
|
if canRetry && internal.IsRetryableError(err, true) {
|
||||||
|
if failedCmdsMap == nil {
|
||||||
|
failedCmdsMap = make(map[string][]Cmder)
|
||||||
|
}
|
||||||
|
failedCmdsMap[hash] = cmds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(failedCmdsMap) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cmdsMap = failedCmdsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstCmdsErr(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) TxPipeline() Pipeliner {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the ring client, releasing any open resources.
|
||||||
|
//
|
||||||
|
// It is rare to Close a Ring, as the Ring is meant to be long-lived
|
||||||
|
// and shared between many goroutines.
|
||||||
|
func (c *Ring) Close() error {
|
||||||
|
return c.shards.Close()
|
||||||
|
}
|
62
vendor/github.com/go-redis/redis/script.go
generated
vendored
Normal file
62
vendor/github.com/go-redis/redis/script.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scripter interface {
|
||||||
|
Eval(script string, keys []string, args ...interface{}) *Cmd
|
||||||
|
EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
|
||||||
|
ScriptExists(hashes ...string) *BoolSliceCmd
|
||||||
|
ScriptLoad(script string) *StringCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ scripter = (*Client)(nil)
|
||||||
|
var _ scripter = (*Ring)(nil)
|
||||||
|
var _ scripter = (*ClusterClient)(nil)
|
||||||
|
|
||||||
|
type Script struct {
|
||||||
|
src, hash string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScript(src string) *Script {
|
||||||
|
h := sha1.New()
|
||||||
|
io.WriteString(h, src)
|
||||||
|
return &Script{
|
||||||
|
src: src,
|
||||||
|
hash: hex.EncodeToString(h.Sum(nil)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) Hash() string {
|
||||||
|
return s.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) Load(c scripter) *StringCmd {
|
||||||
|
return c.ScriptLoad(s.src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) Exists(c scripter) *BoolSliceCmd {
|
||||||
|
return c.ScriptExists(s.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd {
|
||||||
|
return c.Eval(s.src, keys, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Script) EvalSha(c scripter, keys []string, args ...interface{}) *Cmd {
|
||||||
|
return c.EvalSha(s.hash, keys, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run optimistically uses EVALSHA to run the script. If script does not exist
|
||||||
|
// it is retried using EVAL.
|
||||||
|
func (s *Script) Run(c scripter, keys []string, args ...interface{}) *Cmd {
|
||||||
|
r := s.EvalSha(c, keys, args...)
|
||||||
|
if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") {
|
||||||
|
return s.Eval(c, keys, args...)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
339
vendor/github.com/go-redis/redis/sentinel.go
generated
vendored
Normal file
339
vendor/github.com/go-redis/redis/sentinel.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/internal"
|
||||||
|
"github.com/go-redis/redis/internal/pool"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// FailoverOptions are used to configure a failover client and should
|
||||||
|
// be passed to NewFailoverClient.
|
||||||
|
type FailoverOptions struct {
|
||||||
|
// The master name.
|
||||||
|
MasterName string
|
||||||
|
// A seed list of host:port addresses of sentinel nodes.
|
||||||
|
SentinelAddrs []string
|
||||||
|
|
||||||
|
// Following options are copied from Options struct.
|
||||||
|
|
||||||
|
OnConnect func(*Conn) error
|
||||||
|
|
||||||
|
Password string
|
||||||
|
DB int
|
||||||
|
|
||||||
|
MaxRetries int
|
||||||
|
|
||||||
|
DialTimeout time.Duration
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
|
||||||
|
PoolSize int
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opt *FailoverOptions) options() *Options {
|
||||||
|
return &Options{
|
||||||
|
Addr: "FailoverClient",
|
||||||
|
|
||||||
|
OnConnect: opt.OnConnect,
|
||||||
|
|
||||||
|
DB: opt.DB,
|
||||||
|
Password: opt.Password,
|
||||||
|
|
||||||
|
MaxRetries: opt.MaxRetries,
|
||||||
|
|
||||||
|
DialTimeout: opt.DialTimeout,
|
||||||
|
ReadTimeout: opt.ReadTimeout,
|
||||||
|
WriteTimeout: opt.WriteTimeout,
|
||||||
|
|
||||||
|
PoolSize: opt.PoolSize,
|
||||||
|
PoolTimeout: opt.PoolTimeout,
|
||||||
|
IdleTimeout: opt.IdleTimeout,
|
||||||
|
IdleCheckFrequency: opt.IdleCheckFrequency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFailoverClient returns a Redis client that uses Redis Sentinel
|
||||||
|
// for automatic failover. It's safe for concurrent use by multiple
|
||||||
|
// goroutines.
|
||||||
|
func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
|
||||||
|
opt := failoverOpt.options()
|
||||||
|
opt.init()
|
||||||
|
|
||||||
|
failover := &sentinelFailover{
|
||||||
|
masterName: failoverOpt.MasterName,
|
||||||
|
sentinelAddrs: failoverOpt.SentinelAddrs,
|
||||||
|
|
||||||
|
opt: opt,
|
||||||
|
}
|
||||||
|
|
||||||
|
c := Client{
|
||||||
|
baseClient: baseClient{
|
||||||
|
opt: opt,
|
||||||
|
connPool: failover.Pool(),
|
||||||
|
|
||||||
|
onClose: func() error {
|
||||||
|
return failover.Close()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.baseClient.init()
|
||||||
|
c.setProcessor(c.Process)
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type sentinelClient struct {
|
||||||
|
cmdable
|
||||||
|
baseClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSentinel(opt *Options) *sentinelClient {
|
||||||
|
opt.init()
|
||||||
|
c := sentinelClient{
|
||||||
|
baseClient: baseClient{
|
||||||
|
opt: opt,
|
||||||
|
connPool: newConnPool(opt),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.baseClient.init()
|
||||||
|
c.cmdable.setProcessor(c.Process)
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sentinelClient) PubSub() *PubSub {
|
||||||
|
return &PubSub{
|
||||||
|
opt: c.opt,
|
||||||
|
|
||||||
|
newConn: func(channels []string) (*pool.Conn, error) {
|
||||||
|
return c.newConn()
|
||||||
|
},
|
||||||
|
closeConn: c.connPool.CloseConn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sentinelClient) GetMasterAddrByName(name string) *StringSliceCmd {
|
||||||
|
cmd := NewStringSliceCmd("SENTINEL", "get-master-addr-by-name", name)
|
||||||
|
c.Process(cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sentinelClient) Sentinels(name string) *SliceCmd {
|
||||||
|
cmd := NewSliceCmd("SENTINEL", "sentinels", name)
|
||||||
|
c.Process(cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
type sentinelFailover struct {
|
||||||
|
sentinelAddrs []string
|
||||||
|
|
||||||
|
opt *Options
|
||||||
|
|
||||||
|
pool *pool.ConnPool
|
||||||
|
poolOnce sync.Once
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
masterName string
|
||||||
|
_masterAddr string
|
||||||
|
sentinel *sentinelClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) Close() error {
|
||||||
|
return d.resetSentinel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) Pool() *pool.ConnPool {
|
||||||
|
d.poolOnce.Do(func() {
|
||||||
|
d.opt.Dialer = d.dial
|
||||||
|
d.pool = newConnPool(d.opt)
|
||||||
|
})
|
||||||
|
return d.pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) dial() (net.Conn, error) {
|
||||||
|
addr, err := d.MasterAddr()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return net.DialTimeout("tcp", addr, d.opt.DialTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) MasterAddr() (string, error) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
addr, err := d.masterAddr()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d._masterAddr != addr {
|
||||||
|
d.switchMaster(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) masterAddr() (string, error) {
|
||||||
|
// Try last working sentinel.
|
||||||
|
if d.sentinel != nil {
|
||||||
|
addr, err := d.sentinel.GetMasterAddrByName(d.masterName).Result()
|
||||||
|
if err == nil {
|
||||||
|
addr := net.JoinHostPort(addr[0], addr[1])
|
||||||
|
internal.Logf("sentinel: master=%q addr=%q", d.masterName, addr)
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.Logf("sentinel: GetMasterAddrByName name=%q failed: %s", d.masterName, err)
|
||||||
|
d._resetSentinel()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sentinelAddr := range d.sentinelAddrs {
|
||||||
|
sentinel := newSentinel(&Options{
|
||||||
|
Addr: sentinelAddr,
|
||||||
|
|
||||||
|
DialTimeout: d.opt.DialTimeout,
|
||||||
|
ReadTimeout: d.opt.ReadTimeout,
|
||||||
|
WriteTimeout: d.opt.WriteTimeout,
|
||||||
|
|
||||||
|
PoolSize: d.opt.PoolSize,
|
||||||
|
PoolTimeout: d.opt.PoolTimeout,
|
||||||
|
IdleTimeout: d.opt.IdleTimeout,
|
||||||
|
})
|
||||||
|
|
||||||
|
masterAddr, err := sentinel.GetMasterAddrByName(d.masterName).Result()
|
||||||
|
if err != nil {
|
||||||
|
internal.Logf("sentinel: GetMasterAddrByName master=%q failed: %s", d.masterName, err)
|
||||||
|
sentinel.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push working sentinel to the top.
|
||||||
|
d.sentinelAddrs[0], d.sentinelAddrs[i] = d.sentinelAddrs[i], d.sentinelAddrs[0]
|
||||||
|
d.setSentinel(sentinel)
|
||||||
|
|
||||||
|
addr := net.JoinHostPort(masterAddr[0], masterAddr[1])
|
||||||
|
return addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("redis: all sentinels are unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) switchMaster(masterAddr string) {
|
||||||
|
internal.Logf(
|
||||||
|
"sentinel: new master=%q addr=%q",
|
||||||
|
d.masterName, masterAddr,
|
||||||
|
)
|
||||||
|
_ = d.Pool().Filter(func(cn *pool.Conn) bool {
|
||||||
|
return cn.RemoteAddr().String() != masterAddr
|
||||||
|
})
|
||||||
|
d._masterAddr = masterAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) setSentinel(sentinel *sentinelClient) {
|
||||||
|
d.discoverSentinels(sentinel)
|
||||||
|
d.sentinel = sentinel
|
||||||
|
go d.listen(sentinel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) resetSentinel() error {
|
||||||
|
var err error
|
||||||
|
d.mu.Lock()
|
||||||
|
if d.sentinel != nil {
|
||||||
|
err = d._resetSentinel()
|
||||||
|
}
|
||||||
|
d.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) _resetSentinel() error {
|
||||||
|
err := d.sentinel.Close()
|
||||||
|
d.sentinel = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) discoverSentinels(sentinel *sentinelClient) {
|
||||||
|
sentinels, err := sentinel.Sentinels(d.masterName).Result()
|
||||||
|
if err != nil {
|
||||||
|
internal.Logf("sentinel: Sentinels master=%q failed: %s", d.masterName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, sentinel := range sentinels {
|
||||||
|
vals := sentinel.([]interface{})
|
||||||
|
for i := 0; i < len(vals); i += 2 {
|
||||||
|
key := vals[i].(string)
|
||||||
|
if key == "name" {
|
||||||
|
sentinelAddr := vals[i+1].(string)
|
||||||
|
if !contains(d.sentinelAddrs, sentinelAddr) {
|
||||||
|
internal.Logf(
|
||||||
|
"sentinel: discovered new sentinel=%q for master=%q",
|
||||||
|
sentinelAddr, d.masterName,
|
||||||
|
)
|
||||||
|
d.sentinelAddrs = append(d.sentinelAddrs, sentinelAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sentinelFailover) listen(sentinel *sentinelClient) {
|
||||||
|
var pubsub *PubSub
|
||||||
|
for {
|
||||||
|
if pubsub == nil {
|
||||||
|
pubsub = sentinel.PubSub()
|
||||||
|
|
||||||
|
if err := pubsub.Subscribe("+switch-master"); err != nil {
|
||||||
|
internal.Logf("sentinel: Subscribe failed: %s", err)
|
||||||
|
pubsub.Close()
|
||||||
|
d.resetSentinel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := pubsub.ReceiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
if err != pool.ErrClosed {
|
||||||
|
internal.Logf("sentinel: ReceiveMessage failed: %s", err)
|
||||||
|
pubsub.Close()
|
||||||
|
}
|
||||||
|
d.resetSentinel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg.Channel {
|
||||||
|
case "+switch-master":
|
||||||
|
parts := strings.Split(msg.Payload, " ")
|
||||||
|
if parts[0] != d.masterName {
|
||||||
|
internal.Logf("sentinel: ignore addr for master=%q", parts[0])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr := net.JoinHostPort(parts[3], parts[4])
|
||||||
|
|
||||||
|
d.mu.Lock()
|
||||||
|
if d._masterAddr != addr {
|
||||||
|
d.switchMaster(addr)
|
||||||
|
}
|
||||||
|
d.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(slice []string, str string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == str {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
104
vendor/github.com/go-redis/redis/tx.go
generated
vendored
Normal file
104
vendor/github.com/go-redis/redis/tx.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-redis/redis/internal/pool"
|
||||||
|
"github.com/go-redis/redis/internal/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TxFailedErr transaction redis failed.
|
||||||
|
const TxFailedErr = proto.RedisError("redis: transaction failed")
|
||||||
|
|
||||||
|
// Tx implements Redis transactions as described in
|
||||||
|
// http://redis.io/topics/transactions. It's NOT safe for concurrent use
|
||||||
|
// by multiple goroutines, because Exec resets list of watched keys.
|
||||||
|
// If you don't need WATCH it is better to use Pipeline.
|
||||||
|
type Tx struct {
|
||||||
|
statefulCmdable
|
||||||
|
baseClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) newTx() *Tx {
|
||||||
|
tx := Tx{
|
||||||
|
baseClient: baseClient{
|
||||||
|
opt: c.opt,
|
||||||
|
connPool: pool.NewStickyConnPool(c.connPool.(*pool.ConnPool), true),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tx.baseClient.init()
|
||||||
|
tx.statefulCmdable.setProcessor(tx.Process)
|
||||||
|
return &tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Watch(fn func(*Tx) error, keys ...string) error {
|
||||||
|
tx := c.newTx()
|
||||||
|
if len(keys) > 0 {
|
||||||
|
if err := tx.Watch(keys...).Err(); err != nil {
|
||||||
|
_ = tx.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := fn(tx)
|
||||||
|
_ = tx.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the transaction, releasing any open resources.
|
||||||
|
func (c *Tx) Close() error {
|
||||||
|
_ = c.Unwatch().Err()
|
||||||
|
return c.baseClient.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch marks the keys to be watched for conditional execution
|
||||||
|
// of a transaction.
|
||||||
|
func (c *Tx) Watch(keys ...string) *StatusCmd {
|
||||||
|
args := make([]interface{}, 1+len(keys))
|
||||||
|
args[0] = "watch"
|
||||||
|
for i, key := range keys {
|
||||||
|
args[1+i] = key
|
||||||
|
}
|
||||||
|
cmd := NewStatusCmd(args...)
|
||||||
|
c.Process(cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwatch flushes all the previously watched keys for a transaction.
|
||||||
|
func (c *Tx) Unwatch(keys ...string) *StatusCmd {
|
||||||
|
args := make([]interface{}, 1+len(keys))
|
||||||
|
args[0] = "unwatch"
|
||||||
|
for i, key := range keys {
|
||||||
|
args[1+i] = key
|
||||||
|
}
|
||||||
|
cmd := NewStatusCmd(args...)
|
||||||
|
c.Process(cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tx) Pipeline() Pipeliner {
|
||||||
|
pipe := Pipeline{
|
||||||
|
exec: c.processTxPipeline,
|
||||||
|
}
|
||||||
|
pipe.statefulCmdable.setProcessor(pipe.Process)
|
||||||
|
return &pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pipelined executes commands queued in the fn in a transaction
|
||||||
|
// and restores the connection state to normal.
|
||||||
|
//
|
||||||
|
// When using WATCH, EXEC will execute commands only if the watched keys
|
||||||
|
// were not modified, allowing for a check-and-set mechanism.
|
||||||
|
//
|
||||||
|
// Exec always returns list of commands. If transaction fails
|
||||||
|
// TxFailedErr is returned. Otherwise Exec returns error of the first
|
||||||
|
// failed command or nil.
|
||||||
|
func (c *Tx) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipeline().Pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tx) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
return c.Pipelined(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tx) TxPipeline() Pipeliner {
|
||||||
|
return c.Pipeline()
|
||||||
|
}
|
143
vendor/github.com/go-redis/redis/universal.go
generated
vendored
Normal file
143
vendor/github.com/go-redis/redis/universal.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// UniversalOptions information is required by UniversalClient to establish
|
||||||
|
// connections.
|
||||||
|
type UniversalOptions struct {
|
||||||
|
// Either a single address or a seed list of host:port addresses
|
||||||
|
// of cluster/sentinel nodes.
|
||||||
|
Addrs []string
|
||||||
|
|
||||||
|
// The sentinel master name.
|
||||||
|
// Only failover clients.
|
||||||
|
MasterName string
|
||||||
|
|
||||||
|
// Database to be selected after connecting to the server.
|
||||||
|
// Only single-node and failover clients.
|
||||||
|
DB int
|
||||||
|
|
||||||
|
// Only cluster clients.
|
||||||
|
|
||||||
|
// Enables read only queries on slave nodes.
|
||||||
|
ReadOnly bool
|
||||||
|
|
||||||
|
MaxRedirects int
|
||||||
|
RouteByLatency bool
|
||||||
|
|
||||||
|
// Common options
|
||||||
|
|
||||||
|
OnConnect func(*Conn) error
|
||||||
|
MaxRetries int
|
||||||
|
Password string
|
||||||
|
DialTimeout time.Duration
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
PoolSize int
|
||||||
|
PoolTimeout time.Duration
|
||||||
|
IdleTimeout time.Duration
|
||||||
|
IdleCheckFrequency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UniversalOptions) cluster() *ClusterOptions {
|
||||||
|
if len(o.Addrs) == 0 {
|
||||||
|
o.Addrs = []string{"127.0.0.1:6379"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ClusterOptions{
|
||||||
|
Addrs: o.Addrs,
|
||||||
|
MaxRedirects: o.MaxRedirects,
|
||||||
|
RouteByLatency: o.RouteByLatency,
|
||||||
|
ReadOnly: o.ReadOnly,
|
||||||
|
|
||||||
|
OnConnect: o.OnConnect,
|
||||||
|
MaxRetries: o.MaxRetries,
|
||||||
|
Password: o.Password,
|
||||||
|
DialTimeout: o.DialTimeout,
|
||||||
|
ReadTimeout: o.ReadTimeout,
|
||||||
|
WriteTimeout: o.WriteTimeout,
|
||||||
|
PoolSize: o.PoolSize,
|
||||||
|
PoolTimeout: o.PoolTimeout,
|
||||||
|
IdleTimeout: o.IdleTimeout,
|
||||||
|
IdleCheckFrequency: o.IdleCheckFrequency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UniversalOptions) failover() *FailoverOptions {
|
||||||
|
if len(o.Addrs) == 0 {
|
||||||
|
o.Addrs = []string{"127.0.0.1:26379"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FailoverOptions{
|
||||||
|
SentinelAddrs: o.Addrs,
|
||||||
|
MasterName: o.MasterName,
|
||||||
|
DB: o.DB,
|
||||||
|
|
||||||
|
OnConnect: o.OnConnect,
|
||||||
|
MaxRetries: o.MaxRetries,
|
||||||
|
Password: o.Password,
|
||||||
|
DialTimeout: o.DialTimeout,
|
||||||
|
ReadTimeout: o.ReadTimeout,
|
||||||
|
WriteTimeout: o.WriteTimeout,
|
||||||
|
PoolSize: o.PoolSize,
|
||||||
|
PoolTimeout: o.PoolTimeout,
|
||||||
|
IdleTimeout: o.IdleTimeout,
|
||||||
|
IdleCheckFrequency: o.IdleCheckFrequency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *UniversalOptions) simple() *Options {
|
||||||
|
addr := "127.0.0.1:6379"
|
||||||
|
if len(o.Addrs) > 0 {
|
||||||
|
addr = o.Addrs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Options{
|
||||||
|
Addr: addr,
|
||||||
|
DB: o.DB,
|
||||||
|
|
||||||
|
OnConnect: o.OnConnect,
|
||||||
|
MaxRetries: o.MaxRetries,
|
||||||
|
Password: o.Password,
|
||||||
|
DialTimeout: o.DialTimeout,
|
||||||
|
ReadTimeout: o.ReadTimeout,
|
||||||
|
WriteTimeout: o.WriteTimeout,
|
||||||
|
PoolSize: o.PoolSize,
|
||||||
|
PoolTimeout: o.PoolTimeout,
|
||||||
|
IdleTimeout: o.IdleTimeout,
|
||||||
|
IdleCheckFrequency: o.IdleCheckFrequency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// UniversalClient is an abstract client which - based on the provided options -
|
||||||
|
// can connect to either clusters, or sentinel-backed failover instances or simple
|
||||||
|
// single-instance servers. This can be useful for testing cluster-specific
|
||||||
|
// applications locally.
|
||||||
|
type UniversalClient interface {
|
||||||
|
Cmdable
|
||||||
|
Process(cmd Cmder) error
|
||||||
|
WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error)
|
||||||
|
Subscribe(channels ...string) *PubSub
|
||||||
|
PSubscribe(channels ...string) *PubSub
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UniversalClient = (*Client)(nil)
|
||||||
|
var _ UniversalClient = (*ClusterClient)(nil)
|
||||||
|
|
||||||
|
// NewUniversalClient returns a new multi client. The type of client returned depends
|
||||||
|
// on the following three conditions:
|
||||||
|
//
|
||||||
|
// 1. if a MasterName is passed a sentinel-backed FailoverClient will be returned
|
||||||
|
// 2. if the number of Addrs is two or more, a ClusterClient will be returned
|
||||||
|
// 3. otherwise, a single-node redis Client will be returned.
|
||||||
|
func NewUniversalClient(opts *UniversalOptions) UniversalClient {
|
||||||
|
if opts.MasterName != "" {
|
||||||
|
return NewFailoverClient(opts.failover())
|
||||||
|
} else if len(opts.Addrs) > 1 {
|
||||||
|
return NewClusterClient(opts.cluster())
|
||||||
|
}
|
||||||
|
return NewClient(opts.simple())
|
||||||
|
}
|
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||||
|
2898 / PKCS #5 v2.0.
|
||||||
|
|
||||||
|
A key derivation function is useful when encrypting data based on a password
|
||||||
|
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||||
|
a secure encryption key based on the password.
|
||||||
|
|
||||||
|
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||||
|
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||||
|
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||||
|
choose, you can pass the `New` functions from the different SHA packages to
|
||||||
|
pbkdf2.Key.
|
||||||
|
*/
|
||||||
|
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key derives a key from the password, salt and iteration count, returning a
|
||||||
|
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||||
|
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||||
|
// the supplied hash function.
|
||||||
|
//
|
||||||
|
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||||
|
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||||
|
// doing:
|
||||||
|
//
|
||||||
|
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||||
|
//
|
||||||
|
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||||
|
// RFC.
|
||||||
|
//
|
||||||
|
// Using a higher iteration count will increase the cost of an exhaustive
|
||||||
|
// search but will also make derivation proportionally slower.
|
||||||
|
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(h, password)
|
||||||
|
hashLen := prf.Size()
|
||||||
|
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
dk := make([]byte, 0, numBlocks*hashLen)
|
||||||
|
U := make([]byte, hashLen)
|
||||||
|
for block := 1; block <= numBlocks; block++ {
|
||||||
|
// N.B.: || means concatenation, ^ means XOR
|
||||||
|
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||||
|
// U_1 = PRF(password, salt || uint(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
buf[0] = byte(block >> 24)
|
||||||
|
buf[1] = byte(block >> 16)
|
||||||
|
buf[2] = byte(block >> 8)
|
||||||
|
buf[3] = byte(block)
|
||||||
|
prf.Write(buf[:4])
|
||||||
|
dk = prf.Sum(dk)
|
||||||
|
T := dk[len(dk)-hashLen:]
|
||||||
|
copy(U, T)
|
||||||
|
|
||||||
|
// U_n = PRF(password, U_(n-1))
|
||||||
|
for n := 2; n <= iter; n++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(U)
|
||||||
|
U = U[:0]
|
||||||
|
U = prf.Sum(U)
|
||||||
|
for x := range U {
|
||||||
|
T[x] ^= U[x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dk[:keyLen]
|
||||||
|
}
|
54
vendor/vendor.json
vendored
54
vendor/vendor.json
vendored
@ -32,6 +32,54 @@
|
|||||||
"version": "v1.2",
|
"version": "v1.2",
|
||||||
"versionExact": "v1.2"
|
"versionExact": "v1.2"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "szDec1GjS2Xj4BU3R36PAwdR2Jg=",
|
||||||
|
"path": "github.com/go-redis/redis",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "L/n8TwOiRwhhL2TvFNafxB6rAwY=",
|
||||||
|
"path": "github.com/go-redis/redis/internal",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "GQZsUVg/+6UpQAYpc4luMvMutSI=",
|
||||||
|
"path": "github.com/go-redis/redis/internal/consistenthash",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "GE9tg94JwCzMbBwn3Q2LaFs5Rx0=",
|
||||||
|
"path": "github.com/go-redis/redis/internal/hashtag",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "AtcctlDri3zLh6U5sDnT4NULEtw=",
|
||||||
|
"path": "github.com/go-redis/redis/internal/pool",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "2DZs/18lBWzZk2rxWLVtPUzNXhs=",
|
||||||
|
"path": "github.com/go-redis/redis/internal/proto",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "sjbjSJrTdvyDwNjrWjTNeXgUhaU=",
|
||||||
|
"path": "github.com/go-redis/redis/internal/singleflight",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "SvyqZrzHTVxCIBoawql2ucvfWLE=",
|
||||||
|
"path": "github.com/go-redis/redis/internal/util",
|
||||||
|
"revision": "9ccc23344a52164531ed90362e2516b798e3296c",
|
||||||
|
"revisionTime": "2018-04-17T06:18:16Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "JvvhHFvLoEw1DtQCr6Puu41VQaQ=",
|
"checksumSHA1": "JvvhHFvLoEw1DtQCr6Puu41VQaQ=",
|
||||||
"path": "github.com/go-xorm/builder",
|
"path": "github.com/go-xorm/builder",
|
||||||
@ -80,6 +128,12 @@
|
|||||||
"revision": "f3cacc17c85ecb7f1b6a9e373ee85d1480919868",
|
"revision": "f3cacc17c85ecb7f1b6a9e373ee85d1480919868",
|
||||||
"revisionTime": "2018-04-07T10:30:00Z"
|
"revisionTime": "2018-04-07T10:30:00Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "1MGpGDQqnUoRpv7VEcQrXOBydXE=",
|
||||||
|
"path": "golang.org/x/crypto/pbkdf2",
|
||||||
|
"revision": "d6449816ce06963d9d136eee5a56fca5b0616e7e",
|
||||||
|
"revisionTime": "2018-04-11T15:42:50Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "31n/IvwJf+PtXWf1EPwQYcde1jY=",
|
"checksumSHA1": "31n/IvwJf+PtXWf1EPwQYcde1jY=",
|
||||||
"path": "golang.org/x/sys/unix",
|
"path": "golang.org/x/sys/unix",
|
||||||
|
Loading…
Reference in New Issue
Block a user