Depenancy upgrades and movign to dep. (#8630)
Этот коммит содержится в:
коммит произвёл
Joram Wilander
родитель
bf24f51c4e
Коммит
6e2cb00008
1
vendor/github.com/go-redis/redis/.travis.yml
сгенерированный
поставляемый
1
vendor/github.com/go-redis/redis/.travis.yml
сгенерированный
поставляемый
@@ -8,6 +8,7 @@ go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
|
||||
3
vendor/github.com/go-redis/redis/Makefile
сгенерированный
поставляемый
3
vendor/github.com/go-redis/redis/Makefile
сгенерированный
поставляемый
@@ -1,6 +1,7 @@
|
||||
all: testdeps
|
||||
go test ./...
|
||||
go test ./... -short -race
|
||||
env GOOS=linux GOARCH=386 go test ./...
|
||||
go vet
|
||||
|
||||
testdeps: testdata/redis/src/redis-server
|
||||
@@ -15,5 +16,5 @@ testdata/redis:
|
||||
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 's/libjemalloc.a/libjemalloc.a -lrt/g' $</src/Makefile
|
||||
sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $</src/Makefile
|
||||
cd $< && make all
|
||||
|
||||
216
vendor/github.com/go-redis/redis/bench_test.go
сгенерированный
поставляемый
216
vendor/github.com/go-redis/redis/bench_test.go
сгенерированный
поставляемый
@@ -1,216 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
func benchmarkRedisClient(poolSize int) *redis.Client {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: ":6379",
|
||||
DialTimeout: time.Second,
|
||||
ReadTimeout: time.Second,
|
||||
WriteTimeout: time.Second,
|
||||
PoolSize: poolSize,
|
||||
})
|
||||
if err := client.FlushDB().Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func BenchmarkRedisPing(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Ping().Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRedisSetString(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
value := string(bytes.Repeat([]byte{'1'}, 10000))
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Set("key", value, 0).Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRedisGetNil(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Get("key").Err(); err != redis.Nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func benchmarkSetRedis(b *testing.B, poolSize, payloadSize int) {
|
||||
client := benchmarkRedisClient(poolSize)
|
||||
defer client.Close()
|
||||
|
||||
value := string(bytes.Repeat([]byte{'1'}, payloadSize))
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Set("key", value, 0).Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis10Conns64Bytes(b *testing.B) {
|
||||
benchmarkSetRedis(b, 10, 64)
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis100Conns64Bytes(b *testing.B) {
|
||||
benchmarkSetRedis(b, 100, 64)
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis10Conns1KB(b *testing.B) {
|
||||
benchmarkSetRedis(b, 10, 1024)
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis100Conns1KB(b *testing.B) {
|
||||
benchmarkSetRedis(b, 100, 1024)
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis10Conns10KB(b *testing.B) {
|
||||
benchmarkSetRedis(b, 10, 10*1024)
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis100Conns10KB(b *testing.B) {
|
||||
benchmarkSetRedis(b, 100, 10*1024)
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis10Conns1MB(b *testing.B) {
|
||||
benchmarkSetRedis(b, 10, 1024*1024)
|
||||
}
|
||||
|
||||
func BenchmarkSetRedis100Conns1MB(b *testing.B) {
|
||||
benchmarkSetRedis(b, 100, 1024*1024)
|
||||
}
|
||||
|
||||
func BenchmarkRedisSetGetBytes(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
value := bytes.Repeat([]byte{'1'}, 10000)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Set("key", value, 0).Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := client.Get("key").Bytes()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, value) {
|
||||
b.Fatalf("got != value")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRedisMGet(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
if err := client.MSet("key1", "hello1", "key2", "hello2").Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.MGet("key1", "key2").Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSetExpire(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Set("key", "hello", 0).Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := client.Expire("key", time.Second).Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPipeline(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Set("key", "hello", 0)
|
||||
pipe.Expire("key", time.Second)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkZAdd(b *testing.B) {
|
||||
client := benchmarkRedisClient(10)
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.ZAdd("key", redis.Z{float64(1), "hello"}).Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
573
vendor/github.com/go-redis/redis/cluster.go
сгенерированный
поставляемый
573
vendor/github.com/go-redis/redis/cluster.go
сгенерированный
поставляемый
@@ -1,7 +1,10 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
@@ -12,10 +15,10 @@ import (
|
||||
"github.com/go-redis/redis/internal/hashtag"
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
"github.com/go-redis/redis/internal/proto"
|
||||
"github.com/go-redis/redis/internal/singleflight"
|
||||
)
|
||||
|
||||
var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes")
|
||||
var errNilClusterState = fmt.Errorf("redis: cannot load cluster slots")
|
||||
|
||||
// ClusterOptions are used to configure a cluster client and should be
|
||||
// passed to NewClusterClient.
|
||||
@@ -25,13 +28,15 @@ type ClusterOptions struct {
|
||||
|
||||
// The maximum number of retries before giving up. Command is retried
|
||||
// on network errors and MOVED/ASK redirects.
|
||||
// Default is 16.
|
||||
// Default is 8.
|
||||
MaxRedirects int
|
||||
|
||||
// Enables read-only commands on slave nodes.
|
||||
ReadOnly bool
|
||||
// Allows routing read-only commands to the closest master or slave node.
|
||||
RouteByLatency bool
|
||||
// Allows routing read-only commands to the random master or slave node.
|
||||
RouteRandomly bool
|
||||
|
||||
// Following options are copied from Options struct.
|
||||
|
||||
@@ -57,7 +62,7 @@ func (opt *ClusterOptions) init() {
|
||||
if opt.MaxRedirects == -1 {
|
||||
opt.MaxRedirects = 0
|
||||
} else if opt.MaxRedirects == 0 {
|
||||
opt.MaxRedirects = 16
|
||||
opt.MaxRedirects = 8
|
||||
}
|
||||
|
||||
if opt.RouteByLatency {
|
||||
@@ -118,11 +123,11 @@ func (opt *ClusterOptions) clientOptions() *Options {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type clusterNode struct {
|
||||
Client *Client
|
||||
Latency time.Duration
|
||||
Client *Client
|
||||
|
||||
loading time.Time
|
||||
generation uint32
|
||||
latency uint32 // atomic
|
||||
generation uint32 // atomic
|
||||
loading uint32 // atomic
|
||||
}
|
||||
|
||||
func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode {
|
||||
@@ -132,36 +137,69 @@ func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode {
|
||||
Client: NewClient(opt),
|
||||
}
|
||||
|
||||
node.latency = math.MaxUint32
|
||||
if clOpt.RouteByLatency {
|
||||
node.updateLatency()
|
||||
go node.updateLatency()
|
||||
}
|
||||
|
||||
return &node
|
||||
}
|
||||
|
||||
func (n *clusterNode) Close() error {
|
||||
return n.Client.Close()
|
||||
}
|
||||
|
||||
func (n *clusterNode) Test() error {
|
||||
return n.Client.ClusterInfo().Err()
|
||||
}
|
||||
|
||||
func (n *clusterNode) updateLatency() {
|
||||
const probes = 10
|
||||
|
||||
var latency uint32
|
||||
for i := 0; i < probes; i++ {
|
||||
start := time.Now()
|
||||
n.Client.Ping()
|
||||
n.Latency += time.Since(start)
|
||||
probe := uint32(time.Since(start) / time.Microsecond)
|
||||
latency = (latency + probe) / 2
|
||||
}
|
||||
n.Latency = n.Latency / probes
|
||||
atomic.StoreUint32(&n.latency, latency)
|
||||
}
|
||||
|
||||
func (n *clusterNode) Latency() time.Duration {
|
||||
latency := atomic.LoadUint32(&n.latency)
|
||||
return time.Duration(latency) * time.Microsecond
|
||||
}
|
||||
|
||||
func (n *clusterNode) MarkAsLoading() {
|
||||
atomic.StoreUint32(&n.loading, uint32(time.Now().Unix()))
|
||||
}
|
||||
|
||||
func (n *clusterNode) Loading() bool {
|
||||
return !n.loading.IsZero() && time.Since(n.loading) < time.Minute
|
||||
const minute = int64(time.Minute / time.Second)
|
||||
|
||||
loading := atomic.LoadUint32(&n.loading)
|
||||
if loading == 0 {
|
||||
return false
|
||||
}
|
||||
if time.Now().Unix()-int64(loading) < minute {
|
||||
return true
|
||||
}
|
||||
atomic.StoreUint32(&n.loading, 0)
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *clusterNode) Generation() uint32 {
|
||||
return n.generation
|
||||
return atomic.LoadUint32(&n.generation)
|
||||
}
|
||||
|
||||
func (n *clusterNode) SetGeneration(gen uint32) {
|
||||
if gen < n.generation {
|
||||
panic("gen < n.generation")
|
||||
for {
|
||||
v := atomic.LoadUint32(&n.generation)
|
||||
if gen < v || atomic.CompareAndSwapUint32(&n.generation, v, gen) {
|
||||
break
|
||||
}
|
||||
}
|
||||
n.generation = gen
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -169,18 +207,23 @@ func (n *clusterNode) SetGeneration(gen uint32) {
|
||||
type clusterNodes struct {
|
||||
opt *ClusterOptions
|
||||
|
||||
mu sync.RWMutex
|
||||
addrs []string
|
||||
nodes map[string]*clusterNode
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
allAddrs []string
|
||||
allNodes map[string]*clusterNode
|
||||
clusterAddrs []string
|
||||
closed bool
|
||||
|
||||
nodeCreateGroup singleflight.Group
|
||||
|
||||
generation uint32
|
||||
}
|
||||
|
||||
func newClusterNodes(opt *ClusterOptions) *clusterNodes {
|
||||
return &clusterNodes{
|
||||
opt: opt,
|
||||
nodes: make(map[string]*clusterNode),
|
||||
opt: opt,
|
||||
|
||||
allAddrs: opt.Addrs,
|
||||
allNodes: make(map[string]*clusterNode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,21 +237,29 @@ func (c *clusterNodes) Close() error {
|
||||
c.closed = true
|
||||
|
||||
var firstErr error
|
||||
for _, node := range c.nodes {
|
||||
for _, node := range c.allNodes {
|
||||
if err := node.Client.Close(); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
c.addrs = nil
|
||||
c.nodes = nil
|
||||
|
||||
c.allNodes = nil
|
||||
c.clusterAddrs = nil
|
||||
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func (c *clusterNodes) Addrs() ([]string, error) {
|
||||
var addrs []string
|
||||
c.mu.RLock()
|
||||
closed := c.closed
|
||||
addrs := c.addrs
|
||||
if !closed {
|
||||
if len(c.clusterAddrs) > 0 {
|
||||
addrs = c.clusterAddrs
|
||||
} else {
|
||||
addrs = c.allAddrs
|
||||
}
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
|
||||
if closed {
|
||||
@@ -229,25 +280,66 @@ func (c *clusterNodes) NextGeneration() uint32 {
|
||||
func (c *clusterNodes) GC(generation uint32) {
|
||||
var collected []*clusterNode
|
||||
c.mu.Lock()
|
||||
for i := 0; i < len(c.addrs); {
|
||||
addr := c.addrs[i]
|
||||
node := c.nodes[addr]
|
||||
for addr, node := range c.allNodes {
|
||||
if node.Generation() >= generation {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
c.addrs = append(c.addrs[:i], c.addrs[i+1:]...)
|
||||
delete(c.nodes, addr)
|
||||
c.clusterAddrs = remove(c.clusterAddrs, addr)
|
||||
delete(c.allNodes, addr)
|
||||
collected = append(collected, node)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
time.AfterFunc(time.Minute, func() {
|
||||
for _, node := range collected {
|
||||
_ = node.Client.Close()
|
||||
}
|
||||
for _, node := range collected {
|
||||
_ = node.Client.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) {
|
||||
var node *clusterNode
|
||||
var err error
|
||||
|
||||
c.mu.RLock()
|
||||
if c.closed {
|
||||
err = pool.ErrClosed
|
||||
} else {
|
||||
node = c.allNodes[addr]
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node != nil {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
v, err := c.nodeCreateGroup.Do(addr, func() (interface{}, error) {
|
||||
node := newClusterNode(c.opt, addr)
|
||||
return node, node.Test()
|
||||
})
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return nil, pool.ErrClosed
|
||||
}
|
||||
|
||||
node, ok := c.allNodes[addr]
|
||||
if ok {
|
||||
_ = v.(*clusterNode).Close()
|
||||
return node, err
|
||||
}
|
||||
node = v.(*clusterNode)
|
||||
|
||||
c.allAddrs = appendIfNotExists(c.allAddrs, addr)
|
||||
if err == nil {
|
||||
c.clusterAddrs = append(c.clusterAddrs, addr)
|
||||
}
|
||||
c.allNodes[addr] = node
|
||||
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (c *clusterNodes) All() ([]*clusterNode, error) {
|
||||
@@ -258,42 +350,11 @@ func (c *clusterNodes) All() ([]*clusterNode, error) {
|
||||
return nil, pool.ErrClosed
|
||||
}
|
||||
|
||||
nodes := make([]*clusterNode, 0, len(c.nodes))
|
||||
for _, node := range c.nodes {
|
||||
nodes = append(nodes, node)
|
||||
cp := make([]*clusterNode, 0, len(c.allNodes))
|
||||
for _, node := range c.allNodes {
|
||||
cp = append(cp, node)
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) {
|
||||
var node *clusterNode
|
||||
var ok bool
|
||||
|
||||
c.mu.RLock()
|
||||
if !c.closed {
|
||||
node, ok = c.nodes[addr]
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
if ok {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return nil, pool.ErrClosed
|
||||
}
|
||||
|
||||
node, ok = c.nodes[addr]
|
||||
if ok {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
c.addrs = append(c.addrs, addr)
|
||||
node = newClusterNode(c.opt, addr)
|
||||
c.nodes[addr] = node
|
||||
return node, nil
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
func (c *clusterNodes) Random() (*clusterNode, error) {
|
||||
@@ -302,20 +363,8 @@ func (c *clusterNodes) Random() (*clusterNode, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nodeErr error
|
||||
for i := 0; i <= c.opt.MaxRedirects; i++ {
|
||||
n := rand.Intn(len(addrs))
|
||||
node, err := c.GetOrCreate(addrs[n])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodeErr = node.Client.ClusterInfo().Err()
|
||||
if nodeErr == nil {
|
||||
return node, nil
|
||||
}
|
||||
}
|
||||
return nil, nodeErr
|
||||
n := rand.Intn(len(addrs))
|
||||
return c.GetOrCreate(addrs[n])
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -367,6 +416,10 @@ func newClusterState(nodes *clusterNodes, slots []ClusterSlot, origin string) (*
|
||||
}
|
||||
}
|
||||
|
||||
time.AfterFunc(time.Minute, func() {
|
||||
nodes.GC(c.generation)
|
||||
})
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
@@ -416,13 +469,19 @@ func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) {
|
||||
if n.Loading() {
|
||||
continue
|
||||
}
|
||||
if node == nil || node.Latency-n.Latency > threshold {
|
||||
if node == nil || node.Latency()-n.Latency() > threshold {
|
||||
node = n
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (c *clusterState) slotRandomNode(slot int) *clusterNode {
|
||||
nodes := c.slotNodes(slot)
|
||||
n := rand.Intn(len(nodes))
|
||||
return nodes[n]
|
||||
}
|
||||
|
||||
func (c *clusterState) slotNodes(slot int) []*clusterNode {
|
||||
if slot >= 0 && slot < len(c.slots) {
|
||||
return c.slots[slot]
|
||||
@@ -432,25 +491,83 @@ func (c *clusterState) slotNodes(slot int) []*clusterNode {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type clusterStateHolder struct {
|
||||
load func() (*clusterState, error)
|
||||
|
||||
state atomic.Value
|
||||
|
||||
lastErrMu sync.RWMutex
|
||||
lastErr error
|
||||
|
||||
reloading uint32 // atomic
|
||||
}
|
||||
|
||||
func newClusterStateHolder(fn func() (*clusterState, error)) *clusterStateHolder {
|
||||
return &clusterStateHolder{
|
||||
load: fn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clusterStateHolder) Load() (*clusterState, error) {
|
||||
state, err := c.load()
|
||||
if err != nil {
|
||||
c.lastErrMu.Lock()
|
||||
c.lastErr = err
|
||||
c.lastErrMu.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
c.state.Store(state)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (c *clusterStateHolder) LazyReload() {
|
||||
if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
defer atomic.StoreUint32(&c.reloading, 0)
|
||||
|
||||
_, err := c.Load()
|
||||
if err == nil {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *clusterStateHolder) Get() (*clusterState, error) {
|
||||
v := c.state.Load()
|
||||
if v != nil {
|
||||
return v.(*clusterState), nil
|
||||
}
|
||||
|
||||
c.lastErrMu.RLock()
|
||||
err := c.lastErr
|
||||
c.lastErrMu.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, errors.New("redis: cluster has no state")
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// ClusterClient is a Redis Cluster client representing a pool of zero
|
||||
// or more underlying connections. It's safe for concurrent use by
|
||||
// multiple goroutines.
|
||||
type ClusterClient struct {
|
||||
cmdable
|
||||
|
||||
opt *ClusterOptions
|
||||
nodes *clusterNodes
|
||||
_state atomic.Value
|
||||
ctx context.Context
|
||||
|
||||
cmdsInfoOnce internal.Once
|
||||
cmdsInfo map[string]*CommandInfo
|
||||
opt *ClusterOptions
|
||||
nodes *clusterNodes
|
||||
state *clusterStateHolder
|
||||
cmdsInfoCache *cmdsInfoCache
|
||||
|
||||
process func(Cmder) error
|
||||
processPipeline func([]Cmder) error
|
||||
processTxPipeline func([]Cmder) error
|
||||
|
||||
// Reports whether slots reloading is in progress.
|
||||
reloading uint32
|
||||
}
|
||||
|
||||
// NewClusterClient returns a Redis Cluster client as described in
|
||||
@@ -459,9 +576,11 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
|
||||
opt.init()
|
||||
|
||||
c := &ClusterClient{
|
||||
opt: opt,
|
||||
nodes: newClusterNodes(opt),
|
||||
opt: opt,
|
||||
nodes: newClusterNodes(opt),
|
||||
cmdsInfoCache: newCmdsInfoCache(),
|
||||
}
|
||||
c.state = newClusterStateHolder(c.loadState)
|
||||
|
||||
c.process = c.defaultProcess
|
||||
c.processPipeline = c.defaultProcessPipeline
|
||||
@@ -469,20 +588,7 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
|
||||
|
||||
c.cmdable.setProcessor(c.Process)
|
||||
|
||||
// Add initial nodes.
|
||||
for _, addr := range opt.Addrs {
|
||||
_, _ = c.nodes.GetOrCreate(addr)
|
||||
}
|
||||
|
||||
// Preload cluster slots.
|
||||
for i := 0; i < 10; i++ {
|
||||
state, err := c.reloadState()
|
||||
if err == nil {
|
||||
c._state.Store(state)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = c.state.Load()
|
||||
if opt.IdleCheckFrequency > 0 {
|
||||
go c.reaper(opt.IdleCheckFrequency)
|
||||
}
|
||||
@@ -490,6 +596,27 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClusterClient) Context() context.Context {
|
||||
if c.ctx != nil {
|
||||
return c.ctx
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient {
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
c2 := c.copy()
|
||||
c2.ctx = ctx
|
||||
return c2
|
||||
}
|
||||
|
||||
func (c *ClusterClient) copy() *ClusterClient {
|
||||
cp := *c
|
||||
return &cp
|
||||
}
|
||||
|
||||
// Options returns read-only Options that were used to create the client.
|
||||
func (c *ClusterClient) Options() *ClusterOptions {
|
||||
return c.opt
|
||||
@@ -499,40 +626,18 @@ func (c *ClusterClient) retryBackoff(attempt int) time.Duration {
|
||||
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
||||
}
|
||||
|
||||
func (c *ClusterClient) state() (*clusterState, error) {
|
||||
v := c._state.Load()
|
||||
if v != nil {
|
||||
return v.(*clusterState), nil
|
||||
}
|
||||
|
||||
_, err := c.nodes.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.lazyReloadState()
|
||||
return nil, errNilClusterState
|
||||
}
|
||||
|
||||
func (c *ClusterClient) cmdInfo(name string) *CommandInfo {
|
||||
err := c.cmdsInfoOnce.Do(func() error {
|
||||
cmdsInfo, err := c.cmdsInfoCache.Do(func() (map[string]*CommandInfo, error) {
|
||||
node, err := c.nodes.Random()
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmdsInfo, err := node.Client.Command().Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.cmdsInfo = cmdsInfo
|
||||
return nil
|
||||
return node.Client.Command().Result()
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
info := c.cmdsInfo[name]
|
||||
info := cmdsInfo[name]
|
||||
if info == nil {
|
||||
internal.Logf("info for cmd=%s not found", name)
|
||||
}
|
||||
@@ -552,7 +657,12 @@ func (c *ClusterClient) cmdSlot(cmd Cmder) int {
|
||||
return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo))
|
||||
}
|
||||
|
||||
func (c *ClusterClient) cmdSlotAndNode(state *clusterState, cmd Cmder) (int, *clusterNode, error) {
|
||||
func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) {
|
||||
state, err := c.state.Get()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
cmdInfo := c.cmdInfo(cmd.Name())
|
||||
slot := cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo))
|
||||
|
||||
@@ -562,6 +672,11 @@ func (c *ClusterClient) cmdSlotAndNode(state *clusterState, cmd Cmder) (int, *cl
|
||||
return slot, node, err
|
||||
}
|
||||
|
||||
if c.opt.RouteRandomly {
|
||||
node := state.slotRandomNode(slot)
|
||||
return slot, node, nil
|
||||
}
|
||||
|
||||
node, err := state.slotSlaveNode(slot)
|
||||
return slot, node, err
|
||||
}
|
||||
@@ -570,16 +685,24 @@ func (c *ClusterClient) cmdSlotAndNode(state *clusterState, cmd Cmder) (int, *cl
|
||||
return slot, node, err
|
||||
}
|
||||
|
||||
func (c *ClusterClient) slotMasterNode(slot int) (*clusterNode, error) {
|
||||
state, err := c.state.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodes := state.slotNodes(slot)
|
||||
if len(nodes) > 0 {
|
||||
return nodes[0], nil
|
||||
}
|
||||
return c.nodes.Random()
|
||||
}
|
||||
|
||||
func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error {
|
||||
if len(keys) == 0 {
|
||||
return fmt.Errorf("redis: keys don't hash to the same slot")
|
||||
}
|
||||
|
||||
state, err := c.state()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slot := hashtag.Slot(keys[0])
|
||||
for _, key := range keys[1:] {
|
||||
if hashtag.Slot(key) != slot {
|
||||
@@ -587,7 +710,7 @@ func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
node, err := state.slotMasterNode(slot)
|
||||
node, err := c.slotMasterNode(slot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -608,7 +731,7 @@ func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error {
|
||||
|
||||
moved, ask, addr := internal.IsMovedError(err)
|
||||
if moved || ask {
|
||||
c.lazyReloadState()
|
||||
c.state.LazyReload()
|
||||
node, err = c.nodes.GetOrCreate(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -617,10 +740,11 @@ func (c *ClusterClient) Watch(fn func(*Tx) error, keys ...string) error {
|
||||
}
|
||||
|
||||
if err == pool.ErrClosed {
|
||||
node, err = state.slotMasterNode(slot)
|
||||
node, err = c.slotMasterNode(slot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
@@ -644,31 +768,27 @@ func (c *ClusterClient) WrapProcess(
|
||||
}
|
||||
|
||||
func (c *ClusterClient) Process(cmd Cmder) error {
|
||||
if c.process != nil {
|
||||
return c.process(cmd)
|
||||
}
|
||||
return c.defaultProcess(cmd)
|
||||
return c.process(cmd)
|
||||
}
|
||||
|
||||
func (c *ClusterClient) defaultProcess(cmd Cmder) error {
|
||||
state, err := c.state()
|
||||
if err != nil {
|
||||
cmd.setErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, node, err := c.cmdSlotAndNode(state, cmd)
|
||||
if err != nil {
|
||||
cmd.setErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var node *clusterNode
|
||||
var ask bool
|
||||
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
||||
if attempt > 0 {
|
||||
time.Sleep(c.retryBackoff(attempt))
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
var err error
|
||||
_, node, err = c.cmdSlotAndNode(cmd)
|
||||
if err != nil {
|
||||
cmd.setErr(err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if ask {
|
||||
pipe := node.Client.Pipeline()
|
||||
_ = pipe.Process(NewCmd("ASKING"))
|
||||
@@ -687,15 +807,13 @@ func (c *ClusterClient) defaultProcess(cmd Cmder) error {
|
||||
|
||||
// If slave is loading - read from master.
|
||||
if c.opt.ReadOnly && internal.IsLoadingError(err) {
|
||||
// TODO: race
|
||||
node.loading = time.Now()
|
||||
node.MarkAsLoading()
|
||||
continue
|
||||
}
|
||||
|
||||
if internal.IsRetryableError(err, true) {
|
||||
var nodeErr error
|
||||
node, nodeErr = c.nodes.Random()
|
||||
if nodeErr != nil {
|
||||
node, err = c.nodes.Random()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
continue
|
||||
@@ -705,22 +823,18 @@ func (c *ClusterClient) defaultProcess(cmd Cmder) error {
|
||||
var addr string
|
||||
moved, ask, addr = internal.IsMovedError(err)
|
||||
if moved || ask {
|
||||
c.lazyReloadState()
|
||||
c.state.LazyReload()
|
||||
|
||||
var nodeErr error
|
||||
node, nodeErr = c.nodes.GetOrCreate(addr)
|
||||
if nodeErr != nil {
|
||||
node, err = c.nodes.GetOrCreate(addr)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err == pool.ErrClosed {
|
||||
_, node, err = c.cmdSlotAndNode(state, cmd)
|
||||
if err != nil {
|
||||
cmd.setErr(err)
|
||||
return err
|
||||
}
|
||||
node = nil
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
@@ -732,7 +846,7 @@ func (c *ClusterClient) defaultProcess(cmd Cmder) error {
|
||||
// ForEachMaster concurrently calls the fn on each master node in the cluster.
|
||||
// It returns the first error if any.
|
||||
func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error {
|
||||
state, err := c.state()
|
||||
state, err := c.state.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -765,7 +879,7 @@ func (c *ClusterClient) ForEachMaster(fn func(client *Client) error) error {
|
||||
// ForEachSlave concurrently calls the fn on each slave node in the cluster.
|
||||
// It returns the first error if any.
|
||||
func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error {
|
||||
state, err := c.state()
|
||||
state, err := c.state.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -798,7 +912,7 @@ func (c *ClusterClient) ForEachSlave(fn func(client *Client) error) error {
|
||||
// ForEachNode concurrently calls the fn on each known node in the cluster.
|
||||
// It returns the first error if any.
|
||||
func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error {
|
||||
state, err := c.state()
|
||||
state, err := c.state.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -838,7 +952,7 @@ func (c *ClusterClient) ForEachNode(fn func(client *Client) error) error {
|
||||
func (c *ClusterClient) PoolStats() *PoolStats {
|
||||
var acc PoolStats
|
||||
|
||||
state, _ := c.state()
|
||||
state, _ := c.state.Get()
|
||||
if state == nil {
|
||||
return &acc
|
||||
}
|
||||
@@ -868,46 +982,34 @@ func (c *ClusterClient) PoolStats() *PoolStats {
|
||||
return &acc
|
||||
}
|
||||
|
||||
func (c *ClusterClient) lazyReloadState() {
|
||||
if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) {
|
||||
return
|
||||
func (c *ClusterClient) loadState() (*clusterState, error) {
|
||||
addrs, err := c.nodes.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer atomic.StoreUint32(&c.reloading, 0)
|
||||
|
||||
for {
|
||||
state, err := c.reloadState()
|
||||
if err == pool.ErrClosed {
|
||||
return
|
||||
var firstErr error
|
||||
for _, addr := range addrs {
|
||||
node, err := c.nodes.GetOrCreate(addr)
|
||||
if err != nil {
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
time.Sleep(time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
c._state.Store(state)
|
||||
time.Sleep(5 * time.Second)
|
||||
c.nodes.GC(state.generation)
|
||||
break
|
||||
continue
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Not thread-safe.
|
||||
func (c *ClusterClient) reloadState() (*clusterState, error) {
|
||||
node, err := c.nodes.Random()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
slots, err := node.Client.ClusterSlots().Result()
|
||||
if err != nil {
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
return newClusterState(c.nodes, slots, node.Client.opt.Addr)
|
||||
}
|
||||
|
||||
slots, err := node.Client.ClusterSlots().Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newClusterState(c.nodes, slots, node.Client.opt.Addr)
|
||||
return nil, firstErr
|
||||
}
|
||||
|
||||
// reaper closes idle connections to the cluster.
|
||||
@@ -991,7 +1093,7 @@ func (c *ClusterClient) defaultProcessPipeline(cmds []Cmder) error {
|
||||
}
|
||||
|
||||
func (c *ClusterClient) mapCmdsByNode(cmds []Cmder) (map[*clusterNode][]Cmder, error) {
|
||||
state, err := c.state()
|
||||
state, err := c.state.Get()
|
||||
if err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return nil, err
|
||||
@@ -1024,15 +1126,17 @@ func (c *ClusterClient) remapCmds(cmds []Cmder, failedCmds map[*clusterNode][]Cm
|
||||
func (c *ClusterClient) pipelineProcessCmds(
|
||||
node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds map[*clusterNode][]Cmder,
|
||||
) error {
|
||||
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||
if err := writeCmd(cn, cmds...); err != nil {
|
||||
_ = cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||
|
||||
err := writeCmd(cn, cmds...)
|
||||
if err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
failedCmds[node] = cmds
|
||||
return err
|
||||
}
|
||||
|
||||
// Set read timeout for all commands.
|
||||
cn.SetReadTimeout(c.opt.ReadTimeout)
|
||||
_ = cn.SetReadTimeout(c.opt.ReadTimeout)
|
||||
|
||||
return c.pipelineReadCmds(cn, cmds, failedCmds)
|
||||
}
|
||||
@@ -1065,7 +1169,7 @@ func (c *ClusterClient) checkMovedErr(
|
||||
moved, ask, addr := internal.IsMovedError(err)
|
||||
|
||||
if moved {
|
||||
c.lazyReloadState()
|
||||
c.state.LazyReload()
|
||||
|
||||
node, err := c.nodes.GetOrCreate(addr)
|
||||
if err != nil {
|
||||
@@ -1103,7 +1207,7 @@ func (c *ClusterClient) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||
}
|
||||
|
||||
func (c *ClusterClient) defaultProcessTxPipeline(cmds []Cmder) error {
|
||||
state, err := c.state()
|
||||
state, err := c.state.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1249,12 +1353,7 @@ func (c *ClusterClient) pubSub(channels []string) *PubSub {
|
||||
slot = -1
|
||||
}
|
||||
|
||||
state, err := c.state()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
masterNode, err := state.slotMasterNode(slot)
|
||||
masterNode, err := c.slotMasterNode(slot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1310,3 +1409,31 @@ func appendNode(nodes []*clusterNode, node *clusterNode) []*clusterNode {
|
||||
}
|
||||
return append(nodes, node)
|
||||
}
|
||||
|
||||
func appendIfNotExists(ss []string, es ...string) []string {
|
||||
loop:
|
||||
for _, e := range es {
|
||||
for _, s := range ss {
|
||||
if s == e {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
ss = append(ss, e)
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func remove(ss []string, es ...string) []string {
|
||||
if len(es) == 0 {
|
||||
return ss[:0]
|
||||
}
|
||||
for _, e := range es {
|
||||
for i, s := range ss {
|
||||
if s == e {
|
||||
ss = append(ss[:i], ss[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
813
vendor/github.com/go-redis/redis/cluster_test.go
сгенерированный
поставляемый
813
vendor/github.com/go-redis/redis/cluster_test.go
сгенерированный
поставляемый
@@ -1,813 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/go-redis/redis/internal/hashtag"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type clusterScenario struct {
|
||||
ports []string
|
||||
nodeIds []string
|
||||
processes map[string]*redisProcess
|
||||
clients map[string]*redis.Client
|
||||
}
|
||||
|
||||
func (s *clusterScenario) masters() []*redis.Client {
|
||||
result := make([]*redis.Client, 3)
|
||||
for pos, port := range s.ports[:3] {
|
||||
result[pos] = s.clients[port]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *clusterScenario) slaves() []*redis.Client {
|
||||
result := make([]*redis.Client, 3)
|
||||
for pos, port := range s.ports[3:] {
|
||||
result[pos] = s.clients[port]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *clusterScenario) addrs() []string {
|
||||
addrs := make([]string, len(s.ports))
|
||||
for i, port := range s.ports {
|
||||
addrs[i] = net.JoinHostPort("127.0.0.1", port)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
func (s *clusterScenario) clusterClient(opt *redis.ClusterOptions) *redis.ClusterClient {
|
||||
opt.Addrs = s.addrs()
|
||||
return redis.NewClusterClient(opt)
|
||||
}
|
||||
|
||||
func startCluster(scenario *clusterScenario) error {
|
||||
// Start processes and collect node ids
|
||||
for pos, port := range scenario.ports {
|
||||
process, err := startRedis(port, "--cluster-enabled", "yes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: ":" + port,
|
||||
})
|
||||
|
||||
info, err := client.ClusterNodes().Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scenario.processes[port] = process
|
||||
scenario.clients[port] = client
|
||||
scenario.nodeIds[pos] = info[:40]
|
||||
}
|
||||
|
||||
// Meet cluster nodes.
|
||||
for _, client := range scenario.clients {
|
||||
err := client.ClusterMeet("127.0.0.1", scenario.ports[0]).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap masters.
|
||||
slots := []int{0, 5000, 10000, 16384}
|
||||
for pos, master := range scenario.masters() {
|
||||
err := master.ClusterAddSlotsRange(slots[pos], slots[pos+1]-1).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap slaves.
|
||||
for idx, slave := range scenario.slaves() {
|
||||
masterId := scenario.nodeIds[idx]
|
||||
|
||||
// Wait until master is available
|
||||
err := eventually(func() error {
|
||||
s := slave.ClusterNodes().Val()
|
||||
wanted := masterId
|
||||
if !strings.Contains(s, wanted) {
|
||||
return fmt.Errorf("%q does not contain %q", s, wanted)
|
||||
}
|
||||
return nil
|
||||
}, 10*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = slave.ClusterReplicate(masterId).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until all nodes have consistent info.
|
||||
for _, client := range scenario.clients {
|
||||
err := eventually(func() error {
|
||||
res, err := client.ClusterSlots().Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wanted := []redis.ClusterSlot{
|
||||
{0, 4999, []redis.ClusterNode{{"", "127.0.0.1:8220"}, {"", "127.0.0.1:8223"}}},
|
||||
{5000, 9999, []redis.ClusterNode{{"", "127.0.0.1:8221"}, {"", "127.0.0.1:8224"}}},
|
||||
{10000, 16383, []redis.ClusterNode{{"", "127.0.0.1:8222"}, {"", "127.0.0.1:8225"}}},
|
||||
}
|
||||
return assertSlotsEqual(res, wanted)
|
||||
}, 30*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertSlotsEqual(slots, wanted []redis.ClusterSlot) error {
|
||||
outer_loop:
|
||||
for _, s2 := range wanted {
|
||||
for _, s1 := range slots {
|
||||
if slotEqual(s1, s2) {
|
||||
continue outer_loop
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%v not found in %v", s2, slots)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func slotEqual(s1, s2 redis.ClusterSlot) bool {
|
||||
if s1.Start != s2.Start {
|
||||
return false
|
||||
}
|
||||
if s1.End != s2.End {
|
||||
return false
|
||||
}
|
||||
if len(s1.Nodes) != len(s2.Nodes) {
|
||||
return false
|
||||
}
|
||||
for i, n1 := range s1.Nodes {
|
||||
if n1.Addr != s2.Nodes[i].Addr {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func stopCluster(scenario *clusterScenario) error {
|
||||
for _, client := range scenario.clients {
|
||||
if err := client.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, process := range scenario.processes {
|
||||
if err := process.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var _ = Describe("ClusterClient", func() {
|
||||
var opt *redis.ClusterOptions
|
||||
var client *redis.ClusterClient
|
||||
|
||||
assertClusterClient := func() {
|
||||
It("should GET/SET/DEL", func() {
|
||||
val, err := client.Get("A").Result()
|
||||
Expect(err).To(Equal(redis.Nil))
|
||||
Expect(val).To(Equal(""))
|
||||
|
||||
val, err = client.Set("A", "VALUE", 0).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("OK"))
|
||||
|
||||
Eventually(func() string {
|
||||
return client.Get("A").Val()
|
||||
}, 30*time.Second).Should(Equal("VALUE"))
|
||||
|
||||
cnt, err := client.Del("A").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cnt).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("follows redirects", func() {
|
||||
Expect(client.Set("A", "VALUE", 0).Err()).NotTo(HaveOccurred())
|
||||
|
||||
slot := hashtag.Slot("A")
|
||||
client.SwapSlotNodes(slot)
|
||||
|
||||
Eventually(func() string {
|
||||
return client.Get("A").Val()
|
||||
}, 30*time.Second).Should(Equal("VALUE"))
|
||||
})
|
||||
|
||||
It("distributes keys", func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
for _, master := range cluster.masters() {
|
||||
Eventually(func() string {
|
||||
return master.Info("keyspace").Val()
|
||||
}, 30*time.Second).Should(Or(
|
||||
ContainSubstring("keys=31"),
|
||||
ContainSubstring("keys=29"),
|
||||
ContainSubstring("keys=40"),
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
It("distributes keys when using EVAL", func() {
|
||||
script := redis.NewScript(`
|
||||
local r = redis.call('SET', KEYS[1], ARGV[1])
|
||||
return r
|
||||
`)
|
||||
|
||||
var key string
|
||||
for i := 0; i < 100; i++ {
|
||||
key = fmt.Sprintf("key%d", i)
|
||||
err := script.Run(client, []string{key}, "value").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
for _, master := range cluster.masters() {
|
||||
Eventually(func() string {
|
||||
return master.Info("keyspace").Val()
|
||||
}, 30*time.Second).Should(Or(
|
||||
ContainSubstring("keys=31"),
|
||||
ContainSubstring("keys=29"),
|
||||
ContainSubstring("keys=40"),
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
It("supports Watch", func() {
|
||||
var incr func(string) error
|
||||
|
||||
// Transactionally increments key using GET and SET commands.
|
||||
incr = func(key string) error {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
n, err := tx.Get(key).Int64()
|
||||
if err != nil && err != redis.Nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Set(key, strconv.FormatInt(n+1, 10), 0)
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}, key)
|
||||
if err == redis.TxFailedErr {
|
||||
return incr(key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer wg.Done()
|
||||
|
||||
err := incr("key")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
n, err := client.Get("key").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(100)))
|
||||
})
|
||||
|
||||
Describe("pipelining", func() {
|
||||
var pipe *redis.Pipeline
|
||||
|
||||
assertPipeline := func() {
|
||||
keys := []string{"A", "B", "C", "D", "E", "F", "G"}
|
||||
|
||||
It("follows redirects", func() {
|
||||
for _, key := range keys {
|
||||
slot := hashtag.Slot(key)
|
||||
client.SwapSlotNodes(slot)
|
||||
}
|
||||
|
||||
for i, key := range keys {
|
||||
pipe.Set(key, key+"_value", 0)
|
||||
pipe.Expire(key, time.Duration(i+1)*time.Hour)
|
||||
}
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(14))
|
||||
|
||||
for _, key := range keys {
|
||||
slot := hashtag.Slot(key)
|
||||
client.SwapSlotNodes(slot)
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
pipe.Get(key)
|
||||
pipe.TTL(key)
|
||||
}
|
||||
cmds, err = pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(14))
|
||||
|
||||
for i, key := range keys {
|
||||
get := cmds[i*2].(*redis.StringCmd)
|
||||
Expect(get.Val()).To(Equal(key + "_value"))
|
||||
|
||||
ttl := cmds[(i*2)+1].(*redis.DurationCmd)
|
||||
dur := time.Duration(i+1) * time.Hour
|
||||
Expect(ttl.Val()).To(BeNumerically("~", dur, 5*time.Second))
|
||||
}
|
||||
})
|
||||
|
||||
It("works with missing keys", func() {
|
||||
pipe.Set("A", "A_value", 0)
|
||||
pipe.Set("C", "C_value", 0)
|
||||
_, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
a := pipe.Get("A")
|
||||
b := pipe.Get("B")
|
||||
c := pipe.Get("C")
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).To(Equal(redis.Nil))
|
||||
Expect(cmds).To(HaveLen(3))
|
||||
|
||||
Expect(a.Err()).NotTo(HaveOccurred())
|
||||
Expect(a.Val()).To(Equal("A_value"))
|
||||
|
||||
Expect(b.Err()).To(Equal(redis.Nil))
|
||||
Expect(b.Val()).To(Equal(""))
|
||||
|
||||
Expect(c.Err()).NotTo(HaveOccurred())
|
||||
Expect(c.Val()).To(Equal("C_value"))
|
||||
})
|
||||
}
|
||||
|
||||
Describe("with Pipeline", func() {
|
||||
BeforeEach(func() {
|
||||
pipe = client.Pipeline().(*redis.Pipeline)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(pipe.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
assertPipeline()
|
||||
})
|
||||
|
||||
Describe("with TxPipeline", func() {
|
||||
BeforeEach(func() {
|
||||
pipe = client.TxPipeline().(*redis.Pipeline)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(pipe.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
assertPipeline()
|
||||
})
|
||||
})
|
||||
|
||||
It("supports PubSub", func() {
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
Eventually(func() error {
|
||||
_, err := client.Publish("mychannel", "hello").Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg, err := pubsub.ReceiveTimeout(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ok := msg.(*redis.Message)
|
||||
if !ok {
|
||||
return fmt.Errorf("got %T, wanted *redis.Message", msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, 30*time.Second).ShouldNot(HaveOccurred())
|
||||
})
|
||||
}
|
||||
|
||||
Describe("ClusterClient", func() {
|
||||
BeforeEach(func() {
|
||||
opt = redisClusterOptions()
|
||||
client = cluster.clusterClient(opt)
|
||||
|
||||
_ = client.ForEachMaster(func(master *redis.Client) error {
|
||||
return master.FlushDB().Err()
|
||||
})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
_ = client.ForEachMaster(func(master *redis.Client) error {
|
||||
return master.FlushDB().Err()
|
||||
})
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns pool stats", func() {
|
||||
Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{}))
|
||||
})
|
||||
|
||||
It("removes idle connections", func() {
|
||||
stats := client.PoolStats()
|
||||
Expect(stats.TotalConns).NotTo(BeZero())
|
||||
Expect(stats.FreeConns).NotTo(BeZero())
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
stats = client.PoolStats()
|
||||
Expect(stats.TotalConns).To(BeZero())
|
||||
Expect(stats.FreeConns).To(BeZero())
|
||||
})
|
||||
|
||||
It("returns an error when there are no attempts left", func() {
|
||||
opt := redisClusterOptions()
|
||||
opt.MaxRedirects = -1
|
||||
client := cluster.clusterClient(opt)
|
||||
|
||||
slot := hashtag.Slot("A")
|
||||
client.SwapSlotNodes(slot)
|
||||
|
||||
err := client.Get("A").Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("MOVED"))
|
||||
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("calls fn for every master node", func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
Expect(client.Set(strconv.Itoa(i), "", 0).Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
err := client.ForEachMaster(func(master *redis.Client) error {
|
||||
return master.FlushDB().Err()
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
size, err := client.DBSize().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(size).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("should CLUSTER SLOTS", func() {
|
||||
res, err := client.ClusterSlots().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(HaveLen(3))
|
||||
|
||||
wanted := []redis.ClusterSlot{
|
||||
{0, 4999, []redis.ClusterNode{{"", "127.0.0.1:8220"}, {"", "127.0.0.1:8223"}}},
|
||||
{5000, 9999, []redis.ClusterNode{{"", "127.0.0.1:8221"}, {"", "127.0.0.1:8224"}}},
|
||||
{10000, 16383, []redis.ClusterNode{{"", "127.0.0.1:8222"}, {"", "127.0.0.1:8225"}}},
|
||||
}
|
||||
Expect(assertSlotsEqual(res, wanted)).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should CLUSTER NODES", func() {
|
||||
res, err := client.ClusterNodes().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(res)).To(BeNumerically(">", 400))
|
||||
})
|
||||
|
||||
It("should CLUSTER INFO", func() {
|
||||
res, err := client.ClusterInfo().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(ContainSubstring("cluster_known_nodes:6"))
|
||||
})
|
||||
|
||||
It("should CLUSTER KEYSLOT", func() {
|
||||
hashSlot, err := client.ClusterKeySlot("somekey").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(hashSlot).To(Equal(int64(hashtag.Slot("somekey"))))
|
||||
})
|
||||
|
||||
It("should CLUSTER COUNT-FAILURE-REPORTS", func() {
|
||||
n, err := client.ClusterCountFailureReports(cluster.nodeIds[0]).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("should CLUSTER COUNTKEYSINSLOT", func() {
|
||||
n, err := client.ClusterCountKeysInSlot(10).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(0)))
|
||||
})
|
||||
|
||||
It("should CLUSTER SAVECONFIG", func() {
|
||||
res, err := client.ClusterSaveConfig().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).To(Equal("OK"))
|
||||
})
|
||||
|
||||
It("should CLUSTER SLAVES", func() {
|
||||
nodesList, err := client.ClusterSlaves(cluster.nodeIds[0]).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(nodesList).Should(ContainElement(ContainSubstring("slave")))
|
||||
Expect(nodesList).Should(HaveLen(1))
|
||||
})
|
||||
|
||||
It("should RANDOMKEY", func() {
|
||||
const nkeys = 100
|
||||
|
||||
for i := 0; i < nkeys; i++ {
|
||||
err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
var keys []string
|
||||
addKey := func(key string) {
|
||||
for _, k := range keys {
|
||||
if k == key {
|
||||
return
|
||||
}
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
for i := 0; i < nkeys*10; i++ {
|
||||
key := client.RandomKey().Val()
|
||||
addKey(key)
|
||||
}
|
||||
|
||||
Expect(len(keys)).To(BeNumerically("~", nkeys, nkeys/10))
|
||||
})
|
||||
|
||||
assertClusterClient()
|
||||
})
|
||||
|
||||
Describe("ClusterClient failover", func() {
|
||||
BeforeEach(func() {
|
||||
opt = redisClusterOptions()
|
||||
client = cluster.clusterClient(opt)
|
||||
|
||||
_ = client.ForEachMaster(func(master *redis.Client) error {
|
||||
return master.FlushDB().Err()
|
||||
})
|
||||
|
||||
_ = client.ForEachSlave(func(slave *redis.Client) error {
|
||||
Eventually(func() int64 {
|
||||
return client.DBSize().Val()
|
||||
}, 30*time.Second).Should(Equal(int64(0)))
|
||||
return slave.ClusterFailover().Err()
|
||||
})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
_ = client.ForEachMaster(func(master *redis.Client) error {
|
||||
return master.FlushDB().Err()
|
||||
})
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
assertClusterClient()
|
||||
})
|
||||
|
||||
Describe("ClusterClient with RouteByLatency", func() {
|
||||
BeforeEach(func() {
|
||||
opt = redisClusterOptions()
|
||||
opt.RouteByLatency = true
|
||||
client = cluster.clusterClient(opt)
|
||||
|
||||
_ = client.ForEachMaster(func(master *redis.Client) error {
|
||||
return master.FlushDB().Err()
|
||||
})
|
||||
|
||||
_ = client.ForEachSlave(func(slave *redis.Client) error {
|
||||
Eventually(func() int64 {
|
||||
return client.DBSize().Val()
|
||||
}, 30*time.Second).Should(Equal(int64(0)))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
_ = client.ForEachMaster(func(master *redis.Client) error {
|
||||
return master.FlushDB().Err()
|
||||
})
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
assertClusterClient()
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ClusterClient without nodes", func() {
|
||||
var client *redis.ClusterClient
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClusterClient(&redis.ClusterOptions{})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Ping returns an error", func() {
|
||||
err := client.Ping().Err()
|
||||
Expect(err).To(MatchError("redis: cluster has no nodes"))
|
||||
})
|
||||
|
||||
It("pipeline returns an error", func() {
|
||||
_, err := client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
Expect(err).To(MatchError("redis: cluster has no nodes"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ClusterClient without valid nodes", func() {
|
||||
var client *redis.ClusterClient
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: []string{redisAddr},
|
||||
})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns an error", func() {
|
||||
err := client.Ping().Err()
|
||||
Expect(err).To(MatchError("redis: cannot load cluster slots"))
|
||||
})
|
||||
|
||||
It("pipeline returns an error", func() {
|
||||
_, err := client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
Expect(err).To(MatchError("redis: cannot load cluster slots"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ClusterClient timeout", func() {
|
||||
var client *redis.ClusterClient
|
||||
|
||||
AfterEach(func() {
|
||||
_ = client.Close()
|
||||
})
|
||||
|
||||
testTimeout := func() {
|
||||
It("Ping timeouts", func() {
|
||||
err := client.Ping().Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Pipeline timeouts", func() {
|
||||
_, err := client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Tx timeouts", func() {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
return tx.Ping().Err()
|
||||
}, "foo")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Tx Pipeline timeouts", func() {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
_, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}, "foo")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
}
|
||||
|
||||
const pause = time.Second
|
||||
|
||||
Context("read/write timeout", func() {
|
||||
BeforeEach(func() {
|
||||
opt := redisClusterOptions()
|
||||
opt.ReadTimeout = 100 * time.Millisecond
|
||||
opt.WriteTimeout = 100 * time.Millisecond
|
||||
opt.MaxRedirects = 1
|
||||
client = cluster.clusterClient(opt)
|
||||
|
||||
err := client.ForEachNode(func(client *redis.Client) error {
|
||||
return client.ClientPause(pause).Err()
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
client.ForEachNode(func(client *redis.Client) error {
|
||||
Eventually(func() error {
|
||||
return client.Ping().Err()
|
||||
}, 2*pause).ShouldNot(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
testTimeout()
|
||||
})
|
||||
})
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func BenchmarkRedisClusterPing(b *testing.B) {
|
||||
if testing.Short() {
|
||||
b.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
cluster := &clusterScenario{
|
||||
ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"},
|
||||
nodeIds: make([]string, 6),
|
||||
processes: make(map[string]*redisProcess, 6),
|
||||
clients: make(map[string]*redis.Client, 6),
|
||||
}
|
||||
|
||||
if err := startCluster(cluster); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer stopCluster(cluster)
|
||||
|
||||
client := cluster.clusterClient(redisClusterOptions())
|
||||
defer client.Close()
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Ping().Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkRedisClusterSetString(b *testing.B) {
|
||||
if testing.Short() {
|
||||
b.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
cluster := &clusterScenario{
|
||||
ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"},
|
||||
nodeIds: make([]string, 6),
|
||||
processes: make(map[string]*redisProcess, 6),
|
||||
clients: make(map[string]*redis.Client, 6),
|
||||
}
|
||||
|
||||
if err := startCluster(cluster); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer stopCluster(cluster)
|
||||
|
||||
client := cluster.clusterClient(redisClusterOptions())
|
||||
defer client.Close()
|
||||
|
||||
value := string(bytes.Repeat([]byte{'1'}, 10000))
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if err := client.Set("key", value, 0).Err(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
26
vendor/github.com/go-redis/redis/command.go
сгенерированный
поставляемый
26
vendor/github.com/go-redis/redis/command.go
сгенерированный
поставляемый
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/go-redis/redis/internal"
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
"github.com/go-redis/redis/internal/proto"
|
||||
"github.com/go-redis/redis/internal/util"
|
||||
)
|
||||
|
||||
type Cmder interface {
|
||||
@@ -436,7 +437,7 @@ func NewStringCmd(args ...interface{}) *StringCmd {
|
||||
}
|
||||
|
||||
func (cmd *StringCmd) Val() string {
|
||||
return internal.BytesToString(cmd.val)
|
||||
return util.BytesToString(cmd.val)
|
||||
}
|
||||
|
||||
func (cmd *StringCmd) Result() (string, error) {
|
||||
@@ -1022,3 +1023,26 @@ func (cmd *CommandsInfoCmd) readReply(cn *pool.Conn) error {
|
||||
cmd.val = v.(map[string]*CommandInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type cmdsInfoCache struct {
|
||||
once internal.Once
|
||||
cmds map[string]*CommandInfo
|
||||
}
|
||||
|
||||
func newCmdsInfoCache() *cmdsInfoCache {
|
||||
return &cmdsInfoCache{}
|
||||
}
|
||||
|
||||
func (c *cmdsInfoCache) Do(fn func() (map[string]*CommandInfo, error)) (map[string]*CommandInfo, error) {
|
||||
err := c.once.Do(func() error {
|
||||
cmds, err := fn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.cmds = cmds
|
||||
return nil
|
||||
})
|
||||
return c.cmds, err
|
||||
}
|
||||
|
||||
60
vendor/github.com/go-redis/redis/command_test.go
сгенерированный
поставляемый
60
vendor/github.com/go-redis/redis/command_test.go
сгенерированный
поставляемый
@@ -1,60 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Cmd", func() {
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("implements Stringer", func() {
|
||||
set := client.Set("foo", "bar", 0)
|
||||
Expect(set.String()).To(Equal("set foo bar: OK"))
|
||||
|
||||
get := client.Get("foo")
|
||||
Expect(get.String()).To(Equal("get foo: bar"))
|
||||
})
|
||||
|
||||
It("has val/err", func() {
|
||||
set := client.Set("key", "hello", 0)
|
||||
Expect(set.Err()).NotTo(HaveOccurred())
|
||||
Expect(set.Val()).To(Equal("OK"))
|
||||
|
||||
get := client.Get("key")
|
||||
Expect(get.Err()).NotTo(HaveOccurred())
|
||||
Expect(get.Val()).To(Equal("hello"))
|
||||
|
||||
Expect(set.Err()).NotTo(HaveOccurred())
|
||||
Expect(set.Val()).To(Equal("OK"))
|
||||
})
|
||||
|
||||
It("has helpers", func() {
|
||||
set := client.Set("key", "10", 0)
|
||||
Expect(set.Err()).NotTo(HaveOccurred())
|
||||
|
||||
n, err := client.Get("key").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(10)))
|
||||
|
||||
un, err := client.Get("key").Uint64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(un).To(Equal(uint64(10)))
|
||||
|
||||
f, err := client.Get("key").Float64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(f).To(Equal(float64(10)))
|
||||
})
|
||||
|
||||
})
|
||||
146
vendor/github.com/go-redis/redis/commands.go
сгенерированный
поставляемый
146
vendor/github.com/go-redis/redis/commands.go
сгенерированный
поставляемый
@@ -1,6 +1,7 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
@@ -38,6 +39,22 @@ func formatSec(dur time.Duration) int64 {
|
||||
return int64(dur / time.Second)
|
||||
}
|
||||
|
||||
func appendArgs(dst, src []interface{}) []interface{} {
|
||||
if len(src) == 1 {
|
||||
if ss, ok := src[0].([]string); ok {
|
||||
for _, s := range ss {
|
||||
dst = append(dst, s)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range src {
|
||||
dst = append(dst, v)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
type Cmdable interface {
|
||||
Pipeline() Pipeliner
|
||||
Pipelined(fn func(Pipeliner) error) ([]Cmder, error)
|
||||
@@ -70,8 +87,10 @@ type Cmdable interface {
|
||||
RenameNX(key, newkey string) *BoolCmd
|
||||
Restore(key string, ttl time.Duration, value string) *StatusCmd
|
||||
RestoreReplace(key string, ttl time.Duration, value string) *StatusCmd
|
||||
Sort(key string, sort Sort) *StringSliceCmd
|
||||
SortInterfaces(key string, sort Sort) *SliceCmd
|
||||
Sort(key string, sort *Sort) *StringSliceCmd
|
||||
SortStore(key, store string, sort *Sort) *IntCmd
|
||||
SortInterfaces(key string, sort *Sort) *SliceCmd
|
||||
Touch(keys ...string) *IntCmd
|
||||
TTL(key string) *DurationCmd
|
||||
Type(key string) *StatusCmd
|
||||
Scan(cursor uint64, match string, count int64) *ScanCmd
|
||||
@@ -195,6 +214,7 @@ type Cmdable interface {
|
||||
ConfigGet(parameter string) *SliceCmd
|
||||
ConfigResetStat() *StatusCmd
|
||||
ConfigSet(parameter, value string) *StatusCmd
|
||||
ConfigRewrite() *StatusCmd
|
||||
DBSize() *IntCmd
|
||||
FlushAll() *StatusCmd
|
||||
FlushAllAsync() *StatusCmd
|
||||
@@ -210,7 +230,7 @@ type Cmdable interface {
|
||||
Time() *TimeCmd
|
||||
Eval(script string, keys []string, args ...interface{}) *Cmd
|
||||
EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
|
||||
ScriptExists(scripts ...string) *BoolSliceCmd
|
||||
ScriptExists(hashes ...string) *BoolSliceCmd
|
||||
ScriptFlush() *StatusCmd
|
||||
ScriptKill() *StatusCmd
|
||||
ScriptLoad(script string) *StringCmd
|
||||
@@ -252,6 +272,7 @@ type StatefulCmdable interface {
|
||||
Cmdable
|
||||
Auth(password string) *StatusCmd
|
||||
Select(index int) *StatusCmd
|
||||
SwapDB(index1, index2 int) *StatusCmd
|
||||
ClientSetName(name string) *BoolCmd
|
||||
ReadOnly() *StatusCmd
|
||||
ReadWrite() *StatusCmd
|
||||
@@ -316,6 +337,12 @@ func (c *statefulCmdable) Select(index int) *StatusCmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *statefulCmdable) SwapDB(index1, index2 int) *StatusCmd {
|
||||
cmd := NewStatusCmd("swapdb", index1, index2)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c *cmdable) Del(keys ...string) *IntCmd {
|
||||
@@ -484,11 +511,10 @@ func (c *cmdable) RestoreReplace(key string, ttl time.Duration, value string) *S
|
||||
|
||||
type Sort struct {
|
||||
By string
|
||||
Offset, Count float64
|
||||
Offset, Count int64
|
||||
Get []string
|
||||
Order string
|
||||
IsAlpha bool
|
||||
Store string
|
||||
Alpha bool
|
||||
}
|
||||
|
||||
func (sort *Sort) args(key string) []interface{} {
|
||||
@@ -505,27 +531,45 @@ func (sort *Sort) args(key string) []interface{} {
|
||||
if sort.Order != "" {
|
||||
args = append(args, sort.Order)
|
||||
}
|
||||
if sort.IsAlpha {
|
||||
if sort.Alpha {
|
||||
args = append(args, "alpha")
|
||||
}
|
||||
if sort.Store != "" {
|
||||
args = append(args, "store", sort.Store)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (c *cmdable) Sort(key string, sort Sort) *StringSliceCmd {
|
||||
func (c *cmdable) Sort(key string, sort *Sort) *StringSliceCmd {
|
||||
cmd := NewStringSliceCmd(sort.args(key)...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) SortInterfaces(key string, sort Sort) *SliceCmd {
|
||||
func (c *cmdable) SortStore(key, store string, sort *Sort) *IntCmd {
|
||||
args := sort.args(key)
|
||||
if store != "" {
|
||||
args = append(args, "store", store)
|
||||
}
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) SortInterfaces(key string, sort *Sort) *SliceCmd {
|
||||
cmd := NewSliceCmd(sort.args(key)...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) Touch(keys ...string) *IntCmd {
|
||||
args := make([]interface{}, len(keys)+1)
|
||||
args[0] = "touch"
|
||||
for i, key := range keys {
|
||||
args[i+1] = key
|
||||
}
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) TTL(key string) *DurationCmd {
|
||||
cmd := NewDurationCmd(time.Second, "ttl", key)
|
||||
c.process(cmd)
|
||||
@@ -732,22 +776,18 @@ func (c *cmdable) MGet(keys ...string) *SliceCmd {
|
||||
}
|
||||
|
||||
func (c *cmdable) MSet(pairs ...interface{}) *StatusCmd {
|
||||
args := make([]interface{}, 1+len(pairs))
|
||||
args := make([]interface{}, 1, 1+len(pairs))
|
||||
args[0] = "mset"
|
||||
for i, pair := range pairs {
|
||||
args[1+i] = pair
|
||||
}
|
||||
args = appendArgs(args, pairs)
|
||||
cmd := NewStatusCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) MSetNX(pairs ...interface{}) *BoolCmd {
|
||||
args := make([]interface{}, 1+len(pairs))
|
||||
args := make([]interface{}, 1, 1+len(pairs))
|
||||
args[0] = "msetnx"
|
||||
for i, pair := range pairs {
|
||||
args[1+i] = pair
|
||||
}
|
||||
args = appendArgs(args, pairs)
|
||||
cmd := NewBoolCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
@@ -1012,12 +1052,10 @@ func (c *cmdable) LPop(key string) *StringCmd {
|
||||
}
|
||||
|
||||
func (c *cmdable) LPush(key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2+len(values))
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "lpush"
|
||||
args[1] = key
|
||||
for i, value := range values {
|
||||
args[2+i] = value
|
||||
}
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
@@ -1076,12 +1114,10 @@ func (c *cmdable) RPopLPush(source, destination string) *StringCmd {
|
||||
}
|
||||
|
||||
func (c *cmdable) RPush(key string, values ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2+len(values))
|
||||
args := make([]interface{}, 2, 2+len(values))
|
||||
args[0] = "rpush"
|
||||
args[1] = key
|
||||
for i, value := range values {
|
||||
args[2+i] = value
|
||||
}
|
||||
args = appendArgs(args, values)
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
@@ -1096,12 +1132,10 @@ func (c *cmdable) RPushX(key string, value interface{}) *IntCmd {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c *cmdable) SAdd(key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "sadd"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
@@ -1214,12 +1248,10 @@ func (c *cmdable) SRandMemberN(key string, count int64) *StringSliceCmd {
|
||||
}
|
||||
|
||||
func (c *cmdable) SRem(key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "srem"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
@@ -1479,12 +1511,10 @@ func (c *cmdable) ZRank(key, member string) *IntCmd {
|
||||
}
|
||||
|
||||
func (c *cmdable) ZRem(key string, members ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2+len(members))
|
||||
args := make([]interface{}, 2, 2+len(members))
|
||||
args[0] = "zrem"
|
||||
args[1] = key
|
||||
for i, member := range members {
|
||||
args[2+i] = member
|
||||
}
|
||||
args = appendArgs(args, members)
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
@@ -1600,12 +1630,10 @@ func (c *cmdable) ZUnionStore(dest string, store ZStore, keys ...string) *IntCmd
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c *cmdable) PFAdd(key string, els ...interface{}) *IntCmd {
|
||||
args := make([]interface{}, 2+len(els))
|
||||
args := make([]interface{}, 2, 2+len(els))
|
||||
args[0] = "pfadd"
|
||||
args[1] = key
|
||||
for i, el := range els {
|
||||
args[2+i] = el
|
||||
}
|
||||
args = appendArgs(args, els)
|
||||
cmd := NewIntCmd(args...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
@@ -1698,6 +1726,12 @@ func (c *cmdable) ConfigSet(parameter, value string) *StatusCmd {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) ConfigRewrite() *StatusCmd {
|
||||
cmd := NewStatusCmd("config", "rewrite")
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Deperecated. Use DBSize instead.
|
||||
func (c *cmdable) DbSize() *IntCmd {
|
||||
return c.DBSize()
|
||||
@@ -1776,7 +1810,7 @@ func (c *cmdable) shutdown(modifier string) *StatusCmd {
|
||||
}
|
||||
} else {
|
||||
// Server did not quit. String reply contains the reason.
|
||||
cmd.err = internal.RedisError(cmd.val)
|
||||
cmd.err = errors.New(cmd.val)
|
||||
cmd.val = ""
|
||||
}
|
||||
return cmd
|
||||
@@ -1817,45 +1851,39 @@ func (c *cmdable) Time() *TimeCmd {
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func (c *cmdable) Eval(script string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := make([]interface{}, 3+len(keys)+len(args))
|
||||
cmdArgs := make([]interface{}, 3+len(keys), 3+len(keys)+len(args))
|
||||
cmdArgs[0] = "eval"
|
||||
cmdArgs[1] = script
|
||||
cmdArgs[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
cmdArgs[3+i] = key
|
||||
}
|
||||
pos := 3 + len(keys)
|
||||
for i, arg := range args {
|
||||
cmdArgs[pos+i] = arg
|
||||
}
|
||||
cmdArgs = appendArgs(cmdArgs, args)
|
||||
cmd := NewCmd(cmdArgs...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd {
|
||||
cmdArgs := make([]interface{}, 3+len(keys)+len(args))
|
||||
cmdArgs := make([]interface{}, 3+len(keys), 3+len(keys)+len(args))
|
||||
cmdArgs[0] = "evalsha"
|
||||
cmdArgs[1] = sha1
|
||||
cmdArgs[2] = len(keys)
|
||||
for i, key := range keys {
|
||||
cmdArgs[3+i] = key
|
||||
}
|
||||
pos := 3 + len(keys)
|
||||
for i, arg := range args {
|
||||
cmdArgs[pos+i] = arg
|
||||
}
|
||||
cmdArgs = appendArgs(cmdArgs, args)
|
||||
cmd := NewCmd(cmdArgs...)
|
||||
c.process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdable) ScriptExists(scripts ...string) *BoolSliceCmd {
|
||||
args := make([]interface{}, 2+len(scripts))
|
||||
func (c *cmdable) ScriptExists(hashes ...string) *BoolSliceCmd {
|
||||
args := make([]interface{}, 2+len(hashes))
|
||||
args[0] = "script"
|
||||
args[1] = "exists"
|
||||
for i, script := range scripts {
|
||||
args[2+i] = script
|
||||
for i, hash := range hashes {
|
||||
args[2+i] = hash
|
||||
}
|
||||
cmd := NewBoolSliceCmd(args...)
|
||||
c.process(cmd)
|
||||
|
||||
2968
vendor/github.com/go-redis/redis/commands_test.go
сгенерированный
поставляемый
2968
vendor/github.com/go-redis/redis/commands_test.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
48
vendor/github.com/go-redis/redis/example_instrumentation_test.go
сгенерированный
поставляемый
48
vendor/github.com/go-redis/redis/example_instrumentation_test.go
сгенерированный
поставляемый
@@ -1,48 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
func Example_instrumentation() {
|
||||
cl := redis.NewClient(&redis.Options{
|
||||
Addr: ":6379",
|
||||
})
|
||||
cl.WrapProcess(func(old func(cmd redis.Cmder) error) func(cmd redis.Cmder) error {
|
||||
return func(cmd redis.Cmder) error {
|
||||
fmt.Printf("starting processing: <%s>\n", cmd)
|
||||
err := old(cmd)
|
||||
fmt.Printf("finished processing: <%s>\n", cmd)
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
cl.Ping()
|
||||
// Output: starting processing: <ping: >
|
||||
// finished processing: <ping: PONG>
|
||||
}
|
||||
|
||||
func Example_Pipeline_instrumentation() {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: ":6379",
|
||||
})
|
||||
|
||||
client.WrapProcessPipeline(func(old func([]redis.Cmder) error) func([]redis.Cmder) error {
|
||||
return func(cmds []redis.Cmder) error {
|
||||
fmt.Printf("pipeline starting processing: %v\n", cmds)
|
||||
err := old(cmds)
|
||||
fmt.Printf("pipeline finished processing: %v\n", cmds)
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
// Output: pipeline starting processing: [ping: ping: ]
|
||||
// pipeline finished processing: [ping: PONG ping: PONG]
|
||||
}
|
||||
414
vendor/github.com/go-redis/redis/example_test.go
сгенерированный
поставляемый
414
vendor/github.com/go-redis/redis/example_test.go
сгенерированный
поставляемый
@@ -1,414 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
var client *redis.Client
|
||||
|
||||
func init() {
|
||||
client = redis.NewClient(&redis.Options{
|
||||
Addr: ":6379",
|
||||
DialTimeout: 10 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: 30 * time.Second,
|
||||
})
|
||||
client.FlushDB()
|
||||
}
|
||||
|
||||
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 ExampleParseURL() {
|
||||
opt, err := redis.ParseURL("redis://:qwerty@localhost:6379/1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("addr is", opt.Addr)
|
||||
fmt.Println("db is", opt.DB)
|
||||
fmt.Println("password is", opt.Password)
|
||||
|
||||
// Create client as usually.
|
||||
_ = redis.NewClient(opt)
|
||||
|
||||
// Output: addr is localhost:6379
|
||||
// db is 1
|
||||
// password is qwerty
|
||||
}
|
||||
|
||||
func ExampleNewFailoverClient() {
|
||||
// See http://redis.io/topics/sentinel for instructions how to
|
||||
// setup Redis Sentinel.
|
||||
client := redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
MasterName: "master",
|
||||
SentinelAddrs: []string{":26379"},
|
||||
})
|
||||
client.Ping()
|
||||
}
|
||||
|
||||
func ExampleNewClusterClient() {
|
||||
// See http://redis.io/topics/cluster-tutorial for instructions
|
||||
// how to setup Redis Cluster.
|
||||
client := redis.NewClusterClient(&redis.ClusterOptions{
|
||||
Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
|
||||
})
|
||||
client.Ping()
|
||||
}
|
||||
|
||||
func ExampleNewRing() {
|
||||
client := redis.NewRing(&redis.RingOptions{
|
||||
Addrs: map[string]string{
|
||||
"shard1": ":7000",
|
||||
"shard2": ":7001",
|
||||
"shard3": ":7002",
|
||||
},
|
||||
})
|
||||
client.Ping()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func ExampleClient_Set() {
|
||||
// Last argument is expiration. Zero means the key has no
|
||||
// expiration time.
|
||||
err := client.Set("key", "value", 0).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// key2 will expire in an hour.
|
||||
err = client.Set("key2", "value", time.Hour).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_Incr() {
|
||||
result, err := client.Incr("counter").Result()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(result)
|
||||
// Output: 1
|
||||
}
|
||||
|
||||
func ExampleClient_BLPop() {
|
||||
if err := client.RPush("queue", "message").Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// use `client.BLPop(0, "queue")` for infinite waiting time
|
||||
result, err := client.BLPop(1*time.Second, "queue").Result()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(result[0], result[1])
|
||||
// Output: queue message
|
||||
}
|
||||
|
||||
func ExampleClient_Scan() {
|
||||
client.FlushDB()
|
||||
for i := 0; i < 33; i++ {
|
||||
err := client.Set(fmt.Sprintf("key%d", i), "value", 0).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var cursor uint64
|
||||
var n int
|
||||
for {
|
||||
var keys []string
|
||||
var err error
|
||||
keys, cursor, err = client.Scan(cursor, "", 10).Result()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
n += len(keys)
|
||||
if cursor == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("found %d keys\n", n)
|
||||
// Output: found 33 keys
|
||||
}
|
||||
|
||||
func ExampleClient_Pipelined() {
|
||||
var incr *redis.IntCmd
|
||||
_, err := client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
incr = pipe.Incr("pipelined_counter")
|
||||
pipe.Expire("pipelined_counter", time.Hour)
|
||||
return nil
|
||||
})
|
||||
fmt.Println(incr.Val(), err)
|
||||
// Output: 1 <nil>
|
||||
}
|
||||
|
||||
func ExampleClient_Pipeline() {
|
||||
pipe := client.Pipeline()
|
||||
|
||||
incr := pipe.Incr("pipeline_counter")
|
||||
pipe.Expire("pipeline_counter", time.Hour)
|
||||
|
||||
// Execute
|
||||
//
|
||||
// INCR pipeline_counter
|
||||
// EXPIRE pipeline_counts 3600
|
||||
//
|
||||
// using one client-server roundtrip.
|
||||
_, err := pipe.Exec()
|
||||
fmt.Println(incr.Val(), err)
|
||||
// Output: 1 <nil>
|
||||
}
|
||||
|
||||
func ExampleClient_TxPipelined() {
|
||||
var incr *redis.IntCmd
|
||||
_, err := client.TxPipelined(func(pipe redis.Pipeliner) error {
|
||||
incr = pipe.Incr("tx_pipelined_counter")
|
||||
pipe.Expire("tx_pipelined_counter", time.Hour)
|
||||
return nil
|
||||
})
|
||||
fmt.Println(incr.Val(), err)
|
||||
// Output: 1 <nil>
|
||||
}
|
||||
|
||||
func ExampleClient_TxPipeline() {
|
||||
pipe := client.TxPipeline()
|
||||
|
||||
incr := pipe.Incr("tx_pipeline_counter")
|
||||
pipe.Expire("tx_pipeline_counter", time.Hour)
|
||||
|
||||
// Execute
|
||||
//
|
||||
// MULTI
|
||||
// INCR pipeline_counter
|
||||
// EXPIRE pipeline_counts 3600
|
||||
// EXEC
|
||||
//
|
||||
// using one client-server roundtrip.
|
||||
_, err := pipe.Exec()
|
||||
fmt.Println(incr.Val(), err)
|
||||
// Output: 1 <nil>
|
||||
}
|
||||
|
||||
func ExampleClient_Watch() {
|
||||
var incr func(string) error
|
||||
|
||||
// Transactionally increments key using GET and SET commands.
|
||||
incr = func(key string) error {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
n, err := tx.Get(key).Int64()
|
||||
if err != nil && err != redis.Nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Set(key, strconv.FormatInt(n+1, 10), 0)
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}, key)
|
||||
if err == redis.TxFailedErr {
|
||||
return incr(key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
err := incr("counter3")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
n, err := client.Get("counter3").Int64()
|
||||
fmt.Println(n, err)
|
||||
// Output: 100 <nil>
|
||||
}
|
||||
|
||||
func ExamplePubSub() {
|
||||
pubsub := client.Subscribe("mychannel1")
|
||||
defer pubsub.Close()
|
||||
|
||||
// Wait for subscription to be created before publishing message.
|
||||
subscr, err := pubsub.ReceiveTimeout(time.Second)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(subscr)
|
||||
|
||||
err = client.Publish("mychannel1", "hello").Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
msg, err := pubsub.ReceiveMessage()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(msg.Channel, msg.Payload)
|
||||
// Output: subscribe: mychannel1
|
||||
// mychannel1 hello
|
||||
}
|
||||
|
||||
func ExamplePubSub_Receive() {
|
||||
pubsub := client.Subscribe("mychannel2")
|
||||
defer pubsub.Close()
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
// ReceiveTimeout is a low level API. Use ReceiveMessage instead.
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
switch msg := msgi.(type) {
|
||||
case *redis.Subscription:
|
||||
fmt.Println("subscribed to", msg.Channel)
|
||||
|
||||
_, err := client.Publish("mychannel2", "hello").Result()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case *redis.Message:
|
||||
fmt.Println("received", msg.Payload, "from", msg.Channel)
|
||||
default:
|
||||
panic("unreached")
|
||||
}
|
||||
}
|
||||
|
||||
// sent message to 1 client
|
||||
// received hello from mychannel2
|
||||
}
|
||||
|
||||
func ExampleScript() {
|
||||
IncrByXX := redis.NewScript(`
|
||||
if redis.call("GET", KEYS[1]) ~= false then
|
||||
return redis.call("INCRBY", KEYS[1], ARGV[1])
|
||||
end
|
||||
return false
|
||||
`)
|
||||
|
||||
n, err := IncrByXX.Run(client, []string{"xx_counter"}, 2).Result()
|
||||
fmt.Println(n, err)
|
||||
|
||||
err = client.Set("xx_counter", "40", 0).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
n, err = IncrByXX.Run(client, []string{"xx_counter"}, 2).Result()
|
||||
fmt.Println(n, err)
|
||||
|
||||
// Output: <nil> redis: nil
|
||||
// 42 <nil>
|
||||
}
|
||||
|
||||
func Example_customCommand() {
|
||||
Get := func(client *redis.Client, key string) *redis.StringCmd {
|
||||
cmd := redis.NewStringCmd("get", key)
|
||||
client.Process(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
v, err := Get(client, "key_does_not_exist").Result()
|
||||
fmt.Printf("%q %s", v, err)
|
||||
// Output: "" redis: nil
|
||||
}
|
||||
|
||||
func ExampleScanIterator() {
|
||||
iter := client.Scan(0, "", 0).Iterator()
|
||||
for iter.Next() {
|
||||
fmt.Println(iter.Val())
|
||||
}
|
||||
if err := iter.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleScanCmd_Iterator() {
|
||||
iter := client.Scan(0, "", 0).Iterator()
|
||||
for iter.Next() {
|
||||
fmt.Println(iter.Val())
|
||||
}
|
||||
if err := iter.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleNewUniversalClient_simple() {
|
||||
client := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: []string{":6379"},
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
client.Ping()
|
||||
}
|
||||
|
||||
func ExampleNewUniversalClient_failover() {
|
||||
client := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
MasterName: "master",
|
||||
Addrs: []string{":26379"},
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
client.Ping()
|
||||
}
|
||||
|
||||
func ExampleNewUniversalClient_cluster() {
|
||||
client := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: []string{":7000", ":7001", ":7002", ":7003", ":7004", ":7005"},
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
client.Ping()
|
||||
}
|
||||
46
vendor/github.com/go-redis/redis/export_test.go
сгенерированный
поставляемый
46
vendor/github.com/go-redis/redis/export_test.go
сгенерированный
поставляемый
@@ -1,46 +0,0 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
)
|
||||
|
||||
func (c *baseClient) Pool() pool.Pooler {
|
||||
return c.connPool
|
||||
}
|
||||
|
||||
func (c *PubSub) SetNetConn(netConn net.Conn) {
|
||||
c.cn = pool.NewConn(netConn)
|
||||
}
|
||||
|
||||
func (c *PubSub) ReceiveMessageTimeout(timeout time.Duration) (*Message, error) {
|
||||
return c.receiveMessage(timeout)
|
||||
}
|
||||
|
||||
func (c *ClusterClient) SlotAddrs(slot int) []string {
|
||||
state, err := c.state()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var addrs []string
|
||||
for _, n := range state.slotNodes(slot) {
|
||||
addrs = append(addrs, n.Client.getAddr())
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
// SwapSlot swaps a slot's master/slave address for testing MOVED redirects.
|
||||
func (c *ClusterClient) SwapSlotNodes(slot int) {
|
||||
state, err := c.state()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
nodes := state.slots[slot]
|
||||
if len(nodes) == 2 {
|
||||
nodes[0], nodes[1] = nodes[1], nodes[0]
|
||||
}
|
||||
}
|
||||
110
vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash_test.go
сгенерированный
поставляемый
110
vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash_test.go
сгенерированный
поставляемый
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHashing(t *testing.T) {
|
||||
|
||||
// Override the hash function to return easier to reason about values. Assumes
|
||||
// the keys can be converted to an integer.
|
||||
hash := New(3, func(key []byte) uint32 {
|
||||
i, err := strconv.Atoi(string(key))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uint32(i)
|
||||
})
|
||||
|
||||
// Given the above hash function, this will give replicas with "hashes":
|
||||
// 2, 4, 6, 12, 14, 16, 22, 24, 26
|
||||
hash.Add("6", "4", "2")
|
||||
|
||||
testCases := map[string]string{
|
||||
"2": "2",
|
||||
"11": "2",
|
||||
"23": "4",
|
||||
"27": "2",
|
||||
}
|
||||
|
||||
for k, v := range testCases {
|
||||
if hash.Get(k) != v {
|
||||
t.Errorf("Asking for %s, should have yielded %s", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Adds 8, 18, 28
|
||||
hash.Add("8")
|
||||
|
||||
// 27 should now map to 8.
|
||||
testCases["27"] = "8"
|
||||
|
||||
for k, v := range testCases {
|
||||
if hash.Get(k) != v {
|
||||
t.Errorf("Asking for %s, should have yielded %s", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConsistency(t *testing.T) {
|
||||
hash1 := New(1, nil)
|
||||
hash2 := New(1, nil)
|
||||
|
||||
hash1.Add("Bill", "Bob", "Bonny")
|
||||
hash2.Add("Bob", "Bonny", "Bill")
|
||||
|
||||
if hash1.Get("Ben") != hash2.Get("Ben") {
|
||||
t.Errorf("Fetching 'Ben' from both hashes should be the same")
|
||||
}
|
||||
|
||||
hash2.Add("Becky", "Ben", "Bobby")
|
||||
|
||||
if hash1.Get("Ben") != hash2.Get("Ben") ||
|
||||
hash1.Get("Bob") != hash2.Get("Bob") ||
|
||||
hash1.Get("Bonny") != hash2.Get("Bonny") {
|
||||
t.Errorf("Direct matches should always return the same entry")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkGet8(b *testing.B) { benchmarkGet(b, 8) }
|
||||
func BenchmarkGet32(b *testing.B) { benchmarkGet(b, 32) }
|
||||
func BenchmarkGet128(b *testing.B) { benchmarkGet(b, 128) }
|
||||
func BenchmarkGet512(b *testing.B) { benchmarkGet(b, 512) }
|
||||
|
||||
func benchmarkGet(b *testing.B, shards int) {
|
||||
|
||||
hash := New(50, nil)
|
||||
|
||||
var buckets []string
|
||||
for i := 0; i < shards; i++ {
|
||||
buckets = append(buckets, fmt.Sprintf("shard-%d", i))
|
||||
}
|
||||
|
||||
hash.Add(buckets...)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hash.Get(buckets[i&(shards-1)])
|
||||
}
|
||||
}
|
||||
14
vendor/github.com/go-redis/redis/internal/error.go
сгенерированный
поставляемый
14
vendor/github.com/go-redis/redis/internal/error.go
сгенерированный
поставляемый
@@ -4,14 +4,10 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/go-redis/redis/internal/proto"
|
||||
)
|
||||
|
||||
const Nil = RedisError("redis: nil")
|
||||
|
||||
type RedisError string
|
||||
|
||||
func (e RedisError) Error() string { return string(e) }
|
||||
|
||||
func IsRetryableError(err error, retryNetError bool) bool {
|
||||
if IsNetworkError(err) {
|
||||
return retryNetError
|
||||
@@ -30,7 +26,7 @@ func IsRetryableError(err error, retryNetError bool) bool {
|
||||
}
|
||||
|
||||
func IsRedisError(err error) bool {
|
||||
_, ok := err.(RedisError)
|
||||
_, ok := err.(proto.RedisError)
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -42,6 +38,10 @@ func IsNetworkError(err error) bool {
|
||||
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
|
||||
|
||||
74
vendor/github.com/go-redis/redis/internal/hashtag/hashtag_test.go
сгенерированный
поставляемый
74
vendor/github.com/go-redis/redis/internal/hashtag/hashtag_test.go
сгенерированный
поставляемый
@@ -1,74 +0,0 @@
|
||||
package hashtag
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestGinkgoSuite(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "hashtag")
|
||||
}
|
||||
|
||||
var _ = Describe("CRC16", func() {
|
||||
|
||||
// http://redis.io/topics/cluster-spec#keys-distribution-model
|
||||
It("should calculate CRC16", func() {
|
||||
tests := []struct {
|
||||
s string
|
||||
n uint16
|
||||
}{
|
||||
{"123456789", 0x31C3},
|
||||
{string([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215}), 21847},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(crc16sum(test.s)).To(Equal(test.n), "for %s", test.s)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
var _ = Describe("HashSlot", func() {
|
||||
|
||||
It("should calculate hash slots", func() {
|
||||
tests := []struct {
|
||||
key string
|
||||
slot int
|
||||
}{
|
||||
{"123456789", 12739},
|
||||
{"{}foo", 9500},
|
||||
{"foo{}", 5542},
|
||||
{"foo{}{bar}", 8363},
|
||||
{"", 10503},
|
||||
{"", 5176},
|
||||
{string([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215}), 5463},
|
||||
}
|
||||
// Empty keys receive random slot.
|
||||
rand.Seed(100)
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(Slot(test.key)).To(Equal(test.slot), "for %s", test.key)
|
||||
}
|
||||
})
|
||||
|
||||
It("should extract keys from tags", func() {
|
||||
tests := []struct {
|
||||
one, two string
|
||||
}{
|
||||
{"foo{bar}", "bar"},
|
||||
{"{foo}bar", "foo"},
|
||||
{"{user1000}.following", "{user1000}.followers"},
|
||||
{"foo{{bar}}zap", "{bar"},
|
||||
{"foo{bar}{zap}", "bar"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
Expect(Slot(test.one)).To(Equal(Slot(test.two)), "for %s <-> %s", test.one, test.two)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
18
vendor/github.com/go-redis/redis/internal/internal_test.go
сгенерированный
поставляемый
18
vendor/github.com/go-redis/redis/internal/internal_test.go
сгенерированный
поставляемый
@@ -1,18 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestRetryBackoff(t *testing.T) {
|
||||
RegisterTestingT(t)
|
||||
|
||||
for i := -1; i <= 16; i++ {
|
||||
backoff := RetryBackoff(i, time.Millisecond, 512*time.Millisecond)
|
||||
Expect(backoff >= 0).To(BeTrue())
|
||||
Expect(backoff <= 512*time.Millisecond).To(BeTrue())
|
||||
}
|
||||
}
|
||||
80
vendor/github.com/go-redis/redis/internal/pool/bench_test.go
сгенерированный
поставляемый
80
vendor/github.com/go-redis/redis/internal/pool/bench_test.go
сгенерированный
поставляемый
@@ -1,80 +0,0 @@
|
||||
package pool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
)
|
||||
|
||||
func benchmarkPoolGetPut(b *testing.B, poolSize int) {
|
||||
connPool := pool.NewConnPool(&pool.Options{
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: poolSize,
|
||||
PoolTimeout: time.Second,
|
||||
IdleTimeout: time.Hour,
|
||||
IdleCheckFrequency: time.Hour,
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
cn, _, err := connPool.Get()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err = connPool.Put(cn); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetPut10Conns(b *testing.B) {
|
||||
benchmarkPoolGetPut(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetPut100Conns(b *testing.B) {
|
||||
benchmarkPoolGetPut(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetPut1000Conns(b *testing.B) {
|
||||
benchmarkPoolGetPut(b, 1000)
|
||||
}
|
||||
|
||||
func benchmarkPoolGetRemove(b *testing.B, poolSize int) {
|
||||
connPool := pool.NewConnPool(&pool.Options{
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: poolSize,
|
||||
PoolTimeout: time.Second,
|
||||
IdleTimeout: time.Hour,
|
||||
IdleCheckFrequency: time.Hour,
|
||||
})
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
cn, _, err := connPool.Get()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err := connPool.Remove(cn); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetRemove10Conns(b *testing.B) {
|
||||
benchmarkPoolGetRemove(b, 10)
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetRemove100Conns(b *testing.B) {
|
||||
benchmarkPoolGetRemove(b, 100)
|
||||
}
|
||||
|
||||
func BenchmarkPoolGetRemove1000Conns(b *testing.B) {
|
||||
benchmarkPoolGetRemove(b, 1000)
|
||||
}
|
||||
35
vendor/github.com/go-redis/redis/internal/pool/main_test.go
сгенерированный
поставляемый
35
vendor/github.com/go-redis/redis/internal/pool/main_test.go
сгенерированный
поставляемый
@@ -1,35 +0,0 @@
|
||||
package pool_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestGinkgoSuite(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "pool")
|
||||
}
|
||||
|
||||
func perform(n int, cbs ...func(int)) {
|
||||
var wg sync.WaitGroup
|
||||
for _, cb := range cbs {
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(1)
|
||||
go func(cb func(int), i int) {
|
||||
defer GinkgoRecover()
|
||||
defer wg.Done()
|
||||
|
||||
cb(i)
|
||||
}(cb, i)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func dummyDialer() (net.Conn, error) {
|
||||
return &net.TCPConn{}, nil
|
||||
}
|
||||
241
vendor/github.com/go-redis/redis/internal/pool/pool_test.go
сгенерированный
поставляемый
241
vendor/github.com/go-redis/redis/internal/pool/pool_test.go
сгенерированный
поставляемый
@@ -1,241 +0,0 @@
|
||||
package pool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("ConnPool", func() {
|
||||
var connPool *pool.ConnPool
|
||||
|
||||
BeforeEach(func() {
|
||||
connPool = pool.NewConnPool(&pool.Options{
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: time.Hour,
|
||||
IdleTimeout: time.Millisecond,
|
||||
IdleCheckFrequency: time.Millisecond,
|
||||
})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
connPool.Close()
|
||||
})
|
||||
|
||||
It("should unblock client when conn is removed", func() {
|
||||
// Reserve one connection.
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Reserve all other connections.
|
||||
var cns []*pool.Conn
|
||||
for i := 0; i < 9; i++ {
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
cns = append(cns, cn)
|
||||
}
|
||||
|
||||
started := make(chan bool, 1)
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
started <- true
|
||||
_, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
done <- true
|
||||
|
||||
err = connPool.Put(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}()
|
||||
<-started
|
||||
|
||||
// Check that Get is blocked.
|
||||
select {
|
||||
case <-done:
|
||||
Fail("Get is not blocked")
|
||||
default:
|
||||
// ok
|
||||
}
|
||||
|
||||
err = connPool.Remove(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that Ping is unblocked.
|
||||
select {
|
||||
case <-done:
|
||||
// ok
|
||||
case <-time.After(time.Second):
|
||||
Fail("Get is not unblocked")
|
||||
}
|
||||
|
||||
for _, cn := range cns {
|
||||
err = connPool.Put(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("conns reaper", func() {
|
||||
const idleTimeout = time.Minute
|
||||
|
||||
var connPool *pool.ConnPool
|
||||
var conns, idleConns, closedConns []*pool.Conn
|
||||
|
||||
BeforeEach(func() {
|
||||
conns = nil
|
||||
closedConns = nil
|
||||
|
||||
connPool = pool.NewConnPool(&pool.Options{
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: time.Second,
|
||||
IdleTimeout: idleTimeout,
|
||||
IdleCheckFrequency: time.Hour,
|
||||
|
||||
OnClose: func(cn *pool.Conn) error {
|
||||
closedConns = append(closedConns, cn)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
// add stale connections
|
||||
idleConns = nil
|
||||
for i := 0; i < 3; i++ {
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
cn.SetUsedAt(time.Now().Add(-2 * idleTimeout))
|
||||
conns = append(conns, cn)
|
||||
idleConns = append(idleConns, cn)
|
||||
}
|
||||
|
||||
// add fresh connections
|
||||
for i := 0; i < 3; i++ {
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conns = append(conns, cn)
|
||||
}
|
||||
|
||||
for _, cn := range conns {
|
||||
Expect(connPool.Put(cn)).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
Expect(connPool.Len()).To(Equal(6))
|
||||
Expect(connPool.FreeLen()).To(Equal(6))
|
||||
|
||||
n, err := connPool.ReapStaleConns()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(3))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
_ = connPool.Close()
|
||||
Expect(connPool.Len()).To(Equal(0))
|
||||
Expect(connPool.FreeLen()).To(Equal(0))
|
||||
Expect(len(closedConns)).To(Equal(len(conns)))
|
||||
Expect(closedConns).To(ConsistOf(conns))
|
||||
})
|
||||
|
||||
It("reaps stale connections", func() {
|
||||
Expect(connPool.Len()).To(Equal(3))
|
||||
Expect(connPool.FreeLen()).To(Equal(3))
|
||||
})
|
||||
|
||||
It("does not reap fresh connections", func() {
|
||||
n, err := connPool.ReapStaleConns()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(0))
|
||||
})
|
||||
|
||||
It("stale connections are closed", func() {
|
||||
Expect(len(closedConns)).To(Equal(len(idleConns)))
|
||||
Expect(closedConns).To(ConsistOf(idleConns))
|
||||
})
|
||||
|
||||
It("pool is functional", func() {
|
||||
for j := 0; j < 3; j++ {
|
||||
var freeCns []*pool.Conn
|
||||
for i := 0; i < 3; i++ {
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cn).NotTo(BeNil())
|
||||
freeCns = append(freeCns, cn)
|
||||
}
|
||||
|
||||
Expect(connPool.Len()).To(Equal(3))
|
||||
Expect(connPool.FreeLen()).To(Equal(0))
|
||||
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cn).NotTo(BeNil())
|
||||
conns = append(conns, cn)
|
||||
|
||||
Expect(connPool.Len()).To(Equal(4))
|
||||
Expect(connPool.FreeLen()).To(Equal(0))
|
||||
|
||||
err = connPool.Remove(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(connPool.Len()).To(Equal(3))
|
||||
Expect(connPool.FreeLen()).To(Equal(0))
|
||||
|
||||
for _, cn := range freeCns {
|
||||
err := connPool.Put(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
Expect(connPool.Len()).To(Equal(3))
|
||||
Expect(connPool.FreeLen()).To(Equal(3))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("race", func() {
|
||||
var connPool *pool.ConnPool
|
||||
var C, N int
|
||||
|
||||
BeforeEach(func() {
|
||||
C, N = 10, 1000
|
||||
if testing.Short() {
|
||||
C = 4
|
||||
N = 100
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
connPool.Close()
|
||||
})
|
||||
|
||||
It("does not happen on Get, Put, and Remove", func() {
|
||||
connPool = pool.NewConnPool(&pool.Options{
|
||||
Dialer: dummyDialer,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: time.Minute,
|
||||
IdleTimeout: time.Millisecond,
|
||||
IdleCheckFrequency: time.Millisecond,
|
||||
})
|
||||
|
||||
perform(C, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if err == nil {
|
||||
Expect(connPool.Put(cn)).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
}, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
cn, _, err := connPool.Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
if err == nil {
|
||||
Expect(connPool.Remove(cn)).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
13
vendor/github.com/go-redis/redis/internal/proto/proto_test.go
сгенерированный
поставляемый
13
vendor/github.com/go-redis/redis/internal/proto/proto_test.go
сгенерированный
поставляемый
@@ -1,13 +0,0 @@
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestGinkgoSuite(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "proto")
|
||||
}
|
||||
48
vendor/github.com/go-redis/redis/internal/proto/reader.go
сгенерированный
поставляемый
48
vendor/github.com/go-redis/redis/internal/proto/reader.go
сгенерированный
поставляемый
@@ -6,7 +6,7 @@ import (
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-redis/redis/internal"
|
||||
"github.com/go-redis/redis/internal/util"
|
||||
)
|
||||
|
||||
const bytesAllocLimit = 1024 * 1024 // 1mb
|
||||
@@ -19,6 +19,16 @@ const (
|
||||
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 {
|
||||
@@ -66,7 +76,7 @@ func (r *Reader) ReadLine() ([]byte, error) {
|
||||
return nil, fmt.Errorf("redis: reply is empty")
|
||||
}
|
||||
if isNilReply(line) {
|
||||
return nil, internal.Nil
|
||||
return nil, Nil
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
@@ -83,7 +93,7 @@ func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) {
|
||||
case StatusReply:
|
||||
return parseStatusValue(line), nil
|
||||
case IntReply:
|
||||
return parseInt(line[1:], 10, 64)
|
||||
return util.ParseInt(line[1:], 10, 64)
|
||||
case StringReply:
|
||||
return r.readTmpBytesValue(line)
|
||||
case ArrayReply:
|
||||
@@ -105,7 +115,7 @@ func (r *Reader) ReadIntReply() (int64, error) {
|
||||
case ErrorReply:
|
||||
return 0, ParseErrorReply(line)
|
||||
case IntReply:
|
||||
return parseInt(line[1:], 10, 64)
|
||||
return util.ParseInt(line[1:], 10, 64)
|
||||
default:
|
||||
return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
|
||||
}
|
||||
@@ -151,7 +161,7 @@ func (r *Reader) ReadFloatReply() (float64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parseFloat(b, 64)
|
||||
return util.ParseFloat(b, 64)
|
||||
}
|
||||
|
||||
func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) {
|
||||
@@ -221,7 +231,7 @@ func (r *Reader) ReadScanReply() ([]string, uint64, error) {
|
||||
|
||||
func (r *Reader) readTmpBytesValue(line []byte) ([]byte, error) {
|
||||
if isNilReply(line) {
|
||||
return nil, internal.Nil
|
||||
return nil, Nil
|
||||
}
|
||||
|
||||
replyLen, err := strconv.Atoi(string(line[1:]))
|
||||
@@ -241,7 +251,7 @@ func (r *Reader) ReadInt() (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parseInt(b, 10, 64)
|
||||
return util.ParseInt(b, 10, 64)
|
||||
}
|
||||
|
||||
func (r *Reader) ReadUint() (uint64, error) {
|
||||
@@ -249,7 +259,7 @@ func (r *Reader) ReadUint() (uint64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parseUint(b, 10, 64)
|
||||
return util.ParseUint(b, 10, 64)
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
@@ -303,7 +313,7 @@ func isNilReply(b []byte) bool {
|
||||
}
|
||||
|
||||
func ParseErrorReply(line []byte) error {
|
||||
return internal.RedisError(string(line[1:]))
|
||||
return RedisError(string(line[1:]))
|
||||
}
|
||||
|
||||
func parseStatusValue(line []byte) []byte {
|
||||
@@ -312,23 +322,7 @@ func parseStatusValue(line []byte) []byte {
|
||||
|
||||
func parseArrayLen(line []byte) (int64, error) {
|
||||
if isNilReply(line) {
|
||||
return 0, internal.Nil
|
||||
return 0, Nil
|
||||
}
|
||||
return parseInt(line[1:], 10, 64)
|
||||
}
|
||||
|
||||
func atoi(b []byte) (int, error) {
|
||||
return strconv.Atoi(internal.BytesToString(b))
|
||||
}
|
||||
|
||||
func parseInt(b []byte, base int, bitSize int) (int64, error) {
|
||||
return strconv.ParseInt(internal.BytesToString(b), base, bitSize)
|
||||
}
|
||||
|
||||
func parseUint(b []byte, base int, bitSize int) (uint64, error) {
|
||||
return strconv.ParseUint(internal.BytesToString(b), base, bitSize)
|
||||
}
|
||||
|
||||
func parseFloat(b []byte, bitSize int) (float64, error) {
|
||||
return strconv.ParseFloat(internal.BytesToString(b), bitSize)
|
||||
return util.ParseInt(line[1:], 10, 64)
|
||||
}
|
||||
|
||||
87
vendor/github.com/go-redis/redis/internal/proto/reader_test.go
сгенерированный
поставляемый
87
vendor/github.com/go-redis/redis/internal/proto/reader_test.go
сгенерированный
поставляемый
@@ -1,87 +0,0 @@
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-redis/redis/internal/proto"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Reader", func() {
|
||||
|
||||
It("should read n bytes", func() {
|
||||
data, err := proto.NewReader(strings.NewReader("ABCDEFGHIJKLMNO")).ReadN(10)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(data)).To(Equal(10))
|
||||
Expect(string(data)).To(Equal("ABCDEFGHIJ"))
|
||||
|
||||
data, err = proto.NewReader(strings.NewReader(strings.Repeat("x", 8192))).ReadN(6000)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(data)).To(Equal(6000))
|
||||
})
|
||||
|
||||
It("should read lines", func() {
|
||||
p := proto.NewReader(strings.NewReader("$5\r\nhello\r\n"))
|
||||
|
||||
data, err := p.ReadLine()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(data)).To(Equal("$5"))
|
||||
|
||||
data, err = p.ReadLine()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(string(data)).To(Equal("hello"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
func BenchmarkReader_ParseReply_Status(b *testing.B) {
|
||||
benchmarkParseReply(b, "+OK\r\n", nil, false)
|
||||
}
|
||||
|
||||
func BenchmarkReader_ParseReply_Int(b *testing.B) {
|
||||
benchmarkParseReply(b, ":1\r\n", nil, false)
|
||||
}
|
||||
|
||||
func BenchmarkReader_ParseReply_Error(b *testing.B) {
|
||||
benchmarkParseReply(b, "-Error message\r\n", nil, true)
|
||||
}
|
||||
|
||||
func BenchmarkReader_ParseReply_String(b *testing.B) {
|
||||
benchmarkParseReply(b, "$5\r\nhello\r\n", nil, false)
|
||||
}
|
||||
|
||||
func BenchmarkReader_ParseReply_Slice(b *testing.B) {
|
||||
benchmarkParseReply(b, "*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n", multiBulkParse, false)
|
||||
}
|
||||
|
||||
func benchmarkParseReply(b *testing.B, reply string, m proto.MultiBulkParse, wanterr bool) {
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.WriteString(reply)
|
||||
}
|
||||
p := proto.NewReader(buf)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := p.ReadReply(m)
|
||||
if !wanterr && err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func multiBulkParse(p *proto.Reader, n int64) (interface{}, error) {
|
||||
vv := make([]interface{}, 0, n)
|
||||
for i := int64(0); i < n; i++ {
|
||||
v, err := p.ReadReply(multiBulkParse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vv = append(vv, v)
|
||||
}
|
||||
return vv, nil
|
||||
}
|
||||
63
vendor/github.com/go-redis/redis/internal/proto/scan.go
сгенерированный
поставляемый
63
vendor/github.com/go-redis/redis/internal/proto/scan.go
сгенерированный
поставляемый
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-redis/redis/internal"
|
||||
"github.com/go-redis/redis/internal/util"
|
||||
)
|
||||
|
||||
func Scan(b []byte, v interface{}) error {
|
||||
@@ -13,80 +13,80 @@ func Scan(b []byte, v interface{}) error {
|
||||
case nil:
|
||||
return fmt.Errorf("redis: Scan(nil)")
|
||||
case *string:
|
||||
*v = internal.BytesToString(b)
|
||||
*v = util.BytesToString(b)
|
||||
return nil
|
||||
case *[]byte:
|
||||
*v = b
|
||||
return nil
|
||||
case *int:
|
||||
var err error
|
||||
*v, err = atoi(b)
|
||||
*v, err = util.Atoi(b)
|
||||
return err
|
||||
case *int8:
|
||||
n, err := parseInt(b, 10, 8)
|
||||
n, err := util.ParseInt(b, 10, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = int8(n)
|
||||
return nil
|
||||
case *int16:
|
||||
n, err := parseInt(b, 10, 16)
|
||||
n, err := util.ParseInt(b, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = int16(n)
|
||||
return nil
|
||||
case *int32:
|
||||
n, err := parseInt(b, 10, 32)
|
||||
n, err := util.ParseInt(b, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = int32(n)
|
||||
return nil
|
||||
case *int64:
|
||||
n, err := parseInt(b, 10, 64)
|
||||
n, err := util.ParseInt(b, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = n
|
||||
return nil
|
||||
case *uint:
|
||||
n, err := parseUint(b, 10, 64)
|
||||
n, err := util.ParseUint(b, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint(n)
|
||||
return nil
|
||||
case *uint8:
|
||||
n, err := parseUint(b, 10, 8)
|
||||
n, err := util.ParseUint(b, 10, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint8(n)
|
||||
return nil
|
||||
case *uint16:
|
||||
n, err := parseUint(b, 10, 16)
|
||||
n, err := util.ParseUint(b, 10, 16)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint16(n)
|
||||
return nil
|
||||
case *uint32:
|
||||
n, err := parseUint(b, 10, 32)
|
||||
n, err := util.ParseUint(b, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = uint32(n)
|
||||
return nil
|
||||
case *uint64:
|
||||
n, err := parseUint(b, 10, 64)
|
||||
n, err := util.ParseUint(b, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = n
|
||||
return nil
|
||||
case *float32:
|
||||
n, err := parseFloat(b, 32)
|
||||
n, err := util.ParseFloat(b, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -94,7 +94,7 @@ func Scan(b []byte, v interface{}) error {
|
||||
return err
|
||||
case *float64:
|
||||
var err error
|
||||
*v, err = parseFloat(b, 64)
|
||||
*v, err = util.ParseFloat(b, 64)
|
||||
return err
|
||||
case *bool:
|
||||
*v = len(b) == 1 && b[0] == '1'
|
||||
@@ -120,7 +120,7 @@ func ScanSlice(data []string, slice interface{}) error {
|
||||
return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice)
|
||||
}
|
||||
|
||||
next := internal.MakeSliceNextElemFunc(v)
|
||||
next := makeSliceNextElemFunc(v)
|
||||
for i, s := range data {
|
||||
elem := next()
|
||||
if err := Scan([]byte(s), elem.Addr().Interface()); err != nil {
|
||||
@@ -131,3 +131,36 @@ func ScanSlice(data []string, slice interface{}) error {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
48
vendor/github.com/go-redis/redis/internal/proto/scan_test.go
сгенерированный
поставляемый
48
vendor/github.com/go-redis/redis/internal/proto/scan_test.go
сгенерированный
поставляемый
@@ -1,48 +0,0 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
type testScanSliceStruct struct {
|
||||
ID int
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s *testScanSliceStruct) MarshalBinary() ([]byte, error) {
|
||||
return json.Marshal(s)
|
||||
}
|
||||
|
||||
func (s *testScanSliceStruct) UnmarshalBinary(b []byte) error {
|
||||
return json.Unmarshal(b, s)
|
||||
}
|
||||
|
||||
var _ = Describe("ScanSlice", func() {
|
||||
data := []string{
|
||||
`{"ID":-1,"Name":"Back Yu"}`,
|
||||
`{"ID":1,"Name":"szyhf"}`,
|
||||
}
|
||||
|
||||
It("[]testScanSliceStruct", func() {
|
||||
var slice []testScanSliceStruct
|
||||
err := ScanSlice(data, &slice)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(slice).To(Equal([]testScanSliceStruct{
|
||||
{-1, "Back Yu"},
|
||||
{1, "szyhf"},
|
||||
}))
|
||||
})
|
||||
|
||||
It("var testContainer []*testScanSliceStruct", func() {
|
||||
var slice []*testScanSliceStruct
|
||||
err := ScanSlice(data, &slice)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(slice).To(Equal([]*testScanSliceStruct{
|
||||
{-1, "Back Yu"},
|
||||
{1, "szyhf"},
|
||||
}))
|
||||
})
|
||||
})
|
||||
18
vendor/github.com/go-redis/redis/internal/proto/write_buffer.go
сгенерированный
поставляемый
18
vendor/github.com/go-redis/redis/internal/proto/write_buffer.go
сгенерированный
поставляемый
@@ -71,17 +71,15 @@ func (w *WriteBuffer) append(val interface{}) error {
|
||||
} else {
|
||||
w.AppendString("0")
|
||||
}
|
||||
default:
|
||||
if bm, ok := val.(encoding.BinaryMarshaler); ok {
|
||||
bb, err := bm.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.AppendBytes(bb)
|
||||
} else {
|
||||
return fmt.Errorf(
|
||||
"redis: can't marshal %T (consider implementing encoding.BinaryMarshaler)", val)
|
||||
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
|
||||
}
|
||||
|
||||
63
vendor/github.com/go-redis/redis/internal/proto/write_buffer_test.go
сгенерированный
поставляемый
63
vendor/github.com/go-redis/redis/internal/proto/write_buffer_test.go
сгенерированный
поставляемый
@@ -1,63 +0,0 @@
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/internal/proto"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("WriteBuffer", func() {
|
||||
var buf *proto.WriteBuffer
|
||||
|
||||
BeforeEach(func() {
|
||||
buf = proto.NewWriteBuffer()
|
||||
})
|
||||
|
||||
It("should reset", func() {
|
||||
buf.AppendString("string")
|
||||
Expect(buf.Len()).To(Equal(12))
|
||||
buf.Reset()
|
||||
Expect(buf.Len()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("should append args", func() {
|
||||
err := buf.Append([]interface{}{
|
||||
"string",
|
||||
12,
|
||||
34.56,
|
||||
[]byte{'b', 'y', 't', 'e', 's'},
|
||||
true,
|
||||
nil,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(buf.Bytes()).To(Equal([]byte("*6\r\n" +
|
||||
"$6\r\nstring\r\n" +
|
||||
"$2\r\n12\r\n" +
|
||||
"$5\r\n34.56\r\n" +
|
||||
"$5\r\nbytes\r\n" +
|
||||
"$1\r\n1\r\n" +
|
||||
"$0\r\n" +
|
||||
"\r\n")))
|
||||
})
|
||||
|
||||
It("should append marshalable args", func() {
|
||||
err := buf.Append([]interface{}{time.Unix(1414141414, 0)})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(buf.Len()).To(Equal(26))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
func BenchmarkWriteBuffer_Append(b *testing.B) {
|
||||
buf := proto.NewWriteBuffer()
|
||||
args := []interface{}{"hello", "world", "foo", "bar"}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.Append(args)
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
64
vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go
сгенерированный
поставляемый
Обычный файл
64
vendor/github.com/go-redis/redis/internal/singleflight/singleflight.go
сгенерированный
поставляемый
Обычный файл
@@ -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
|
||||
}
|
||||
37
vendor/github.com/go-redis/redis/internal/util.go
сгенерированный
поставляемый
37
vendor/github.com/go-redis/redis/internal/util.go
сгенерированный
поставляемый
@@ -1,6 +1,6 @@
|
||||
package internal
|
||||
|
||||
import "reflect"
|
||||
import "github.com/go-redis/redis/internal/util"
|
||||
|
||||
func ToLower(s string) string {
|
||||
if isLower(s) {
|
||||
@@ -15,7 +15,7 @@ func ToLower(s string) string {
|
||||
}
|
||||
b[i] = c
|
||||
}
|
||||
return BytesToString(b)
|
||||
return util.BytesToString(b)
|
||||
}
|
||||
|
||||
func isLower(s string) bool {
|
||||
@@ -27,36 +27,3 @@ func isLower(s string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/go-redis/redis/internal/safe.go → vendor/github.com/go-redis/redis/internal/util/safe.go
сгенерированный
поставляемый
2
vendor/github.com/go-redis/redis/internal/safe.go → vendor/github.com/go-redis/redis/internal/util/safe.go
сгенерированный
поставляемый
@@ -1,6 +1,6 @@
|
||||
// +build appengine
|
||||
|
||||
package internal
|
||||
package util
|
||||
|
||||
func BytesToString(b []byte) string {
|
||||
return string(b)
|
||||
19
vendor/github.com/go-redis/redis/internal/util/strconv.go
сгенерированный
поставляемый
Обычный файл
19
vendor/github.com/go-redis/redis/internal/util/strconv.go
сгенерированный
поставляемый
Обычный файл
@@ -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)
|
||||
}
|
||||
2
vendor/github.com/go-redis/redis/internal/unsafe.go → vendor/github.com/go-redis/redis/internal/util/unsafe.go
сгенерированный
поставляемый
2
vendor/github.com/go-redis/redis/internal/unsafe.go → vendor/github.com/go-redis/redis/internal/util/unsafe.go
сгенерированный
поставляемый
@@ -1,6 +1,6 @@
|
||||
// +build !appengine
|
||||
|
||||
package internal
|
||||
package util
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
136
vendor/github.com/go-redis/redis/iterator_test.go
сгенерированный
поставляемый
136
vendor/github.com/go-redis/redis/iterator_test.go
сгенерированный
поставляемый
@@ -1,136 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("ScanIterator", func() {
|
||||
var client *redis.Client
|
||||
|
||||
var seed = func(n int) error {
|
||||
pipe := client.Pipeline()
|
||||
for i := 1; i <= n; i++ {
|
||||
pipe.Set(fmt.Sprintf("K%02d", i), "x", 0).Err()
|
||||
}
|
||||
_, err := pipe.Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
var extraSeed = func(n int, m int) error {
|
||||
pipe := client.Pipeline()
|
||||
for i := 1; i <= m; i++ {
|
||||
pipe.Set(fmt.Sprintf("A%02d", i), "x", 0).Err()
|
||||
}
|
||||
for i := 1; i <= n; i++ {
|
||||
pipe.Set(fmt.Sprintf("K%02d", i), "x", 0).Err()
|
||||
}
|
||||
_, err := pipe.Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
var hashKey = "K_HASHTEST"
|
||||
var hashSeed = func(n int) error {
|
||||
pipe := client.Pipeline()
|
||||
for i := 1; i <= n; i++ {
|
||||
pipe.HSet(hashKey, fmt.Sprintf("K%02d", i), "x").Err()
|
||||
}
|
||||
_, err := pipe.Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should scan across empty DBs", func() {
|
||||
iter := client.Scan(0, "", 10).Iterator()
|
||||
Expect(iter.Next()).To(BeFalse())
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should scan across one page", func() {
|
||||
Expect(seed(7)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
iter := client.Scan(0, "", 0).Iterator()
|
||||
for iter.Next() {
|
||||
vals = append(vals, iter.Val())
|
||||
}
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(ConsistOf([]string{"K01", "K02", "K03", "K04", "K05", "K06", "K07"}))
|
||||
})
|
||||
|
||||
It("should scan across multiple pages", func() {
|
||||
Expect(seed(71)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
iter := client.Scan(0, "", 10).Iterator()
|
||||
for iter.Next() {
|
||||
vals = append(vals, iter.Val())
|
||||
}
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(71))
|
||||
Expect(vals).To(ContainElement("K01"))
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
})
|
||||
|
||||
It("should hscan across multiple pages", func() {
|
||||
Expect(hashSeed(71)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
iter := client.HScan(hashKey, 0, "", 10).Iterator()
|
||||
for iter.Next() {
|
||||
vals = append(vals, iter.Val())
|
||||
}
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(71 * 2))
|
||||
Expect(vals).To(ContainElement("K01"))
|
||||
Expect(vals).To(ContainElement("K71"))
|
||||
})
|
||||
|
||||
It("should scan to page borders", func() {
|
||||
Expect(seed(20)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
iter := client.Scan(0, "", 10).Iterator()
|
||||
for iter.Next() {
|
||||
vals = append(vals, iter.Val())
|
||||
}
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(20))
|
||||
})
|
||||
|
||||
It("should scan with match", func() {
|
||||
Expect(seed(33)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
iter := client.Scan(0, "K*2*", 10).Iterator()
|
||||
for iter.Next() {
|
||||
vals = append(vals, iter.Val())
|
||||
}
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(13))
|
||||
})
|
||||
|
||||
It("should scan with match across empty pages", func() {
|
||||
Expect(extraSeed(2, 10)).NotTo(HaveOccurred())
|
||||
|
||||
var vals []string
|
||||
iter := client.Scan(0, "K*", 1).Iterator()
|
||||
for iter.Next() {
|
||||
vals = append(vals, iter.Val())
|
||||
}
|
||||
Expect(iter.Err()).NotTo(HaveOccurred())
|
||||
Expect(vals).To(HaveLen(2))
|
||||
})
|
||||
})
|
||||
351
vendor/github.com/go-redis/redis/main_test.go
сгенерированный
поставляемый
351
vendor/github.com/go-redis/redis/main_test.go
сгенерированный
поставляемый
@@ -1,351 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const (
|
||||
redisPort = "6380"
|
||||
redisAddr = ":" + redisPort
|
||||
redisSecondaryPort = "6381"
|
||||
)
|
||||
|
||||
const (
|
||||
ringShard1Port = "6390"
|
||||
ringShard2Port = "6391"
|
||||
)
|
||||
|
||||
const (
|
||||
sentinelName = "mymaster"
|
||||
sentinelMasterPort = "8123"
|
||||
sentinelSlave1Port = "8124"
|
||||
sentinelSlave2Port = "8125"
|
||||
sentinelPort = "8126"
|
||||
)
|
||||
|
||||
var (
|
||||
redisMain *redisProcess
|
||||
ringShard1, ringShard2 *redisProcess
|
||||
sentinelMaster, sentinelSlave1, sentinelSlave2, sentinel *redisProcess
|
||||
)
|
||||
|
||||
var cluster = &clusterScenario{
|
||||
ports: []string{"8220", "8221", "8222", "8223", "8224", "8225"},
|
||||
nodeIds: make([]string, 6),
|
||||
processes: make(map[string]*redisProcess, 6),
|
||||
clients: make(map[string]*redis.Client, 6),
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
var err error
|
||||
|
||||
redisMain, err = startRedis(redisPort)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ringShard1, err = startRedis(ringShard1Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ringShard2, err = startRedis(ringShard2Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinelMaster, err = startRedis(sentinelMasterPort)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinel, err = startSentinel(sentinelPort, sentinelName, sentinelMasterPort)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinelSlave1, err = startRedis(
|
||||
sentinelSlave1Port, "--slaveof", "127.0.0.1", sentinelMasterPort)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sentinelSlave2, err = startRedis(
|
||||
sentinelSlave2Port, "--slaveof", "127.0.0.1", sentinelMasterPort)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(startCluster(cluster)).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
Expect(redisMain.Close()).NotTo(HaveOccurred())
|
||||
|
||||
Expect(ringShard1.Close()).NotTo(HaveOccurred())
|
||||
Expect(ringShard2.Close()).NotTo(HaveOccurred())
|
||||
|
||||
Expect(sentinel.Close()).NotTo(HaveOccurred())
|
||||
Expect(sentinelSlave1.Close()).NotTo(HaveOccurred())
|
||||
Expect(sentinelSlave2.Close()).NotTo(HaveOccurred())
|
||||
Expect(sentinelMaster.Close()).NotTo(HaveOccurred())
|
||||
|
||||
Expect(stopCluster(cluster)).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
func TestGinkgoSuite(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "go-redis")
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
func redisOptions() *redis.Options {
|
||||
return &redis.Options{
|
||||
Addr: redisAddr,
|
||||
DB: 15,
|
||||
DialTimeout: 10 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: 30 * time.Second,
|
||||
IdleTimeout: 500 * time.Millisecond,
|
||||
IdleCheckFrequency: 500 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
func redisClusterOptions() *redis.ClusterOptions {
|
||||
return &redis.ClusterOptions{
|
||||
DialTimeout: 10 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: 30 * time.Second,
|
||||
IdleTimeout: 500 * time.Millisecond,
|
||||
IdleCheckFrequency: 500 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
func redisRingOptions() *redis.RingOptions {
|
||||
return &redis.RingOptions{
|
||||
Addrs: map[string]string{
|
||||
"ringShardOne": ":" + ringShard1Port,
|
||||
"ringShardTwo": ":" + ringShard2Port,
|
||||
},
|
||||
DialTimeout: 10 * time.Second,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
PoolSize: 10,
|
||||
PoolTimeout: 30 * time.Second,
|
||||
IdleTimeout: 500 * time.Millisecond,
|
||||
IdleCheckFrequency: 500 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
func perform(n int, cbs ...func(int)) {
|
||||
var wg sync.WaitGroup
|
||||
for _, cb := range cbs {
|
||||
for i := 0; i < n; i++ {
|
||||
wg.Add(1)
|
||||
go func(cb func(int), i int) {
|
||||
defer GinkgoRecover()
|
||||
defer wg.Done()
|
||||
|
||||
cb(i)
|
||||
}(cb, i)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func eventually(fn func() error, timeout time.Duration) error {
|
||||
var exit int32
|
||||
errCh := make(chan error)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
for atomic.LoadInt32(&exit) == 0 {
|
||||
err := fn()
|
||||
if err == nil {
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case errCh <- err:
|
||||
default:
|
||||
}
|
||||
time.Sleep(timeout / 100)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-time.After(timeout):
|
||||
atomic.StoreInt32(&exit, 1)
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("timeout after %s", timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func execCmd(name string, args ...string) (*os.Process, error) {
|
||||
cmd := exec.Command(name, args...)
|
||||
if testing.Verbose() {
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
return cmd.Process, cmd.Start()
|
||||
}
|
||||
|
||||
func connectTo(port string) (*redis.Client, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: ":" + port,
|
||||
})
|
||||
|
||||
err := eventually(func() error {
|
||||
return client.Ping().Err()
|
||||
}, 30*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
type redisProcess struct {
|
||||
*os.Process
|
||||
*redis.Client
|
||||
}
|
||||
|
||||
func (p *redisProcess) Close() error {
|
||||
if err := p.Kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := eventually(func() error {
|
||||
if err := p.Client.Ping().Err(); err != nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New("client is not shutdown")
|
||||
}, 10*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Client.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server"))
|
||||
redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis.conf"))
|
||||
)
|
||||
|
||||
func redisDir(port string) (string, error) {
|
||||
dir, err := filepath.Abs(filepath.Join("testdata", "instances", port))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.RemoveAll(dir); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0775); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func startRedis(port string, args ...string) (*redisProcess, error) {
|
||||
dir, err := redisDir(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = exec.Command("cp", "-f", redisServerConf, dir).Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseArgs := []string{filepath.Join(dir, "redis.conf"), "--port", port, "--dir", dir}
|
||||
process, err := execCmd(redisServerBin, append(baseArgs, args...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := connectTo(port)
|
||||
if err != nil {
|
||||
process.Kill()
|
||||
return nil, err
|
||||
}
|
||||
return &redisProcess{process, client}, err
|
||||
}
|
||||
|
||||
func startSentinel(port, masterName, masterPort string) (*redisProcess, error) {
|
||||
dir, err := redisDir(port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
process, err := execCmd(redisServerBin, os.DevNull, "--sentinel", "--port", port, "--dir", dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := connectTo(port)
|
||||
if err != nil {
|
||||
process.Kill()
|
||||
return nil, err
|
||||
}
|
||||
for _, cmd := range []*redis.StatusCmd{
|
||||
redis.NewStatusCmd("SENTINEL", "MONITOR", masterName, "127.0.0.1", masterPort, "1"),
|
||||
redis.NewStatusCmd("SENTINEL", "SET", masterName, "down-after-milliseconds", "500"),
|
||||
redis.NewStatusCmd("SENTINEL", "SET", masterName, "failover-timeout", "1000"),
|
||||
redis.NewStatusCmd("SENTINEL", "SET", masterName, "parallel-syncs", "1"),
|
||||
} {
|
||||
client.Process(cmd)
|
||||
if err := cmd.Err(); err != nil {
|
||||
process.Kill()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &redisProcess{process, client}, nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type badConnError string
|
||||
|
||||
func (e badConnError) Error() string { return string(e) }
|
||||
func (e badConnError) Timeout() bool { return false }
|
||||
func (e badConnError) Temporary() bool { return false }
|
||||
|
||||
type badConn struct {
|
||||
net.TCPConn
|
||||
|
||||
readDelay, writeDelay time.Duration
|
||||
readErr, writeErr error
|
||||
}
|
||||
|
||||
var _ net.Conn = &badConn{}
|
||||
|
||||
func (cn *badConn) Read([]byte) (int, error) {
|
||||
if cn.readDelay != 0 {
|
||||
time.Sleep(cn.readDelay)
|
||||
}
|
||||
if cn.readErr != nil {
|
||||
return 0, cn.readErr
|
||||
}
|
||||
return 0, badConnError("bad connection")
|
||||
}
|
||||
|
||||
func (cn *badConn) Write([]byte) (int, error) {
|
||||
if cn.writeDelay != 0 {
|
||||
time.Sleep(cn.writeDelay)
|
||||
}
|
||||
if cn.writeErr != nil {
|
||||
return 0, cn.writeErr
|
||||
}
|
||||
return 0, badConnError("bad connection")
|
||||
}
|
||||
94
vendor/github.com/go-redis/redis/options_test.go
сгенерированный
поставляемый
94
vendor/github.com/go-redis/redis/options_test.go
сгенерированный
поставляемый
@@ -1,94 +0,0 @@
|
||||
// +build go1.7
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseURL(t *testing.T) {
|
||||
cases := []struct {
|
||||
u string
|
||||
addr string
|
||||
db int
|
||||
tls bool
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"redis://localhost:123/1",
|
||||
"localhost:123",
|
||||
1, false, nil,
|
||||
},
|
||||
{
|
||||
"redis://localhost:123",
|
||||
"localhost:123",
|
||||
0, false, nil,
|
||||
},
|
||||
{
|
||||
"redis://localhost/1",
|
||||
"localhost:6379",
|
||||
1, false, nil,
|
||||
},
|
||||
{
|
||||
"redis://12345",
|
||||
"12345:6379",
|
||||
0, false, nil,
|
||||
},
|
||||
{
|
||||
"rediss://localhost:123",
|
||||
"localhost:123",
|
||||
0, true, nil,
|
||||
},
|
||||
{
|
||||
"redis://localhost/?abc=123",
|
||||
"",
|
||||
0, false, errors.New("no options supported"),
|
||||
},
|
||||
{
|
||||
"http://google.com",
|
||||
"",
|
||||
0, false, errors.New("invalid redis URL scheme: http"),
|
||||
},
|
||||
{
|
||||
"redis://localhost/1/2/3/4",
|
||||
"",
|
||||
0, false, errors.New("invalid redis URL path: /1/2/3/4"),
|
||||
},
|
||||
{
|
||||
"12345",
|
||||
"",
|
||||
0, false, errors.New("invalid redis URL scheme: "),
|
||||
},
|
||||
{
|
||||
"redis://localhost/iamadatabase",
|
||||
"",
|
||||
0, false, errors.New(`invalid redis database number: "iamadatabase"`),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.u, func(t *testing.T) {
|
||||
o, err := ParseURL(c.u)
|
||||
if c.err == nil && err != nil {
|
||||
t.Fatalf("unexpected error: %q", err)
|
||||
return
|
||||
}
|
||||
if c.err != nil && err != nil {
|
||||
if c.err.Error() != err.Error() {
|
||||
t.Fatalf("got %q, expected %q", err, c.err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if o.Addr != c.addr {
|
||||
t.Errorf("got %q, want %q", o.Addr, c.addr)
|
||||
}
|
||||
if o.DB != c.db {
|
||||
t.Errorf("got %q, expected %q", o.DB, c.db)
|
||||
}
|
||||
if c.tls && o.TLSConfig == nil {
|
||||
t.Errorf("got nil TLSConfig, expected a TLSConfig")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/go-redis/redis/parser.go
сгенерированный
поставляемый
26
vendor/github.com/go-redis/redis/parser.go
сгенерированный
поставляемый
@@ -14,17 +14,23 @@ 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 {
|
||||
vals = append(vals, nil)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
switch vv := v.(type) {
|
||||
case []byte:
|
||||
vals = append(vals, string(vv))
|
||||
default:
|
||||
vals = append(vals, v)
|
||||
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
|
||||
|
||||
80
vendor/github.com/go-redis/redis/pipeline_test.go
сгенерированный
поставляемый
80
vendor/github.com/go-redis/redis/pipeline_test.go
сгенерированный
поставляемый
@@ -1,80 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("pipelining", func() {
|
||||
var client *redis.Client
|
||||
var pipe *redis.Pipeline
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("supports block style", func() {
|
||||
var get *redis.StringCmd
|
||||
cmds, err := client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
get = pipe.Get("foo")
|
||||
return nil
|
||||
})
|
||||
Expect(err).To(Equal(redis.Nil))
|
||||
Expect(cmds).To(HaveLen(1))
|
||||
Expect(cmds[0]).To(Equal(get))
|
||||
Expect(get.Err()).To(Equal(redis.Nil))
|
||||
Expect(get.Val()).To(Equal(""))
|
||||
})
|
||||
|
||||
assertPipeline := func() {
|
||||
It("returns no errors when there are no commands", func() {
|
||||
_, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("discards queued commands", func() {
|
||||
pipe.Get("key")
|
||||
pipe.Discard()
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(BeNil())
|
||||
})
|
||||
|
||||
It("handles val/err", func() {
|
||||
err := client.Set("key", "value", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
get := pipe.Get("key")
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(1))
|
||||
|
||||
val, err := get.Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("value"))
|
||||
})
|
||||
}
|
||||
|
||||
Describe("Pipeline", func() {
|
||||
BeforeEach(func() {
|
||||
pipe = client.Pipeline().(*redis.Pipeline)
|
||||
})
|
||||
|
||||
assertPipeline()
|
||||
})
|
||||
|
||||
Describe("TxPipeline", func() {
|
||||
BeforeEach(func() {
|
||||
pipe = client.TxPipeline().(*redis.Pipeline)
|
||||
})
|
||||
|
||||
assertPipeline()
|
||||
})
|
||||
})
|
||||
143
vendor/github.com/go-redis/redis/pool_test.go
сгенерированный
поставляемый
143
vendor/github.com/go-redis/redis/pool_test.go
сгенерированный
поставляемый
@@ -1,143 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("pool", func() {
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("respects max size", func() {
|
||||
perform(1000, func(id int) {
|
||||
val, err := client.Ping().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("PONG"))
|
||||
})
|
||||
|
||||
pool := client.Pool()
|
||||
Expect(pool.Len()).To(BeNumerically("<=", 10))
|
||||
Expect(pool.FreeLen()).To(BeNumerically("<=", 10))
|
||||
Expect(pool.Len()).To(Equal(pool.FreeLen()))
|
||||
})
|
||||
|
||||
It("respects max size on multi", func() {
|
||||
perform(1000, func(id int) {
|
||||
var ping *redis.StatusCmd
|
||||
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
ping = pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(1))
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(ping.Err()).NotTo(HaveOccurred())
|
||||
Expect(ping.Val()).To(Equal("PONG"))
|
||||
})
|
||||
|
||||
pool := client.Pool()
|
||||
Expect(pool.Len()).To(BeNumerically("<=", 10))
|
||||
Expect(pool.FreeLen()).To(BeNumerically("<=", 10))
|
||||
Expect(pool.Len()).To(Equal(pool.FreeLen()))
|
||||
})
|
||||
|
||||
It("respects max size on pipelines", func() {
|
||||
perform(1000, func(id int) {
|
||||
pipe := client.Pipeline()
|
||||
ping := pipe.Ping()
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(1))
|
||||
Expect(ping.Err()).NotTo(HaveOccurred())
|
||||
Expect(ping.Val()).To(Equal("PONG"))
|
||||
Expect(pipe.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
pool := client.Pool()
|
||||
Expect(pool.Len()).To(BeNumerically("<=", 10))
|
||||
Expect(pool.FreeLen()).To(BeNumerically("<=", 10))
|
||||
Expect(pool.Len()).To(Equal(pool.FreeLen()))
|
||||
})
|
||||
|
||||
It("removes broken connections", func() {
|
||||
cn, _, err := client.Pool().Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
cn.SetNetConn(&badConn{})
|
||||
Expect(client.Pool().Put(cn)).NotTo(HaveOccurred())
|
||||
|
||||
err = client.Ping().Err()
|
||||
Expect(err).To(MatchError("bad connection"))
|
||||
|
||||
val, err := client.Ping().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("PONG"))
|
||||
|
||||
pool := client.Pool()
|
||||
Expect(pool.Len()).To(Equal(1))
|
||||
Expect(pool.FreeLen()).To(Equal(1))
|
||||
|
||||
stats := pool.Stats()
|
||||
Expect(stats.Hits).To(Equal(uint32(2)))
|
||||
Expect(stats.Misses).To(Equal(uint32(2)))
|
||||
Expect(stats.Timeouts).To(Equal(uint32(0)))
|
||||
})
|
||||
|
||||
It("reuses connections", func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
val, err := client.Ping().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("PONG"))
|
||||
}
|
||||
|
||||
pool := client.Pool()
|
||||
Expect(pool.Len()).To(Equal(1))
|
||||
Expect(pool.FreeLen()).To(Equal(1))
|
||||
|
||||
stats := pool.Stats()
|
||||
Expect(stats.Hits).To(Equal(uint32(100)))
|
||||
Expect(stats.Misses).To(Equal(uint32(1)))
|
||||
Expect(stats.Timeouts).To(Equal(uint32(0)))
|
||||
})
|
||||
|
||||
It("removes idle connections", func() {
|
||||
stats := client.PoolStats()
|
||||
Expect(stats).To(Equal(&redis.PoolStats{
|
||||
Hits: 0,
|
||||
Misses: 1,
|
||||
Timeouts: 0,
|
||||
TotalConns: 1,
|
||||
FreeConns: 1,
|
||||
StaleConns: 0,
|
||||
}))
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
stats = client.PoolStats()
|
||||
Expect(stats).To(Equal(&redis.PoolStats{
|
||||
Hits: 0,
|
||||
Misses: 1,
|
||||
Timeouts: 0,
|
||||
TotalConns: 0,
|
||||
FreeConns: 0,
|
||||
StaleConns: 1,
|
||||
}))
|
||||
})
|
||||
})
|
||||
70
vendor/github.com/go-redis/redis/pubsub.go
сгенерированный
поставляемый
70
vendor/github.com/go-redis/redis/pubsub.go
сгенерированный
поставляемый
@@ -24,8 +24,8 @@ type PubSub struct {
|
||||
|
||||
mu sync.Mutex
|
||||
cn *pool.Conn
|
||||
channels []string
|
||||
patterns []string
|
||||
channels map[string]struct{}
|
||||
patterns map[string]struct{}
|
||||
closed bool
|
||||
|
||||
cmd *Cmd
|
||||
@@ -67,12 +67,24 @@ func (c *PubSub) _conn(channels []string) (*pool.Conn, error) {
|
||||
func (c *PubSub) resubscribe(cn *pool.Conn) error {
|
||||
var firstErr error
|
||||
if len(c.channels) > 0 {
|
||||
if err := c._subscribe(cn, "subscribe", c.channels...); err != nil && firstErr == nil {
|
||||
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 {
|
||||
if err := c._subscribe(cn, "psubscribe", c.patterns...); err != nil && firstErr == nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -132,7 +144,12 @@ func (c *PubSub) Close() error {
|
||||
func (c *PubSub) Subscribe(channels ...string) error {
|
||||
c.mu.Lock()
|
||||
err := c.subscribe("subscribe", channels...)
|
||||
c.channels = appendIfNotExists(c.channels, channels...)
|
||||
if c.channels == nil {
|
||||
c.channels = make(map[string]struct{})
|
||||
}
|
||||
for _, channel := range channels {
|
||||
c.channels[channel] = struct{}{}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
@@ -142,7 +159,12 @@ func (c *PubSub) Subscribe(channels ...string) error {
|
||||
func (c *PubSub) PSubscribe(patterns ...string) error {
|
||||
c.mu.Lock()
|
||||
err := c.subscribe("psubscribe", patterns...)
|
||||
c.patterns = appendIfNotExists(c.patterns, patterns...)
|
||||
if c.patterns == nil {
|
||||
c.patterns = make(map[string]struct{})
|
||||
}
|
||||
for _, pattern := range patterns {
|
||||
c.patterns[pattern] = struct{}{}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
@@ -152,7 +174,9 @@ func (c *PubSub) PSubscribe(patterns ...string) error {
|
||||
func (c *PubSub) Unsubscribe(channels ...string) error {
|
||||
c.mu.Lock()
|
||||
err := c.subscribe("unsubscribe", channels...)
|
||||
c.channels = remove(c.channels, channels...)
|
||||
for _, channel := range channels {
|
||||
delete(c.channels, channel)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
@@ -162,7 +186,9 @@ func (c *PubSub) Unsubscribe(channels ...string) error {
|
||||
func (c *PubSub) PUnsubscribe(patterns ...string) error {
|
||||
c.mu.Lock()
|
||||
err := c.subscribe("punsubscribe", patterns...)
|
||||
c.patterns = remove(c.patterns, patterns...)
|
||||
for _, pattern := range patterns {
|
||||
delete(c.patterns, pattern)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
@@ -371,31 +397,3 @@ func (c *PubSub) Channel() <-chan *Message {
|
||||
})
|
||||
return c.ch
|
||||
}
|
||||
|
||||
func appendIfNotExists(ss []string, es ...string) []string {
|
||||
loop:
|
||||
for _, e := range es {
|
||||
for _, s := range ss {
|
||||
if s == e {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
ss = append(ss, e)
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func remove(ss []string, es ...string) []string {
|
||||
if len(es) == 0 {
|
||||
return ss[:0]
|
||||
}
|
||||
for _, e := range es {
|
||||
for i, s := range ss {
|
||||
if s == e {
|
||||
ss = append(ss[:i], ss[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
443
vendor/github.com/go-redis/redis/pubsub_test.go
сгенерированный
поставляемый
443
vendor/github.com/go-redis/redis/pubsub_test.go
сгенерированный
поставляемый
@@ -1,443 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("PubSub", func() {
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should support pattern matching", func() {
|
||||
pubsub := client.PSubscribe("mychannel*")
|
||||
defer pubsub.Close()
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
subscr := msgi.(*redis.Subscription)
|
||||
Expect(subscr.Kind).To(Equal("psubscribe"))
|
||||
Expect(subscr.Channel).To(Equal("mychannel*"))
|
||||
Expect(subscr.Count).To(Equal(1))
|
||||
}
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err.(net.Error).Timeout()).To(Equal(true))
|
||||
Expect(msgi).To(BeNil())
|
||||
}
|
||||
|
||||
n, err := client.Publish("mychannel1", "hello").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(1)))
|
||||
|
||||
Expect(pubsub.PUnsubscribe("mychannel*")).NotTo(HaveOccurred())
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
subscr := msgi.(*redis.Message)
|
||||
Expect(subscr.Channel).To(Equal("mychannel1"))
|
||||
Expect(subscr.Pattern).To(Equal("mychannel*"))
|
||||
Expect(subscr.Payload).To(Equal("hello"))
|
||||
}
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
subscr := msgi.(*redis.Subscription)
|
||||
Expect(subscr.Kind).To(Equal("punsubscribe"))
|
||||
Expect(subscr.Channel).To(Equal("mychannel*"))
|
||||
Expect(subscr.Count).To(Equal(0))
|
||||
}
|
||||
|
||||
stats := client.PoolStats()
|
||||
Expect(stats.Misses).To(Equal(uint32(2)))
|
||||
})
|
||||
|
||||
It("should pub/sub channels", func() {
|
||||
channels, err := client.PubSubChannels("mychannel*").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(channels).To(BeEmpty())
|
||||
|
||||
pubsub := client.Subscribe("mychannel", "mychannel2")
|
||||
defer pubsub.Close()
|
||||
|
||||
channels, err = client.PubSubChannels("mychannel*").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(channels).To(ConsistOf([]string{"mychannel", "mychannel2"}))
|
||||
|
||||
channels, err = client.PubSubChannels("").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(channels).To(BeEmpty())
|
||||
|
||||
channels, err = client.PubSubChannels("*").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(channels)).To(BeNumerically(">=", 2))
|
||||
})
|
||||
|
||||
It("should return the numbers of subscribers", func() {
|
||||
pubsub := client.Subscribe("mychannel", "mychannel2")
|
||||
defer pubsub.Close()
|
||||
|
||||
channels, err := client.PubSubNumSub("mychannel", "mychannel2", "mychannel3").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(channels).To(Equal(map[string]int64{
|
||||
"mychannel": 1,
|
||||
"mychannel2": 1,
|
||||
"mychannel3": 0,
|
||||
}))
|
||||
})
|
||||
|
||||
It("should return the numbers of subscribers by pattern", func() {
|
||||
num, err := client.PubSubNumPat().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(num).To(Equal(int64(0)))
|
||||
|
||||
pubsub := client.PSubscribe("*")
|
||||
defer pubsub.Close()
|
||||
|
||||
num, err = client.PubSubNumPat().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(num).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("should pub/sub", func() {
|
||||
pubsub := client.Subscribe("mychannel", "mychannel2")
|
||||
defer pubsub.Close()
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
subscr := msgi.(*redis.Subscription)
|
||||
Expect(subscr.Kind).To(Equal("subscribe"))
|
||||
Expect(subscr.Channel).To(Equal("mychannel"))
|
||||
Expect(subscr.Count).To(Equal(1))
|
||||
}
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
subscr := msgi.(*redis.Subscription)
|
||||
Expect(subscr.Kind).To(Equal("subscribe"))
|
||||
Expect(subscr.Channel).To(Equal("mychannel2"))
|
||||
Expect(subscr.Count).To(Equal(2))
|
||||
}
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err.(net.Error).Timeout()).To(Equal(true))
|
||||
Expect(msgi).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
n, err := client.Publish("mychannel", "hello").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(1)))
|
||||
|
||||
n, err = client.Publish("mychannel2", "hello2").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(1)))
|
||||
|
||||
Expect(pubsub.Unsubscribe("mychannel", "mychannel2")).NotTo(HaveOccurred())
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
msg := msgi.(*redis.Message)
|
||||
Expect(msg.Channel).To(Equal("mychannel"))
|
||||
Expect(msg.Payload).To(Equal("hello"))
|
||||
}
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
msg := msgi.(*redis.Message)
|
||||
Expect(msg.Channel).To(Equal("mychannel2"))
|
||||
Expect(msg.Payload).To(Equal("hello2"))
|
||||
}
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
subscr := msgi.(*redis.Subscription)
|
||||
Expect(subscr.Kind).To(Equal("unsubscribe"))
|
||||
Expect(subscr.Channel).To(Equal("mychannel"))
|
||||
Expect(subscr.Count).To(Equal(1))
|
||||
}
|
||||
|
||||
{
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
subscr := msgi.(*redis.Subscription)
|
||||
Expect(subscr.Kind).To(Equal("unsubscribe"))
|
||||
Expect(subscr.Channel).To(Equal("mychannel2"))
|
||||
Expect(subscr.Count).To(Equal(0))
|
||||
}
|
||||
|
||||
stats := client.PoolStats()
|
||||
Expect(stats.Misses).To(Equal(uint32(2)))
|
||||
})
|
||||
|
||||
It("should ping/pong", func() {
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
_, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = pubsub.Ping("")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
pong := msgi.(*redis.Pong)
|
||||
Expect(pong.Payload).To(Equal(""))
|
||||
})
|
||||
|
||||
It("should ping/pong with payload", func() {
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
_, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = pubsub.Ping("hello")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
msgi, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
pong := msgi.(*redis.Pong)
|
||||
Expect(pong.Payload).To(Equal("hello"))
|
||||
})
|
||||
|
||||
It("should multi-ReceiveMessage", func() {
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
subscr, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(subscr).To(Equal(&redis.Subscription{
|
||||
Kind: "subscribe",
|
||||
Channel: "mychannel",
|
||||
Count: 1,
|
||||
}))
|
||||
|
||||
err = client.Publish("mychannel", "hello").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = client.Publish("mychannel", "world").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
msg, err := pubsub.ReceiveMessage()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(msg.Channel).To(Equal("mychannel"))
|
||||
Expect(msg.Payload).To(Equal("hello"))
|
||||
|
||||
msg, err = pubsub.ReceiveMessage()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(msg.Channel).To(Equal("mychannel"))
|
||||
Expect(msg.Payload).To(Equal("world"))
|
||||
})
|
||||
|
||||
It("should ReceiveMessage after timeout", func() {
|
||||
timeout := 100 * time.Millisecond
|
||||
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
subscr, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(subscr).To(Equal(&redis.Subscription{
|
||||
Kind: "subscribe",
|
||||
Channel: "mychannel",
|
||||
Count: 1,
|
||||
}))
|
||||
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer func() {
|
||||
done <- true
|
||||
}()
|
||||
|
||||
time.Sleep(timeout + 100*time.Millisecond)
|
||||
n, err := client.Publish("mychannel", "hello").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(1)))
|
||||
}()
|
||||
|
||||
msg, err := pubsub.ReceiveMessageTimeout(timeout)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(msg.Channel).To(Equal("mychannel"))
|
||||
Expect(msg.Payload).To(Equal("hello"))
|
||||
|
||||
Eventually(done).Should(Receive())
|
||||
|
||||
stats := client.PoolStats()
|
||||
Expect(stats.Hits).To(Equal(uint32(1)))
|
||||
Expect(stats.Misses).To(Equal(uint32(1)))
|
||||
})
|
||||
|
||||
It("returns an error when subscribe fails", func() {
|
||||
pubsub := client.Subscribe()
|
||||
defer pubsub.Close()
|
||||
|
||||
pubsub.SetNetConn(&badConn{
|
||||
readErr: io.EOF,
|
||||
writeErr: io.EOF,
|
||||
})
|
||||
|
||||
err := pubsub.Subscribe("mychannel")
|
||||
Expect(err).To(MatchError("EOF"))
|
||||
|
||||
err = pubsub.Subscribe("mychannel")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
expectReceiveMessageOnError := func(pubsub *redis.PubSub) {
|
||||
pubsub.SetNetConn(&badConn{
|
||||
readErr: io.EOF,
|
||||
writeErr: io.EOF,
|
||||
})
|
||||
|
||||
done := make(chan bool, 1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer func() {
|
||||
done <- true
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
err := client.Publish("mychannel", "hello").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}()
|
||||
|
||||
msg, err := pubsub.ReceiveMessage()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(msg.Channel).To(Equal("mychannel"))
|
||||
Expect(msg.Payload).To(Equal("hello"))
|
||||
|
||||
Eventually(done).Should(Receive())
|
||||
}
|
||||
|
||||
It("Subscribe should reconnect on ReceiveMessage error", func() {
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
subscr, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(subscr).To(Equal(&redis.Subscription{
|
||||
Kind: "subscribe",
|
||||
Channel: "mychannel",
|
||||
Count: 1,
|
||||
}))
|
||||
|
||||
expectReceiveMessageOnError(pubsub)
|
||||
})
|
||||
|
||||
It("PSubscribe should reconnect on ReceiveMessage error", func() {
|
||||
pubsub := client.PSubscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
subscr, err := pubsub.ReceiveTimeout(time.Second)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(subscr).To(Equal(&redis.Subscription{
|
||||
Kind: "psubscribe",
|
||||
Channel: "mychannel",
|
||||
Count: 1,
|
||||
}))
|
||||
|
||||
expectReceiveMessageOnError(pubsub)
|
||||
})
|
||||
|
||||
It("should return on Close", func() {
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
|
||||
wg.Done()
|
||||
defer wg.Done()
|
||||
|
||||
_, err := pubsub.ReceiveMessage()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(SatisfyAny(
|
||||
MatchError("redis: client is closed"),
|
||||
MatchError("use of closed network connection"), // Go 1.4
|
||||
))
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
wg.Add(1)
|
||||
|
||||
Expect(pubsub.Close()).NotTo(HaveOccurred())
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
It("should ReceiveMessage without a subscription", func() {
|
||||
timeout := 100 * time.Millisecond
|
||||
|
||||
pubsub := client.Subscribe()
|
||||
defer pubsub.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer wg.Done()
|
||||
|
||||
time.Sleep(2 * timeout)
|
||||
|
||||
err := pubsub.Subscribe("mychannel")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
time.Sleep(timeout)
|
||||
|
||||
err = client.Publish("mychannel", "hello").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}()
|
||||
|
||||
msg, err := pubsub.ReceiveMessageTimeout(timeout)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(msg.Channel).To(Equal("mychannel"))
|
||||
Expect(msg.Payload).To(Equal("hello"))
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
It("handles big message payload", func() {
|
||||
pubsub := client.Subscribe("mychannel")
|
||||
defer pubsub.Close()
|
||||
|
||||
ch := pubsub.Channel()
|
||||
|
||||
bigVal := bigVal()
|
||||
err := client.Publish("mychannel", bigVal).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var msg *redis.Message
|
||||
Eventually(ch).Should(Receive(&msg))
|
||||
Expect(msg.Channel).To(Equal("mychannel"))
|
||||
Expect(msg.Payload).To(Equal(string(bigVal)))
|
||||
})
|
||||
})
|
||||
250
vendor/github.com/go-redis/redis/race_test.go
сгенерированный
поставляемый
250
vendor/github.com/go-redis/redis/race_test.go
сгенерированный
поставляемый
@@ -1,250 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("races", func() {
|
||||
var client *redis.Client
|
||||
var C, N int
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).To(BeNil())
|
||||
|
||||
C, N = 10, 1000
|
||||
if testing.Short() {
|
||||
C = 4
|
||||
N = 100
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
err := client.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should echo", func() {
|
||||
perform(C, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
msg := fmt.Sprintf("echo %d %d", id, i)
|
||||
echo, err := client.Echo(msg).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(echo).To(Equal(msg))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
It("should incr", func() {
|
||||
key := "TestIncrFromGoroutines"
|
||||
|
||||
perform(C, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
err := client.Incr(key).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
val, err := client.Get(key).Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal(int64(C * N)))
|
||||
})
|
||||
|
||||
It("should handle many keys", func() {
|
||||
perform(C, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
err := client.Set(
|
||||
fmt.Sprintf("keys.key-%d-%d", id, i),
|
||||
fmt.Sprintf("hello-%d-%d", id, i),
|
||||
0,
|
||||
).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
keys := client.Keys("keys.*")
|
||||
Expect(keys.Err()).NotTo(HaveOccurred())
|
||||
Expect(len(keys.Val())).To(Equal(C * N))
|
||||
})
|
||||
|
||||
It("should handle many keys 2", func() {
|
||||
perform(C, func(id int) {
|
||||
keys := []string{"non-existent-key"}
|
||||
for i := 0; i < N; i++ {
|
||||
key := fmt.Sprintf("keys.key-%d", i)
|
||||
keys = append(keys, key)
|
||||
|
||||
err := client.Set(key, fmt.Sprintf("hello-%d", i), 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
keys = append(keys, "non-existent-key")
|
||||
|
||||
vals, err := client.MGet(keys...).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(vals)).To(Equal(N + 2))
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
Expect(vals[i+1]).To(Equal(fmt.Sprintf("hello-%d", i)))
|
||||
}
|
||||
|
||||
Expect(vals[0]).To(BeNil())
|
||||
Expect(vals[N+1]).To(BeNil())
|
||||
})
|
||||
})
|
||||
|
||||
It("should handle big vals in Get", func() {
|
||||
C, N = 4, 100
|
||||
|
||||
bigVal := bigVal()
|
||||
|
||||
err := client.Set("key", bigVal, 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Reconnect to get new connection.
|
||||
Expect(client.Close()).To(BeNil())
|
||||
client = redis.NewClient(redisOptions())
|
||||
|
||||
perform(C, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
got, err := client.Get("key").Bytes()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(got).To(Equal(bigVal))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
It("should handle big vals in Set", func() {
|
||||
C, N = 4, 100
|
||||
|
||||
bigVal := bigVal()
|
||||
perform(C, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
err := client.Set("key", bigVal, 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
It("should select db", func() {
|
||||
err := client.Set("db", 1, 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
perform(C, func(id int) {
|
||||
opt := redisOptions()
|
||||
opt.DB = id
|
||||
client := redis.NewClient(opt)
|
||||
for i := 0; i < N; i++ {
|
||||
err := client.Set("db", id, 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
n, err := client.Get("db").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(id)))
|
||||
}
|
||||
err := client.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
n, err := client.Get("db").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(1)))
|
||||
})
|
||||
|
||||
It("should select DB with read timeout", func() {
|
||||
perform(C, func(id int) {
|
||||
opt := redisOptions()
|
||||
opt.DB = id
|
||||
opt.ReadTimeout = time.Nanosecond
|
||||
client := redis.NewClient(opt)
|
||||
|
||||
perform(C, func(id int) {
|
||||
err := client.Ping().Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
err := client.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
It("should Watch/Unwatch", func() {
|
||||
err := client.Set("key", "0", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
perform(C, func(id int) {
|
||||
for i := 0; i < N; i++ {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
val, err := tx.Get("key").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).NotTo(Equal(redis.Nil))
|
||||
|
||||
num, err := strconv.ParseInt(val, 10, 64)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Set("key", strconv.FormatInt(num+1, 10), 0)
|
||||
return nil
|
||||
})
|
||||
Expect(cmds).To(HaveLen(1))
|
||||
return err
|
||||
}, "key")
|
||||
if err == redis.TxFailedErr {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
})
|
||||
|
||||
val, err := client.Get("key").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal(int64(C * N)))
|
||||
})
|
||||
|
||||
It("should Pipeline", func() {
|
||||
perform(C, func(id int) {
|
||||
pipe := client.Pipeline()
|
||||
for i := 0; i < N; i++ {
|
||||
pipe.Echo(fmt.Sprint(i))
|
||||
}
|
||||
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(N))
|
||||
|
||||
for i := 0; i < N; i++ {
|
||||
Expect(cmds[i].(*redis.StringCmd).Val()).To(Equal(fmt.Sprint(i)))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
It("should Pipeline", func() {
|
||||
pipe := client.Pipeline()
|
||||
perform(N, func(id int) {
|
||||
pipe.Incr("key")
|
||||
})
|
||||
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(N))
|
||||
|
||||
n, err := client.Get("key").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(N)))
|
||||
})
|
||||
})
|
||||
|
||||
func bigVal() []byte {
|
||||
return bytes.Repeat([]byte{'*'}, 1<<17) // 128kb
|
||||
}
|
||||
95
vendor/github.com/go-redis/redis/redis.go
сгенерированный
поставляемый
95
vendor/github.com/go-redis/redis/redis.go
сгенерированный
поставляемый
@@ -1,6 +1,7 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -11,8 +12,8 @@ import (
|
||||
"github.com/go-redis/redis/internal/proto"
|
||||
)
|
||||
|
||||
// Nil reply redis returned when key does not exist.
|
||||
const Nil = internal.Nil
|
||||
// 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))
|
||||
@@ -22,6 +23,17 @@ 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
|
||||
@@ -84,16 +96,7 @@ func (c *baseClient) initConn(cn *pool.Conn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Temp client to initialize connection.
|
||||
conn := &Conn{
|
||||
baseClient: baseClient{
|
||||
opt: c.opt,
|
||||
connPool: pool.NewSingleConnPool(cn),
|
||||
},
|
||||
}
|
||||
conn.baseClient.init()
|
||||
conn.statefulCmdable.setProcessor(conn.Process)
|
||||
|
||||
conn := newConn(c.opt, cn)
|
||||
_, err := conn.Pipelined(func(pipe Pipeliner) error {
|
||||
if c.opt.Password != "" {
|
||||
pipe.Auth(c.opt.Password)
|
||||
@@ -119,10 +122,7 @@ func (c *baseClient) initConn(cn *pool.Conn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WrapProcess replaces the process func. It takes a function createWrapper
|
||||
// which is supplied by the user. createWrapper takes the old process func as
|
||||
// an input and returns the new wrapper process func. createWrapper should
|
||||
// use call the old process func within the new process func.
|
||||
// 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)
|
||||
}
|
||||
@@ -338,31 +338,50 @@ func (c *baseClient) txPipelineReadQueued(cn *pool.Conn, cmds []Cmder) error {
|
||||
type Client struct {
|
||||
baseClient
|
||||
cmdable
|
||||
}
|
||||
|
||||
func newClient(opt *Options, pool pool.Pooler) *Client {
|
||||
c := Client{
|
||||
baseClient: baseClient{
|
||||
opt: opt,
|
||||
connPool: pool,
|
||||
},
|
||||
}
|
||||
c.baseClient.init()
|
||||
c.cmdable.setProcessor(c.Process)
|
||||
return &c
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewClient returns a client to the Redis Server specified by Options.
|
||||
func NewClient(opt *Options) *Client {
|
||||
opt.init()
|
||||
return newClient(opt, newConnPool(opt))
|
||||
|
||||
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 {
|
||||
c2 := new(Client)
|
||||
*c2 = *c
|
||||
c2.cmdable.setProcessor(c2.Process)
|
||||
return c2
|
||||
cp := *c
|
||||
cp.init()
|
||||
return &cp
|
||||
}
|
||||
|
||||
// Options returns read-only Options that were used to create the client.
|
||||
@@ -442,6 +461,18 @@ type Conn struct {
|
||||
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)
|
||||
}
|
||||
|
||||
38
vendor/github.com/go-redis/redis/redis_context.go
сгенерированный
поставляемый
38
vendor/github.com/go-redis/redis/redis_context.go
сгенерированный
поставляемый
@@ -1,38 +0,0 @@
|
||||
// +build go1.7
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
)
|
||||
|
||||
type baseClient struct {
|
||||
connPool pool.Pooler
|
||||
opt *Options
|
||||
|
||||
process func(Cmder) error
|
||||
processPipeline func([]Cmder) error
|
||||
processTxPipeline func([]Cmder) error
|
||||
|
||||
onClose func() error // hook called when client is closed
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
18
vendor/github.com/go-redis/redis/redis_no_context.go
сгенерированный
поставляемый
18
vendor/github.com/go-redis/redis/redis_no_context.go
сгенерированный
поставляемый
@@ -1,18 +0,0 @@
|
||||
// +build !go1.7
|
||||
|
||||
package redis
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
)
|
||||
|
||||
type baseClient struct {
|
||||
connPool pool.Pooler
|
||||
opt *Options
|
||||
|
||||
process func(Cmder) error
|
||||
processPipeline func([]Cmder) error
|
||||
processTxPipeline func([]Cmder) error
|
||||
|
||||
onClose func() error // hook called when client is closed
|
||||
}
|
||||
364
vendor/github.com/go-redis/redis/redis_test.go
сгенерированный
поставляемый
364
vendor/github.com/go-redis/redis/redis_test.go
сгенерированный
поставляемый
@@ -1,364 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Client", func() {
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
client.Close()
|
||||
})
|
||||
|
||||
It("should Stringer", func() {
|
||||
Expect(client.String()).To(Equal("Redis<:6380 db:15>"))
|
||||
})
|
||||
|
||||
It("should ping", func() {
|
||||
val, err := client.Ping().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("PONG"))
|
||||
})
|
||||
|
||||
It("should return pool stats", func() {
|
||||
Expect(client.PoolStats()).To(BeAssignableToTypeOf(&redis.PoolStats{}))
|
||||
})
|
||||
|
||||
It("should support custom dialers", func() {
|
||||
custom := redis.NewClient(&redis.Options{
|
||||
Addr: ":1234",
|
||||
Dialer: func() (net.Conn, error) {
|
||||
return net.Dial("tcp", redisAddr)
|
||||
},
|
||||
})
|
||||
|
||||
val, err := custom.Ping().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("PONG"))
|
||||
Expect(custom.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should close", func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
err := client.Ping().Err()
|
||||
Expect(err).To(MatchError("redis: client is closed"))
|
||||
})
|
||||
|
||||
It("should close pubsub without closing the client", func() {
|
||||
pubsub := client.Subscribe()
|
||||
Expect(pubsub.Close()).NotTo(HaveOccurred())
|
||||
|
||||
_, err := pubsub.Receive()
|
||||
Expect(err).To(MatchError("redis: client is closed"))
|
||||
Expect(client.Ping().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should close Tx without closing the client", func() {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
_, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(client.Ping().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should close pipeline without closing the client", func() {
|
||||
pipeline := client.Pipeline()
|
||||
Expect(pipeline.Close()).NotTo(HaveOccurred())
|
||||
|
||||
pipeline.Ping()
|
||||
_, err := pipeline.Exec()
|
||||
Expect(err).To(MatchError("redis: client is closed"))
|
||||
|
||||
Expect(client.Ping().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should close pubsub when client is closed", func() {
|
||||
pubsub := client.Subscribe()
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
|
||||
_, err := pubsub.Receive()
|
||||
Expect(err).To(MatchError("redis: client is closed"))
|
||||
|
||||
Expect(pubsub.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should close pipeline when client is closed", func() {
|
||||
pipeline := client.Pipeline()
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
Expect(pipeline.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should select DB", func() {
|
||||
db2 := redis.NewClient(&redis.Options{
|
||||
Addr: redisAddr,
|
||||
DB: 2,
|
||||
})
|
||||
Expect(db2.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
Expect(db2.Get("db").Err()).To(Equal(redis.Nil))
|
||||
Expect(db2.Set("db", 2, 0).Err()).NotTo(HaveOccurred())
|
||||
|
||||
n, err := db2.Get("db").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(2)))
|
||||
|
||||
Expect(client.Get("db").Err()).To(Equal(redis.Nil))
|
||||
|
||||
Expect(db2.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
Expect(db2.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("processes custom commands", func() {
|
||||
cmd := redis.NewCmd("PING")
|
||||
client.Process(cmd)
|
||||
|
||||
// Flush buffers.
|
||||
Expect(client.Echo("hello").Err()).NotTo(HaveOccurred())
|
||||
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd.Val()).To(Equal("PONG"))
|
||||
})
|
||||
|
||||
It("should retry command on network error", func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
|
||||
client = redis.NewClient(&redis.Options{
|
||||
Addr: redisAddr,
|
||||
MaxRetries: 1,
|
||||
})
|
||||
|
||||
// Put bad connection in the pool.
|
||||
cn, _, err := client.Pool().Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cn.SetNetConn(&badConn{})
|
||||
err = client.Pool().Put(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = client.Ping().Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should retry with backoff", func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
|
||||
// use up all the available connections to force a fail
|
||||
connectionHogClient := redis.NewClient(&redis.Options{
|
||||
Addr: redisAddr,
|
||||
MaxRetries: 1,
|
||||
})
|
||||
defer connectionHogClient.Close()
|
||||
|
||||
for i := 0; i <= 1002; i++ {
|
||||
connectionHogClient.Pool().NewConn()
|
||||
}
|
||||
|
||||
clientNoRetry := redis.NewClient(&redis.Options{
|
||||
Addr: redisAddr,
|
||||
PoolSize: 1,
|
||||
MaxRetryBackoff: -1,
|
||||
})
|
||||
defer clientNoRetry.Close()
|
||||
|
||||
clientRetry := redis.NewClient(&redis.Options{
|
||||
Addr: redisAddr,
|
||||
MaxRetries: 5,
|
||||
PoolSize: 1,
|
||||
MaxRetryBackoff: 128 * time.Millisecond,
|
||||
})
|
||||
defer clientRetry.Close()
|
||||
|
||||
startNoRetry := time.Now()
|
||||
err := clientNoRetry.Ping().Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
elapseNoRetry := time.Since(startNoRetry)
|
||||
|
||||
startRetry := time.Now()
|
||||
err = clientRetry.Ping().Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
elapseRetry := time.Since(startRetry)
|
||||
|
||||
Expect(elapseRetry > elapseNoRetry).To(BeTrue())
|
||||
})
|
||||
|
||||
It("should update conn.UsedAt on read/write", func() {
|
||||
cn, _, err := client.Pool().Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cn.UsedAt).NotTo(BeZero())
|
||||
createdAt := cn.UsedAt()
|
||||
|
||||
err = client.Pool().Put(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cn.UsedAt().Equal(createdAt)).To(BeTrue())
|
||||
|
||||
err = client.Ping().Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cn, _, err = client.Pool().Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cn).NotTo(BeNil())
|
||||
Expect(cn.UsedAt().After(createdAt)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("should process command with special chars", func() {
|
||||
set := client.Set("key", "hello1\r\nhello2\r\n", 0)
|
||||
Expect(set.Err()).NotTo(HaveOccurred())
|
||||
Expect(set.Val()).To(Equal("OK"))
|
||||
|
||||
get := client.Get("key")
|
||||
Expect(get.Err()).NotTo(HaveOccurred())
|
||||
Expect(get.Val()).To(Equal("hello1\r\nhello2\r\n"))
|
||||
})
|
||||
|
||||
It("should handle big vals", func() {
|
||||
bigVal := bytes.Repeat([]byte{'*'}, 2e6)
|
||||
|
||||
err := client.Set("key", bigVal, 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Reconnect to get new connection.
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
client = redis.NewClient(redisOptions())
|
||||
|
||||
got, err := client.Get("key").Bytes()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(got).To(Equal(bigVal))
|
||||
})
|
||||
|
||||
It("should call WrapProcess", func() {
|
||||
var wrapperFnCalled bool
|
||||
|
||||
client.WrapProcess(func(oldProcess func(redis.Cmder) error) func(redis.Cmder) error {
|
||||
return func(cmd redis.Cmder) error {
|
||||
wrapperFnCalled = true
|
||||
return oldProcess(cmd)
|
||||
}
|
||||
})
|
||||
|
||||
Expect(client.Ping().Err()).NotTo(HaveOccurred())
|
||||
|
||||
Expect(wrapperFnCalled).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Client timeout", func() {
|
||||
var opt *redis.Options
|
||||
var client *redis.Client
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
testTimeout := func() {
|
||||
It("Ping timeouts", func() {
|
||||
err := client.Ping().Err()
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Pipeline timeouts", func() {
|
||||
_, err := client.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Subscribe timeouts", func() {
|
||||
if opt.WriteTimeout == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
pubsub := client.Subscribe()
|
||||
defer pubsub.Close()
|
||||
|
||||
err := pubsub.Subscribe("_")
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Tx timeouts", func() {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
return tx.Ping().Err()
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Tx Pipeline timeouts", func() {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
_, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.(net.Error).Timeout()).To(BeTrue())
|
||||
})
|
||||
}
|
||||
|
||||
Context("read timeout", func() {
|
||||
BeforeEach(func() {
|
||||
opt = redisOptions()
|
||||
opt.ReadTimeout = time.Nanosecond
|
||||
opt.WriteTimeout = -1
|
||||
client = redis.NewClient(opt)
|
||||
})
|
||||
|
||||
testTimeout()
|
||||
})
|
||||
|
||||
Context("write timeout", func() {
|
||||
BeforeEach(func() {
|
||||
opt = redisOptions()
|
||||
opt.ReadTimeout = -1
|
||||
opt.WriteTimeout = time.Nanosecond
|
||||
client = redis.NewClient(opt)
|
||||
})
|
||||
|
||||
testTimeout()
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Client OnConnect", func() {
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
opt := redisOptions()
|
||||
opt.DB = 0
|
||||
opt.OnConnect = func(cn *redis.Conn) error {
|
||||
return cn.ClientSetName("on_connect").Err()
|
||||
}
|
||||
|
||||
client = redis.NewClient(opt)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("calls OnConnect", func() {
|
||||
name, err := client.ClientGetName().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(name).To(Equal("on_connect"))
|
||||
})
|
||||
})
|
||||
370
vendor/github.com/go-redis/redis/ring.go
сгенерированный
поставляемый
370
vendor/github.com/go-redis/redis/ring.go
сгенерированный
поставляемый
@@ -1,6 +1,7 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@@ -15,6 +16,8 @@ import (
|
||||
"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
|
||||
@@ -85,6 +88,8 @@ func (opt *RingOptions) clientOptions() *Options {
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
type ringShard struct {
|
||||
Client *Client
|
||||
down int32
|
||||
@@ -125,6 +130,150 @@ func (shard *ringShard) Vote(up bool) bool {
|
||||
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.
|
||||
@@ -142,33 +291,22 @@ func (shard *ringShard) Vote(up bool) bool {
|
||||
type Ring struct {
|
||||
cmdable
|
||||
|
||||
opt *RingOptions
|
||||
nreplicas int
|
||||
ctx context.Context
|
||||
|
||||
mu sync.RWMutex
|
||||
hash *consistenthash.Map
|
||||
shards map[string]*ringShard
|
||||
shardsList []*ringShard
|
||||
opt *RingOptions
|
||||
shards *ringShards
|
||||
cmdsInfoCache *cmdsInfoCache
|
||||
|
||||
processPipeline func([]Cmder) error
|
||||
|
||||
cmdsInfoOnce internal.Once
|
||||
cmdsInfo map[string]*CommandInfo
|
||||
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewRing(opt *RingOptions) *Ring {
|
||||
const nreplicas = 100
|
||||
|
||||
opt.init()
|
||||
|
||||
ring := &Ring{
|
||||
opt: opt,
|
||||
nreplicas: nreplicas,
|
||||
|
||||
hash: consistenthash.New(nreplicas, nil),
|
||||
shards: make(map[string]*ringShard),
|
||||
opt: opt,
|
||||
shards: newRingShards(),
|
||||
cmdsInfoCache: newCmdsInfoCache(),
|
||||
}
|
||||
ring.processPipeline = ring.defaultProcessPipeline
|
||||
ring.cmdable.setProcessor(ring.Process)
|
||||
@@ -176,21 +314,33 @@ func NewRing(opt *RingOptions) *Ring {
|
||||
for name, addr := range opt.Addrs {
|
||||
clopt := opt.clientOptions()
|
||||
clopt.Addr = addr
|
||||
ring.addShard(name, NewClient(clopt))
|
||||
ring.shards.Add(name, NewClient(clopt))
|
||||
}
|
||||
|
||||
go ring.heartbeat()
|
||||
go ring.shards.Heartbeat(opt.HeartbeatFrequency)
|
||||
|
||||
return ring
|
||||
}
|
||||
|
||||
func (c *Ring) addShard(name string, cl *Client) {
|
||||
shard := &ringShard{Client: cl}
|
||||
c.mu.Lock()
|
||||
c.hash.Add(name)
|
||||
c.shards[name] = shard
|
||||
c.shardsList = append(c.shardsList, shard)
|
||||
c.mu.Unlock()
|
||||
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.
|
||||
@@ -204,10 +354,7 @@ func (c *Ring) retryBackoff(attempt int) time.Duration {
|
||||
|
||||
// PoolStats returns accumulated connection pool stats.
|
||||
func (c *Ring) PoolStats() *PoolStats {
|
||||
c.mu.RLock()
|
||||
shards := c.shardsList
|
||||
c.mu.RUnlock()
|
||||
|
||||
shards := c.shards.List()
|
||||
var acc PoolStats
|
||||
for _, shard := range shards {
|
||||
s := shard.Client.connPool.Stats()
|
||||
@@ -226,7 +373,7 @@ func (c *Ring) Subscribe(channels ...string) *PubSub {
|
||||
panic("at least one channel is required")
|
||||
}
|
||||
|
||||
shard, err := c.shardByKey(channels[0])
|
||||
shard, err := c.shards.GetByKey(channels[0])
|
||||
if err != nil {
|
||||
// TODO: return PubSub with sticky error
|
||||
panic(err)
|
||||
@@ -240,7 +387,7 @@ func (c *Ring) PSubscribe(channels ...string) *PubSub {
|
||||
panic("at least one channel is required")
|
||||
}
|
||||
|
||||
shard, err := c.shardByKey(channels[0])
|
||||
shard, err := c.shards.GetByKey(channels[0])
|
||||
if err != nil {
|
||||
// TODO: return PubSub with sticky error
|
||||
panic(err)
|
||||
@@ -251,10 +398,7 @@ func (c *Ring) PSubscribe(channels ...string) *PubSub {
|
||||
// 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 {
|
||||
c.mu.RLock()
|
||||
shards := c.shardsList
|
||||
c.mu.RUnlock()
|
||||
|
||||
shards := c.shards.List()
|
||||
var wg sync.WaitGroup
|
||||
errCh := make(chan error, 1)
|
||||
for _, shard := range shards {
|
||||
@@ -285,81 +429,38 @@ func (c *Ring) ForEachShard(fn func(client *Client) error) error {
|
||||
}
|
||||
|
||||
func (c *Ring) cmdInfo(name string) *CommandInfo {
|
||||
err := c.cmdsInfoOnce.Do(func() error {
|
||||
c.mu.RLock()
|
||||
shards := c.shardsList
|
||||
c.mu.RUnlock()
|
||||
|
||||
var firstErr error
|
||||
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 {
|
||||
c.cmdsInfo = cmdsInfo
|
||||
return nil
|
||||
return cmdsInfo, nil
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
return nil, firstErr
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if c.cmdsInfo == nil {
|
||||
return nil
|
||||
}
|
||||
info := c.cmdsInfo[name]
|
||||
info := cmdsInfo[name]
|
||||
if info == nil {
|
||||
internal.Logf("info for cmd=%s not found", name)
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (c *Ring) shardByKey(key string) (*ringShard, error) {
|
||||
key = hashtag.Key(key)
|
||||
|
||||
c.mu.RLock()
|
||||
|
||||
if c.closed {
|
||||
c.mu.RUnlock()
|
||||
return nil, pool.ErrClosed
|
||||
}
|
||||
|
||||
name := c.hash.Get(key)
|
||||
if name == "" {
|
||||
c.mu.RUnlock()
|
||||
return nil, errRingShardsDown
|
||||
}
|
||||
|
||||
shard := c.shards[name]
|
||||
c.mu.RUnlock()
|
||||
return shard, nil
|
||||
}
|
||||
|
||||
func (c *Ring) randomShard() (*ringShard, error) {
|
||||
return c.shardByKey(strconv.Itoa(rand.Int()))
|
||||
}
|
||||
|
||||
func (c *Ring) shardByName(name string) (*ringShard, error) {
|
||||
if name == "" {
|
||||
return c.randomShard()
|
||||
}
|
||||
|
||||
c.mu.RLock()
|
||||
shard := c.shards[name]
|
||||
c.mu.RUnlock()
|
||||
return shard, nil
|
||||
}
|
||||
|
||||
func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
|
||||
cmdInfo := c.cmdInfo(cmd.Name())
|
||||
pos := cmdFirstKeyPos(cmd, cmdInfo)
|
||||
if pos == 0 {
|
||||
return c.randomShard()
|
||||
return c.shards.Random()
|
||||
}
|
||||
firstKey := cmd.stringArg(pos)
|
||||
return c.shardByKey(firstKey)
|
||||
return c.shards.GetByKey(firstKey)
|
||||
}
|
||||
|
||||
func (c *Ring) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error) {
|
||||
@@ -378,77 +479,6 @@ func (c *Ring) Process(cmd Cmder) error {
|
||||
return shard.Client.Process(cmd)
|
||||
}
|
||||
|
||||
// rebalance removes dead shards from the Ring.
|
||||
func (c *Ring) rebalance() {
|
||||
hash := consistenthash.New(c.nreplicas, nil)
|
||||
for name, shard := range c.shards {
|
||||
if shard.IsUp() {
|
||||
hash.Add(name)
|
||||
}
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
c.hash = hash
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// heartbeat monitors state of each shard in the ring.
|
||||
func (c *Ring) heartbeat() {
|
||||
ticker := time.NewTicker(c.opt.HeartbeatFrequency)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
var rebalance bool
|
||||
|
||||
c.mu.RLock()
|
||||
|
||||
if c.closed {
|
||||
c.mu.RUnlock()
|
||||
break
|
||||
}
|
||||
|
||||
shards := c.shardsList
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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.shardsList = nil
|
||||
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func (c *Ring) Pipeline() Pipeliner {
|
||||
pipe := Pipeline{
|
||||
exec: c.processPipeline,
|
||||
@@ -471,11 +501,11 @@ func (c *Ring) defaultProcessPipeline(cmds []Cmder) error {
|
||||
cmdsMap := make(map[string][]Cmder)
|
||||
for _, cmd := range cmds {
|
||||
cmdInfo := c.cmdInfo(cmd.Name())
|
||||
name := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo))
|
||||
if name != "" {
|
||||
name = c.hash.Get(hashtag.Key(name))
|
||||
hash := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo))
|
||||
if hash != "" {
|
||||
hash = c.shards.Hash(hashtag.Key(hash))
|
||||
}
|
||||
cmdsMap[name] = append(cmdsMap[name], cmd)
|
||||
cmdsMap[hash] = append(cmdsMap[hash], cmd)
|
||||
}
|
||||
|
||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||
@@ -485,8 +515,8 @@ func (c *Ring) defaultProcessPipeline(cmds []Cmder) error {
|
||||
|
||||
var failedCmdsMap map[string][]Cmder
|
||||
|
||||
for name, cmds := range cmdsMap {
|
||||
shard, err := c.shardByName(name)
|
||||
for hash, cmds := range cmdsMap {
|
||||
shard, err := c.shards.GetByHash(hash)
|
||||
if err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
continue
|
||||
@@ -509,7 +539,7 @@ func (c *Ring) defaultProcessPipeline(cmds []Cmder) error {
|
||||
if failedCmdsMap == nil {
|
||||
failedCmdsMap = make(map[string][]Cmder)
|
||||
}
|
||||
failedCmdsMap[name] = cmds
|
||||
failedCmdsMap[hash] = cmds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,3 +559,11 @@ func (c *Ring) TxPipeline() Pipeliner {
|
||||
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()
|
||||
}
|
||||
|
||||
193
vendor/github.com/go-redis/redis/ring_test.go
сгенерированный
поставляемый
193
vendor/github.com/go-redis/redis/ring_test.go
сгенерированный
поставляемый
@@ -1,193 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Redis Ring", func() {
|
||||
const heartbeat = 100 * time.Millisecond
|
||||
|
||||
var ring *redis.Ring
|
||||
|
||||
setRingKeys := func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
err := ring.Set(fmt.Sprintf("key%d", i), "value", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
BeforeEach(func() {
|
||||
opt := redisRingOptions()
|
||||
opt.HeartbeatFrequency = heartbeat
|
||||
ring = redis.NewRing(opt)
|
||||
|
||||
err := ring.ForEachShard(func(cl *redis.Client) error {
|
||||
return cl.FlushDB().Err()
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(ring.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("distributes keys", func() {
|
||||
setRingKeys()
|
||||
|
||||
// Both shards should have some keys now.
|
||||
Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57"))
|
||||
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43"))
|
||||
})
|
||||
|
||||
It("distributes keys when using EVAL", func() {
|
||||
script := redis.NewScript(`
|
||||
local r = redis.call('SET', KEYS[1], ARGV[1])
|
||||
return r
|
||||
`)
|
||||
|
||||
var key string
|
||||
for i := 0; i < 100; i++ {
|
||||
key = fmt.Sprintf("key%d", i)
|
||||
err := script.Run(ring, []string{key}, "value").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57"))
|
||||
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43"))
|
||||
})
|
||||
|
||||
It("uses single shard when one of the shards is down", func() {
|
||||
// Stop ringShard2.
|
||||
Expect(ringShard2.Close()).NotTo(HaveOccurred())
|
||||
|
||||
// Ring needs 3 * heartbeat time to detect that node is down.
|
||||
// Give it more to be sure.
|
||||
time.Sleep(2 * 3 * heartbeat)
|
||||
|
||||
setRingKeys()
|
||||
|
||||
// RingShard1 should have all keys.
|
||||
Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=100"))
|
||||
|
||||
// Start ringShard2.
|
||||
var err error
|
||||
ringShard2, err = startRedis(ringShard2Port)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Wait for ringShard2 to come up.
|
||||
Eventually(func() error {
|
||||
return ringShard2.Ping().Err()
|
||||
}, "1s").ShouldNot(HaveOccurred())
|
||||
|
||||
// Ring needs heartbeat time to detect that node is up.
|
||||
// Give it more to be sure.
|
||||
time.Sleep(heartbeat + heartbeat)
|
||||
|
||||
setRingKeys()
|
||||
|
||||
// RingShard2 should have its keys.
|
||||
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43"))
|
||||
})
|
||||
|
||||
It("supports hash tags", func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
err := ring.Set(fmt.Sprintf("key%d{tag}", i), "value", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
Expect(ringShard1.Info().Val()).ToNot(ContainSubstring("keys="))
|
||||
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=100"))
|
||||
})
|
||||
|
||||
Describe("pipeline", func() {
|
||||
It("distributes keys", func() {
|
||||
pipe := ring.Pipeline()
|
||||
for i := 0; i < 100; i++ {
|
||||
err := pipe.Set(fmt.Sprintf("key%d", i), "value", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
cmds, err := pipe.Exec()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(100))
|
||||
Expect(pipe.Close()).NotTo(HaveOccurred())
|
||||
|
||||
for _, cmd := range cmds {
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
Expect(cmd.(*redis.StatusCmd).Val()).To(Equal("OK"))
|
||||
}
|
||||
|
||||
// Both shards should have some keys now.
|
||||
Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=57"))
|
||||
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=43"))
|
||||
})
|
||||
|
||||
It("is consistent with ring", func() {
|
||||
var keys []string
|
||||
for i := 0; i < 100; i++ {
|
||||
key := make([]byte, 64)
|
||||
_, err := rand.Read(key)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
keys = append(keys, string(key))
|
||||
}
|
||||
|
||||
_, err := ring.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
for _, key := range keys {
|
||||
pipe.Set(key, "value", 0).Err()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
for _, key := range keys {
|
||||
val, err := ring.Get(key).Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("value"))
|
||||
}
|
||||
})
|
||||
|
||||
It("supports hash tags", func() {
|
||||
_, err := ring.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
for i := 0; i < 100; i++ {
|
||||
pipe.Set(fmt.Sprintf("key%d{tag}", i), "value", 0).Err()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(ringShard1.Info().Val()).ToNot(ContainSubstring("keys="))
|
||||
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=100"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("empty Redis Ring", func() {
|
||||
var ring *redis.Ring
|
||||
|
||||
BeforeEach(func() {
|
||||
ring = redis.NewRing(&redis.RingOptions{})
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(ring.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("returns an error", func() {
|
||||
err := ring.Ping().Err()
|
||||
Expect(err).To(MatchError("redis: all ring shards are down"))
|
||||
})
|
||||
|
||||
It("pipeline returns an error", func() {
|
||||
_, err := ring.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
Expect(err).To(MatchError("redis: all ring shards are down"))
|
||||
})
|
||||
})
|
||||
4
vendor/github.com/go-redis/redis/script.go
сгенерированный
поставляемый
4
vendor/github.com/go-redis/redis/script.go
сгенерированный
поставляемый
@@ -10,7 +10,7 @@ import (
|
||||
type scripter interface {
|
||||
Eval(script string, keys []string, args ...interface{}) *Cmd
|
||||
EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
|
||||
ScriptExists(scripts ...string) *BoolSliceCmd
|
||||
ScriptExists(hashes ...string) *BoolSliceCmd
|
||||
ScriptLoad(script string) *StringCmd
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (s *Script) Load(c scripter) *StringCmd {
|
||||
}
|
||||
|
||||
func (s *Script) Exists(c scripter) *BoolSliceCmd {
|
||||
return c.ScriptExists(s.src)
|
||||
return c.ScriptExists(s.hash)
|
||||
}
|
||||
|
||||
func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd {
|
||||
|
||||
88
vendor/github.com/go-redis/redis/sentinel_test.go
сгенерированный
поставляемый
88
vendor/github.com/go-redis/redis/sentinel_test.go
сгенерированный
поставляемый
@@ -1,88 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Sentinel", func() {
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
MasterName: sentinelName,
|
||||
SentinelAddrs: []string{":" + sentinelPort},
|
||||
})
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should facilitate failover", func() {
|
||||
// Set value on master.
|
||||
err := client.Set("foo", "master", 0).Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Verify.
|
||||
val, err := sentinelMaster.Get("foo").Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(val).To(Equal("master"))
|
||||
|
||||
// Create subscription.
|
||||
ch := client.Subscribe("foo").Channel()
|
||||
|
||||
// Wait until replicated.
|
||||
Eventually(func() string {
|
||||
return sentinelSlave1.Get("foo").Val()
|
||||
}, "1s", "100ms").Should(Equal("master"))
|
||||
Eventually(func() string {
|
||||
return sentinelSlave2.Get("foo").Val()
|
||||
}, "1s", "100ms").Should(Equal("master"))
|
||||
|
||||
// Wait until slaves are picked up by sentinel.
|
||||
Eventually(func() string {
|
||||
return sentinel.Info().Val()
|
||||
}, "10s", "100ms").Should(ContainSubstring("slaves=2"))
|
||||
|
||||
// Kill master.
|
||||
sentinelMaster.Shutdown()
|
||||
Eventually(func() error {
|
||||
return sentinelMaster.Ping().Err()
|
||||
}, "5s", "100ms").Should(HaveOccurred())
|
||||
|
||||
// Wait for Redis sentinel to elect new master.
|
||||
Eventually(func() string {
|
||||
return sentinelSlave1.Info().Val() + sentinelSlave2.Info().Val()
|
||||
}, "30s", "1s").Should(ContainSubstring("role:master"))
|
||||
|
||||
// Check that client picked up new master.
|
||||
Eventually(func() error {
|
||||
return client.Get("foo").Err()
|
||||
}, "5s", "100ms").ShouldNot(HaveOccurred())
|
||||
|
||||
// Publish message to check if subscription is renewed.
|
||||
err = client.Publish("foo", "hello").Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var msg *redis.Message
|
||||
Eventually(ch).Should(Receive(&msg))
|
||||
Expect(msg.Channel).To(Equal("foo"))
|
||||
Expect(msg.Payload).To(Equal("hello"))
|
||||
})
|
||||
|
||||
It("supports DB selection", func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
|
||||
client = redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
MasterName: sentinelName,
|
||||
SentinelAddrs: []string{":" + sentinelPort},
|
||||
DB: 1,
|
||||
})
|
||||
err := client.Ping().Err()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
10
vendor/github.com/go-redis/redis/testdata/redis.conf
сгенерированный
поставляемый
10
vendor/github.com/go-redis/redis/testdata/redis.conf
сгенерированный
поставляемый
@@ -1,10 +0,0 @@
|
||||
# Minimal redis.conf
|
||||
|
||||
port 6379
|
||||
daemonize no
|
||||
dir .
|
||||
save ""
|
||||
appendonly yes
|
||||
cluster-config-file nodes.conf
|
||||
cluster-node-timeout 30000
|
||||
maxclients 1001
|
||||
4
vendor/github.com/go-redis/redis/tx.go
сгенерированный
поставляемый
4
vendor/github.com/go-redis/redis/tx.go
сгенерированный
поставляемый
@@ -1,12 +1,12 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"github.com/go-redis/redis/internal"
|
||||
"github.com/go-redis/redis/internal/pool"
|
||||
"github.com/go-redis/redis/internal/proto"
|
||||
)
|
||||
|
||||
// TxFailedErr transaction redis failed.
|
||||
const TxFailedErr = internal.RedisError("redis: transaction 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
|
||||
|
||||
151
vendor/github.com/go-redis/redis/tx_test.go
сгенерированный
поставляемый
151
vendor/github.com/go-redis/redis/tx_test.go
сгенерированный
поставляемый
@@ -1,151 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Tx", func() {
|
||||
var client *redis.Client
|
||||
|
||||
BeforeEach(func() {
|
||||
client = redis.NewClient(redisOptions())
|
||||
Expect(client.FlushDB().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(client.Close()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should Watch", func() {
|
||||
var incr func(string) error
|
||||
|
||||
// Transactionally increments key using GET and SET commands.
|
||||
incr = func(key string) error {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
n, err := tx.Get(key).Int64()
|
||||
if err != nil && err != redis.Nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Set(key, strconv.FormatInt(n+1, 10), 0)
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}, key)
|
||||
if err == redis.TxFailedErr {
|
||||
return incr(key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
defer wg.Done()
|
||||
|
||||
err := incr("key")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
n, err := client.Get("key").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(n).To(Equal(int64(100)))
|
||||
})
|
||||
|
||||
It("should discard", func() {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Set("key1", "hello1", 0)
|
||||
pipe.Discard()
|
||||
pipe.Set("key2", "hello2", 0)
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cmds).To(HaveLen(1))
|
||||
return err
|
||||
}, "key1", "key2")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
get := client.Get("key1")
|
||||
Expect(get.Err()).To(Equal(redis.Nil))
|
||||
Expect(get.Val()).To(Equal(""))
|
||||
|
||||
get = client.Get("key2")
|
||||
Expect(get.Err()).NotTo(HaveOccurred())
|
||||
Expect(get.Val()).To(Equal("hello2"))
|
||||
})
|
||||
|
||||
It("returns no error when there are no commands", func() {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
_, err := tx.Pipelined(func(redis.Pipeliner) error { return nil })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
v, err := client.Ping().Result()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(v).To(Equal("PONG"))
|
||||
})
|
||||
|
||||
It("should exec bulks", func() {
|
||||
const N = 20000
|
||||
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
cmds, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
for i := 0; i < N; i++ {
|
||||
pipe.Incr("key")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(cmds)).To(Equal(N))
|
||||
for _, cmd := range cmds {
|
||||
Expect(cmd.Err()).NotTo(HaveOccurred())
|
||||
}
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
num, err := client.Get("key").Int64()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(num).To(Equal(int64(N)))
|
||||
})
|
||||
|
||||
It("should recover from bad connection", func() {
|
||||
// Put bad connection in the pool.
|
||||
cn, _, err := client.Pool().Get()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cn.SetNetConn(&badConn{})
|
||||
err = client.Pool().Put(cn)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
do := func() error {
|
||||
err := client.Watch(func(tx *redis.Tx) error {
|
||||
_, err := tx.Pipelined(func(pipe redis.Pipeliner) error {
|
||||
pipe.Ping()
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
err = do()
|
||||
Expect(err).To(MatchError("bad connection"))
|
||||
|
||||
err = do()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
1
vendor/github.com/go-redis/redis/universal.go
сгенерированный
поставляемый
1
vendor/github.com/go-redis/redis/universal.go
сгенерированный
поставляемый
@@ -114,6 +114,7 @@ func (o *UniversalOptions) simple() *Options {
|
||||
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
|
||||
|
||||
41
vendor/github.com/go-redis/redis/universal_test.go
сгенерированный
поставляемый
41
vendor/github.com/go-redis/redis/universal_test.go
сгенерированный
поставляемый
@@ -1,41 +0,0 @@
|
||||
package redis_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
)
|
||||
|
||||
var _ = Describe("UniversalClient", func() {
|
||||
var client redis.UniversalClient
|
||||
|
||||
AfterEach(func() {
|
||||
if client != nil {
|
||||
Expect(client.Close()).To(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
It("should connect to failover servers", func() {
|
||||
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
MasterName: sentinelName,
|
||||
Addrs: []string{":" + sentinelPort},
|
||||
})
|
||||
Expect(client.Ping().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should connect to simple servers", func() {
|
||||
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: []string{redisAddr},
|
||||
})
|
||||
Expect(client.Ping().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should connect to clusters", func() {
|
||||
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: cluster.addrs(),
|
||||
})
|
||||
Expect(client.Ping().Err()).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
})
|
||||
Ссылка в новой задаче
Block a user