diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2023-12-02 01:45:21 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2023-12-02 02:42:50 +0300 |
commit | 9c43d58f75cf086b744cf4fe2ae180e8f37e4a0c (patch) | |
tree | 9f88a486917d371d099cd712efd91b4c122d209d /vendor/github.com/go-redis/redis/v8/internal | |
parent | 32fb6dda1feb24f9ab69ece5df0cb9ec238ca5e6 (diff) | |
download | ydb-9c43d58f75cf086b744cf4fe2ae180e8f37e4a0c.tar.gz |
Intermediate changes
Diffstat (limited to 'vendor/github.com/go-redis/redis/v8/internal')
26 files changed, 2416 insertions, 0 deletions
diff --git a/vendor/github.com/go-redis/redis/v8/internal/arg.go b/vendor/github.com/go-redis/redis/v8/internal/arg.go new file mode 100644 index 0000000000..b97fa0d685 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/arg.go @@ -0,0 +1,56 @@ +package internal + +import ( + "fmt" + "strconv" + "time" +) + +func AppendArg(b []byte, v interface{}) []byte { + switch v := v.(type) { + case nil: + return append(b, "<nil>"...) + case string: + return appendUTF8String(b, Bytes(v)) + case []byte: + return appendUTF8String(b, v) + case int: + return strconv.AppendInt(b, int64(v), 10) + case int8: + return strconv.AppendInt(b, int64(v), 10) + case int16: + return strconv.AppendInt(b, int64(v), 10) + case int32: + return strconv.AppendInt(b, int64(v), 10) + case int64: + return strconv.AppendInt(b, v, 10) + case uint: + return strconv.AppendUint(b, uint64(v), 10) + case uint8: + return strconv.AppendUint(b, uint64(v), 10) + case uint16: + return strconv.AppendUint(b, uint64(v), 10) + case uint32: + return strconv.AppendUint(b, uint64(v), 10) + case uint64: + return strconv.AppendUint(b, v, 10) + case float32: + return strconv.AppendFloat(b, float64(v), 'f', -1, 64) + case float64: + return strconv.AppendFloat(b, v, 'f', -1, 64) + case bool: + if v { + return append(b, "true"...) + } + return append(b, "false"...) + case time.Time: + return v.AppendFormat(b, time.RFC3339Nano) + default: + return append(b, fmt.Sprint(v)...) + } +} + +func appendUTF8String(dst []byte, src []byte) []byte { + dst = append(dst, src...) + return dst +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/hashtag/hashtag.go b/vendor/github.com/go-redis/redis/v8/internal/hashtag/hashtag.go new file mode 100644 index 0000000000..b3a4f211e3 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/hashtag/hashtag.go @@ -0,0 +1,78 @@ +package hashtag + +import ( + "strings" + + "github.com/go-redis/redis/v8/internal/rand" +) + +const slotNumber = 16384 + +// CRC16 implementation according to CCITT standards. +// Copyright 2001-2010 Georges Menie (www.menie.org) +// Copyright 2013 The Go Authors. All rights reserved. +// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c +var crc16tab = [256]uint16{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, +} + +func Key(key string) string { + if s := strings.IndexByte(key, '{'); s > -1 { + if e := strings.IndexByte(key[s+1:], '}'); e > 0 { + return key[s+1 : s+e+1] + } + } + return key +} + +func RandomSlot() int { + return rand.Intn(slotNumber) +} + +// Slot returns a consistent slot number between 0 and 16383 +// for any given string key. +func Slot(key string) int { + if key == "" { + return RandomSlot() + } + key = Key(key) + return int(crc16sum(key)) % slotNumber +} + +func crc16sum(key string) (crc uint16) { + for i := 0; i < len(key); i++ { + crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff] + } + return +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/hashtag/ya.make b/vendor/github.com/go-redis/redis/v8/internal/hashtag/ya.make new file mode 100644 index 0000000000..ada1b318a8 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/hashtag/ya.make @@ -0,0 +1,11 @@ +GO_LIBRARY() + +LICENSE(BSD-2-Clause) + +SRCS(hashtag.go) + +GO_TEST_SRCS(hashtag_test.go) + +END() + +RECURSE(gotest) diff --git a/vendor/github.com/go-redis/redis/v8/internal/hscan/hscan.go b/vendor/github.com/go-redis/redis/v8/internal/hscan/hscan.go new file mode 100644 index 0000000000..852c8bd525 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/hscan/hscan.go @@ -0,0 +1,201 @@ +package hscan + +import ( + "errors" + "fmt" + "reflect" + "strconv" +) + +// decoderFunc represents decoding functions for default built-in types. +type decoderFunc func(reflect.Value, string) error + +var ( + // List of built-in decoders indexed by their numeric constant values (eg: reflect.Bool = 1). + decoders = []decoderFunc{ + reflect.Bool: decodeBool, + reflect.Int: decodeInt, + reflect.Int8: decodeInt8, + reflect.Int16: decodeInt16, + reflect.Int32: decodeInt32, + reflect.Int64: decodeInt64, + reflect.Uint: decodeUint, + reflect.Uint8: decodeUint8, + reflect.Uint16: decodeUint16, + reflect.Uint32: decodeUint32, + reflect.Uint64: decodeUint64, + reflect.Float32: decodeFloat32, + reflect.Float64: decodeFloat64, + reflect.Complex64: decodeUnsupported, + reflect.Complex128: decodeUnsupported, + reflect.Array: decodeUnsupported, + reflect.Chan: decodeUnsupported, + reflect.Func: decodeUnsupported, + reflect.Interface: decodeUnsupported, + reflect.Map: decodeUnsupported, + reflect.Ptr: decodeUnsupported, + reflect.Slice: decodeSlice, + reflect.String: decodeString, + reflect.Struct: decodeUnsupported, + reflect.UnsafePointer: decodeUnsupported, + } + + // Global map of struct field specs that is populated once for every new + // struct type that is scanned. This caches the field types and the corresponding + // decoder functions to avoid iterating through struct fields on subsequent scans. + globalStructMap = newStructMap() +) + +func Struct(dst interface{}) (StructValue, error) { + v := reflect.ValueOf(dst) + + // The destination to scan into should be a struct pointer. + if v.Kind() != reflect.Ptr || v.IsNil() { + return StructValue{}, fmt.Errorf("redis.Scan(non-pointer %T)", dst) + } + + v = v.Elem() + if v.Kind() != reflect.Struct { + return StructValue{}, fmt.Errorf("redis.Scan(non-struct %T)", dst) + } + + return StructValue{ + spec: globalStructMap.get(v.Type()), + value: v, + }, nil +} + +// Scan scans the results from a key-value Redis map result set to a destination struct. +// The Redis keys are matched to the struct's field with the `redis` tag. +func Scan(dst interface{}, keys []interface{}, vals []interface{}) error { + if len(keys) != len(vals) { + return errors.New("args should have the same number of keys and vals") + } + + strct, err := Struct(dst) + if err != nil { + return err + } + + // Iterate through the (key, value) sequence. + for i := 0; i < len(vals); i++ { + key, ok := keys[i].(string) + if !ok { + continue + } + + val, ok := vals[i].(string) + if !ok { + continue + } + + if err := strct.Scan(key, val); err != nil { + return err + } + } + + return nil +} + +func decodeBool(f reflect.Value, s string) error { + b, err := strconv.ParseBool(s) + if err != nil { + return err + } + f.SetBool(b) + return nil +} + +func decodeInt8(f reflect.Value, s string) error { + return decodeNumber(f, s, 8) +} + +func decodeInt16(f reflect.Value, s string) error { + return decodeNumber(f, s, 16) +} + +func decodeInt32(f reflect.Value, s string) error { + return decodeNumber(f, s, 32) +} + +func decodeInt64(f reflect.Value, s string) error { + return decodeNumber(f, s, 64) +} + +func decodeInt(f reflect.Value, s string) error { + return decodeNumber(f, s, 0) +} + +func decodeNumber(f reflect.Value, s string, bitSize int) error { + v, err := strconv.ParseInt(s, 10, bitSize) + if err != nil { + return err + } + f.SetInt(v) + return nil +} + +func decodeUint8(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 8) +} + +func decodeUint16(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 16) +} + +func decodeUint32(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 32) +} + +func decodeUint64(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 64) +} + +func decodeUint(f reflect.Value, s string) error { + return decodeUnsignedNumber(f, s, 0) +} + +func decodeUnsignedNumber(f reflect.Value, s string, bitSize int) error { + v, err := strconv.ParseUint(s, 10, bitSize) + if err != nil { + return err + } + f.SetUint(v) + return nil +} + +func decodeFloat32(f reflect.Value, s string) error { + v, err := strconv.ParseFloat(s, 32) + if err != nil { + return err + } + f.SetFloat(v) + return nil +} + +// although the default is float64, but we better define it. +func decodeFloat64(f reflect.Value, s string) error { + v, err := strconv.ParseFloat(s, 64) + if err != nil { + return err + } + f.SetFloat(v) + return nil +} + +func decodeString(f reflect.Value, s string) error { + f.SetString(s) + return nil +} + +func decodeSlice(f reflect.Value, s string) error { + // []byte slice ([]uint8). + if f.Type().Elem().Kind() == reflect.Uint8 { + f.SetBytes([]byte(s)) + } + return nil +} + +func decodeUnsupported(v reflect.Value, s string) error { + return fmt.Errorf("redis.Scan(unsupported %s)", v.Type()) +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/hscan/structmap.go b/vendor/github.com/go-redis/redis/v8/internal/hscan/structmap.go new file mode 100644 index 0000000000..6839412ba2 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/hscan/structmap.go @@ -0,0 +1,93 @@ +package hscan + +import ( + "fmt" + "reflect" + "strings" + "sync" +) + +// structMap contains the map of struct fields for target structs +// indexed by the struct type. +type structMap struct { + m sync.Map +} + +func newStructMap() *structMap { + return new(structMap) +} + +func (s *structMap) get(t reflect.Type) *structSpec { + if v, ok := s.m.Load(t); ok { + return v.(*structSpec) + } + + spec := newStructSpec(t, "redis") + s.m.Store(t, spec) + return spec +} + +//------------------------------------------------------------------------------ + +// structSpec contains the list of all fields in a target struct. +type structSpec struct { + m map[string]*structField +} + +func (s *structSpec) set(tag string, sf *structField) { + s.m[tag] = sf +} + +func newStructSpec(t reflect.Type, fieldTag string) *structSpec { + numField := t.NumField() + out := &structSpec{ + m: make(map[string]*structField, numField), + } + + for i := 0; i < numField; i++ { + f := t.Field(i) + + tag := f.Tag.Get(fieldTag) + if tag == "" || tag == "-" { + continue + } + + tag = strings.Split(tag, ",")[0] + if tag == "" { + continue + } + + // Use the built-in decoder. + out.set(tag, &structField{index: i, fn: decoders[f.Type.Kind()]}) + } + + return out +} + +//------------------------------------------------------------------------------ + +// structField represents a single field in a target struct. +type structField struct { + index int + fn decoderFunc +} + +//------------------------------------------------------------------------------ + +type StructValue struct { + spec *structSpec + value reflect.Value +} + +func (s StructValue) Scan(key string, value string) error { + field, ok := s.spec.m[key] + if !ok { + return nil + } + if err := field.fn(s.value.Field(field.index), value); err != nil { + t := s.value.Type() + return fmt.Errorf("cannot scan redis.result %s into struct field %s.%s of type %s, error-%s", + value, t.Name(), t.Field(field.index).Name, t.Field(field.index).Type, err.Error()) + } + return nil +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/hscan/ya.make b/vendor/github.com/go-redis/redis/v8/internal/hscan/ya.make new file mode 100644 index 0000000000..b193b03b81 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/hscan/ya.make @@ -0,0 +1,14 @@ +GO_LIBRARY() + +LICENSE(BSD-2-Clause) + +SRCS( + hscan.go + structmap.go +) + +GO_TEST_SRCS(hscan_test.go) + +END() + +RECURSE(gotest) diff --git a/vendor/github.com/go-redis/redis/v8/internal/internal.go b/vendor/github.com/go-redis/redis/v8/internal/internal.go new file mode 100644 index 0000000000..4a59c599be --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/internal.go @@ -0,0 +1,29 @@ +package internal + +import ( + "time" + + "github.com/go-redis/redis/v8/internal/rand" +) + +func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration { + if retry < 0 { + panic("not reached") + } + if minBackoff == 0 { + return 0 + } + + d := minBackoff << uint(retry) + if d < minBackoff { + return maxBackoff + } + + d = minBackoff + time.Duration(rand.Int63n(int64(d))) + + if d > maxBackoff || d < minBackoff { + d = maxBackoff + } + + return d +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/log.go b/vendor/github.com/go-redis/redis/v8/internal/log.go new file mode 100644 index 0000000000..c8b9213de4 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/log.go @@ -0,0 +1,26 @@ +package internal + +import ( + "context" + "fmt" + "log" + "os" +) + +type Logging interface { + Printf(ctx context.Context, format string, v ...interface{}) +} + +type logger struct { + log *log.Logger +} + +func (l *logger) Printf(ctx context.Context, format string, v ...interface{}) { + _ = l.log.Output(2, fmt.Sprintf(format, v...)) +} + +// Logger calls Output to print to the stderr. +// Arguments are handled in the manner of fmt.Print. +var Logger Logging = &logger{ + log: log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile), +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/once.go b/vendor/github.com/go-redis/redis/v8/internal/once.go new file mode 100644 index 0000000000..64f46272ae --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/once.go @@ -0,0 +1,60 @@ +/* +Copyright 2014 The Camlistore Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package internal + +import ( + "sync" + "sync/atomic" +) + +// A Once will perform a successful action exactly once. +// +// Unlike a sync.Once, this Once's func returns an error +// and is re-armed on failure. +type Once struct { + m sync.Mutex + done uint32 +} + +// Do calls the function f if and only if Do has not been invoked +// without error for this instance of Once. In other words, given +// var once Once +// if once.Do(f) is called multiple times, only the first call will +// invoke f, even if f has a different value in each invocation unless +// f returns an error. A new instance of Once is required for each +// function to execute. +// +// Do is intended for initialization that must be run exactly once. Since f +// is niladic, it may be necessary to use a function literal to capture the +// arguments to a function to be invoked by Do: +// err := config.once.Do(func() error { return config.init(filename) }) +func (o *Once) Do(f func() error) error { + if atomic.LoadUint32(&o.done) == 1 { + return nil + } + // Slow-path. + o.m.Lock() + defer o.m.Unlock() + var err error + if o.done == 0 { + err = f() + if err == nil { + atomic.StoreUint32(&o.done, 1) + } + } + return err +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/pool/conn.go b/vendor/github.com/go-redis/redis/v8/internal/pool/conn.go new file mode 100644 index 0000000000..5661659865 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/pool/conn.go @@ -0,0 +1,121 @@ +package pool + +import ( + "bufio" + "context" + "net" + "sync/atomic" + "time" + + "github.com/go-redis/redis/v8/internal/proto" +) + +var noDeadline = time.Time{} + +type Conn struct { + usedAt int64 // atomic + netConn net.Conn + + rd *proto.Reader + bw *bufio.Writer + wr *proto.Writer + + Inited bool + pooled bool + createdAt time.Time +} + +func NewConn(netConn net.Conn) *Conn { + cn := &Conn{ + netConn: netConn, + createdAt: time.Now(), + } + cn.rd = proto.NewReader(netConn) + cn.bw = bufio.NewWriter(netConn) + cn.wr = proto.NewWriter(cn.bw) + cn.SetUsedAt(time.Now()) + return cn +} + +func (cn *Conn) UsedAt() time.Time { + unix := atomic.LoadInt64(&cn.usedAt) + return time.Unix(unix, 0) +} + +func (cn *Conn) SetUsedAt(tm time.Time) { + atomic.StoreInt64(&cn.usedAt, tm.Unix()) +} + +func (cn *Conn) SetNetConn(netConn net.Conn) { + cn.netConn = netConn + cn.rd.Reset(netConn) + cn.bw.Reset(netConn) +} + +func (cn *Conn) Write(b []byte) (int, error) { + return cn.netConn.Write(b) +} + +func (cn *Conn) RemoteAddr() net.Addr { + if cn.netConn != nil { + return cn.netConn.RemoteAddr() + } + return nil +} + +func (cn *Conn) WithReader(ctx context.Context, timeout time.Duration, fn func(rd *proto.Reader) error) error { + if err := cn.netConn.SetReadDeadline(cn.deadline(ctx, timeout)); err != nil { + return err + } + return fn(cn.rd) +} + +func (cn *Conn) WithWriter( + ctx context.Context, timeout time.Duration, fn func(wr *proto.Writer) error, +) error { + if err := cn.netConn.SetWriteDeadline(cn.deadline(ctx, timeout)); err != nil { + return err + } + + if cn.bw.Buffered() > 0 { + cn.bw.Reset(cn.netConn) + } + + if err := fn(cn.wr); err != nil { + return err + } + + return cn.bw.Flush() +} + +func (cn *Conn) Close() error { + return cn.netConn.Close() +} + +func (cn *Conn) deadline(ctx context.Context, timeout time.Duration) time.Time { + tm := time.Now() + cn.SetUsedAt(tm) + + if timeout > 0 { + tm = tm.Add(timeout) + } + + if ctx != nil { + deadline, ok := ctx.Deadline() + if ok { + if timeout == 0 { + return deadline + } + if deadline.Before(tm) { + return deadline + } + return tm + } + } + + if timeout > 0 { + return tm + } + + return noDeadline +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/pool/pool.go b/vendor/github.com/go-redis/redis/v8/internal/pool/pool.go new file mode 100644 index 0000000000..44a4e779df --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/pool/pool.go @@ -0,0 +1,557 @@ +package pool + +import ( + "context" + "errors" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/go-redis/redis/v8/internal" +) + +var ( + // ErrClosed performs any operation on the closed client will return this error. + ErrClosed = errors.New("redis: client is closed") + + // ErrPoolTimeout timed out waiting to get a connection from the connection pool. + ErrPoolTimeout = errors.New("redis: connection pool timeout") +) + +var timers = sync.Pool{ + New: func() interface{} { + t := time.NewTimer(time.Hour) + t.Stop() + return t + }, +} + +// Stats contains pool state information and accumulated stats. +type Stats struct { + Hits uint32 // number of times free connection was found in the pool + Misses uint32 // number of times free connection was NOT found in the pool + Timeouts uint32 // number of times a wait timeout occurred + + TotalConns uint32 // number of total connections in the pool + IdleConns uint32 // number of idle connections in the pool + StaleConns uint32 // number of stale connections removed from the pool +} + +type Pooler interface { + NewConn(context.Context) (*Conn, error) + CloseConn(*Conn) error + + Get(context.Context) (*Conn, error) + Put(context.Context, *Conn) + Remove(context.Context, *Conn, error) + + Len() int + IdleLen() int + Stats() *Stats + + Close() error +} + +type Options struct { + Dialer func(context.Context) (net.Conn, error) + OnClose func(*Conn) error + + PoolFIFO bool + PoolSize int + MinIdleConns int + MaxConnAge time.Duration + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +type lastDialErrorWrap struct { + err error +} + +type ConnPool struct { + opt *Options + + dialErrorsNum uint32 // atomic + + lastDialError atomic.Value + + queue chan struct{} + + connsMu sync.Mutex + conns []*Conn + idleConns []*Conn + poolSize int + idleConnsLen int + + stats Stats + + _closed uint32 // atomic + closedCh chan struct{} +} + +var _ Pooler = (*ConnPool)(nil) + +func NewConnPool(opt *Options) *ConnPool { + p := &ConnPool{ + opt: opt, + + queue: make(chan struct{}, opt.PoolSize), + conns: make([]*Conn, 0, opt.PoolSize), + idleConns: make([]*Conn, 0, opt.PoolSize), + closedCh: make(chan struct{}), + } + + p.connsMu.Lock() + p.checkMinIdleConns() + p.connsMu.Unlock() + + if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 { + go p.reaper(opt.IdleCheckFrequency) + } + + return p +} + +func (p *ConnPool) checkMinIdleConns() { + if p.opt.MinIdleConns == 0 { + return + } + for p.poolSize < p.opt.PoolSize && p.idleConnsLen < p.opt.MinIdleConns { + p.poolSize++ + p.idleConnsLen++ + + go func() { + err := p.addIdleConn() + if err != nil && err != ErrClosed { + p.connsMu.Lock() + p.poolSize-- + p.idleConnsLen-- + p.connsMu.Unlock() + } + }() + } +} + +func (p *ConnPool) addIdleConn() error { + cn, err := p.dialConn(context.TODO(), true) + if err != nil { + return err + } + + p.connsMu.Lock() + defer p.connsMu.Unlock() + + // It is not allowed to add new connections to the closed connection pool. + if p.closed() { + _ = cn.Close() + return ErrClosed + } + + p.conns = append(p.conns, cn) + p.idleConns = append(p.idleConns, cn) + return nil +} + +func (p *ConnPool) NewConn(ctx context.Context) (*Conn, error) { + return p.newConn(ctx, false) +} + +func (p *ConnPool) newConn(ctx context.Context, pooled bool) (*Conn, error) { + cn, err := p.dialConn(ctx, pooled) + if err != nil { + return nil, err + } + + p.connsMu.Lock() + defer p.connsMu.Unlock() + + // It is not allowed to add new connections to the closed connection pool. + if p.closed() { + _ = cn.Close() + return nil, ErrClosed + } + + p.conns = append(p.conns, cn) + if pooled { + // If pool is full remove the cn on next Put. + if p.poolSize >= p.opt.PoolSize { + cn.pooled = false + } else { + p.poolSize++ + } + } + + return cn, nil +} + +func (p *ConnPool) dialConn(ctx context.Context, pooled bool) (*Conn, error) { + if p.closed() { + return nil, ErrClosed + } + + if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) { + return nil, p.getLastDialError() + } + + netConn, err := p.opt.Dialer(ctx) + if err != nil { + p.setLastDialError(err) + if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) { + go p.tryDial() + } + return nil, err + } + + cn := NewConn(netConn) + cn.pooled = pooled + return cn, nil +} + +func (p *ConnPool) tryDial() { + for { + if p.closed() { + return + } + + conn, err := p.opt.Dialer(context.Background()) + if err != nil { + p.setLastDialError(err) + time.Sleep(time.Second) + continue + } + + atomic.StoreUint32(&p.dialErrorsNum, 0) + _ = conn.Close() + return + } +} + +func (p *ConnPool) setLastDialError(err error) { + p.lastDialError.Store(&lastDialErrorWrap{err: err}) +} + +func (p *ConnPool) getLastDialError() error { + err, _ := p.lastDialError.Load().(*lastDialErrorWrap) + if err != nil { + return err.err + } + return nil +} + +// Get returns existed connection from the pool or creates a new one. +func (p *ConnPool) Get(ctx context.Context) (*Conn, error) { + if p.closed() { + return nil, ErrClosed + } + + if err := p.waitTurn(ctx); err != nil { + return nil, err + } + + for { + p.connsMu.Lock() + cn, err := p.popIdle() + p.connsMu.Unlock() + + if err != nil { + return nil, err + } + + if cn == nil { + break + } + + if p.isStaleConn(cn) { + _ = p.CloseConn(cn) + continue + } + + atomic.AddUint32(&p.stats.Hits, 1) + return cn, nil + } + + atomic.AddUint32(&p.stats.Misses, 1) + + newcn, err := p.newConn(ctx, true) + if err != nil { + p.freeTurn() + return nil, err + } + + return newcn, nil +} + +func (p *ConnPool) getTurn() { + p.queue <- struct{}{} +} + +func (p *ConnPool) waitTurn(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + select { + case p.queue <- struct{}{}: + return nil + default: + } + + timer := timers.Get().(*time.Timer) + timer.Reset(p.opt.PoolTimeout) + + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + timers.Put(timer) + return ctx.Err() + case p.queue <- struct{}{}: + if !timer.Stop() { + <-timer.C + } + timers.Put(timer) + return nil + case <-timer.C: + timers.Put(timer) + atomic.AddUint32(&p.stats.Timeouts, 1) + return ErrPoolTimeout + } +} + +func (p *ConnPool) freeTurn() { + <-p.queue +} + +func (p *ConnPool) popIdle() (*Conn, error) { + if p.closed() { + return nil, ErrClosed + } + n := len(p.idleConns) + if n == 0 { + return nil, nil + } + + var cn *Conn + if p.opt.PoolFIFO { + cn = p.idleConns[0] + copy(p.idleConns, p.idleConns[1:]) + p.idleConns = p.idleConns[:n-1] + } else { + idx := n - 1 + cn = p.idleConns[idx] + p.idleConns = p.idleConns[:idx] + } + p.idleConnsLen-- + p.checkMinIdleConns() + return cn, nil +} + +func (p *ConnPool) Put(ctx context.Context, cn *Conn) { + if cn.rd.Buffered() > 0 { + internal.Logger.Printf(ctx, "Conn has unread data") + p.Remove(ctx, cn, BadConnError{}) + return + } + + if !cn.pooled { + p.Remove(ctx, cn, nil) + return + } + + p.connsMu.Lock() + p.idleConns = append(p.idleConns, cn) + p.idleConnsLen++ + p.connsMu.Unlock() + p.freeTurn() +} + +func (p *ConnPool) Remove(ctx context.Context, cn *Conn, reason error) { + p.removeConnWithLock(cn) + p.freeTurn() + _ = p.closeConn(cn) +} + +func (p *ConnPool) CloseConn(cn *Conn) error { + p.removeConnWithLock(cn) + return p.closeConn(cn) +} + +func (p *ConnPool) removeConnWithLock(cn *Conn) { + p.connsMu.Lock() + p.removeConn(cn) + p.connsMu.Unlock() +} + +func (p *ConnPool) removeConn(cn *Conn) { + for i, c := range p.conns { + if c == cn { + p.conns = append(p.conns[:i], p.conns[i+1:]...) + if cn.pooled { + p.poolSize-- + p.checkMinIdleConns() + } + return + } + } +} + +func (p *ConnPool) closeConn(cn *Conn) error { + if p.opt.OnClose != nil { + _ = p.opt.OnClose(cn) + } + return cn.Close() +} + +// Len returns total number of connections. +func (p *ConnPool) Len() int { + p.connsMu.Lock() + n := len(p.conns) + p.connsMu.Unlock() + return n +} + +// IdleLen returns number of idle connections. +func (p *ConnPool) IdleLen() int { + p.connsMu.Lock() + n := p.idleConnsLen + p.connsMu.Unlock() + return n +} + +func (p *ConnPool) Stats() *Stats { + idleLen := p.IdleLen() + return &Stats{ + Hits: atomic.LoadUint32(&p.stats.Hits), + Misses: atomic.LoadUint32(&p.stats.Misses), + Timeouts: atomic.LoadUint32(&p.stats.Timeouts), + + TotalConns: uint32(p.Len()), + IdleConns: uint32(idleLen), + StaleConns: atomic.LoadUint32(&p.stats.StaleConns), + } +} + +func (p *ConnPool) closed() bool { + return atomic.LoadUint32(&p._closed) == 1 +} + +func (p *ConnPool) Filter(fn func(*Conn) bool) error { + p.connsMu.Lock() + defer p.connsMu.Unlock() + + var firstErr error + for _, cn := range p.conns { + if fn(cn) { + if err := p.closeConn(cn); err != nil && firstErr == nil { + firstErr = err + } + } + } + return firstErr +} + +func (p *ConnPool) Close() error { + if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) { + return ErrClosed + } + close(p.closedCh) + + var firstErr error + p.connsMu.Lock() + for _, cn := range p.conns { + if err := p.closeConn(cn); err != nil && firstErr == nil { + firstErr = err + } + } + p.conns = nil + p.poolSize = 0 + p.idleConns = nil + p.idleConnsLen = 0 + p.connsMu.Unlock() + + return firstErr +} + +func (p *ConnPool) reaper(frequency time.Duration) { + ticker := time.NewTicker(frequency) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // It is possible that ticker and closedCh arrive together, + // and select pseudo-randomly pick ticker case, we double + // check here to prevent being executed after closed. + if p.closed() { + return + } + _, err := p.ReapStaleConns() + if err != nil { + internal.Logger.Printf(context.Background(), "ReapStaleConns failed: %s", err) + continue + } + case <-p.closedCh: + return + } + } +} + +func (p *ConnPool) ReapStaleConns() (int, error) { + var n int + for { + p.getTurn() + + p.connsMu.Lock() + cn := p.reapStaleConn() + p.connsMu.Unlock() + + p.freeTurn() + + if cn != nil { + _ = p.closeConn(cn) + n++ + } else { + break + } + } + atomic.AddUint32(&p.stats.StaleConns, uint32(n)) + return n, nil +} + +func (p *ConnPool) reapStaleConn() *Conn { + if len(p.idleConns) == 0 { + return nil + } + + cn := p.idleConns[0] + if !p.isStaleConn(cn) { + return nil + } + + p.idleConns = append(p.idleConns[:0], p.idleConns[1:]...) + p.idleConnsLen-- + p.removeConn(cn) + + return cn +} + +func (p *ConnPool) isStaleConn(cn *Conn) bool { + if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 { + return false + } + + now := time.Now() + if p.opt.IdleTimeout > 0 && now.Sub(cn.UsedAt()) >= p.opt.IdleTimeout { + return true + } + if p.opt.MaxConnAge > 0 && now.Sub(cn.createdAt) >= p.opt.MaxConnAge { + return true + } + + return false +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/pool/pool_single.go b/vendor/github.com/go-redis/redis/v8/internal/pool/pool_single.go new file mode 100644 index 0000000000..5a3fde191b --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/pool/pool_single.go @@ -0,0 +1,58 @@ +package pool + +import "context" + +type SingleConnPool struct { + pool Pooler + cn *Conn + stickyErr error +} + +var _ Pooler = (*SingleConnPool)(nil) + +func NewSingleConnPool(pool Pooler, cn *Conn) *SingleConnPool { + return &SingleConnPool{ + pool: pool, + cn: cn, + } +} + +func (p *SingleConnPool) NewConn(ctx context.Context) (*Conn, error) { + return p.pool.NewConn(ctx) +} + +func (p *SingleConnPool) CloseConn(cn *Conn) error { + return p.pool.CloseConn(cn) +} + +func (p *SingleConnPool) Get(ctx context.Context) (*Conn, error) { + if p.stickyErr != nil { + return nil, p.stickyErr + } + return p.cn, nil +} + +func (p *SingleConnPool) Put(ctx context.Context, cn *Conn) {} + +func (p *SingleConnPool) Remove(ctx context.Context, cn *Conn, reason error) { + p.cn = nil + p.stickyErr = reason +} + +func (p *SingleConnPool) Close() error { + p.cn = nil + p.stickyErr = ErrClosed + return nil +} + +func (p *SingleConnPool) Len() int { + return 0 +} + +func (p *SingleConnPool) IdleLen() int { + return 0 +} + +func (p *SingleConnPool) Stats() *Stats { + return &Stats{} +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/pool/pool_sticky.go b/vendor/github.com/go-redis/redis/v8/internal/pool/pool_sticky.go new file mode 100644 index 0000000000..3adb99bc82 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/pool/pool_sticky.go @@ -0,0 +1,201 @@ +package pool + +import ( + "context" + "errors" + "fmt" + "sync/atomic" +) + +const ( + stateDefault = 0 + stateInited = 1 + stateClosed = 2 +) + +type BadConnError struct { + wrapped error +} + +var _ error = (*BadConnError)(nil) + +func (e BadConnError) Error() string { + s := "redis: Conn is in a bad state" + if e.wrapped != nil { + s += ": " + e.wrapped.Error() + } + return s +} + +func (e BadConnError) Unwrap() error { + return e.wrapped +} + +//------------------------------------------------------------------------------ + +type StickyConnPool struct { + pool Pooler + shared int32 // atomic + + state uint32 // atomic + ch chan *Conn + + _badConnError atomic.Value +} + +var _ Pooler = (*StickyConnPool)(nil) + +func NewStickyConnPool(pool Pooler) *StickyConnPool { + p, ok := pool.(*StickyConnPool) + if !ok { + p = &StickyConnPool{ + pool: pool, + ch: make(chan *Conn, 1), + } + } + atomic.AddInt32(&p.shared, 1) + return p +} + +func (p *StickyConnPool) NewConn(ctx context.Context) (*Conn, error) { + return p.pool.NewConn(ctx) +} + +func (p *StickyConnPool) CloseConn(cn *Conn) error { + return p.pool.CloseConn(cn) +} + +func (p *StickyConnPool) Get(ctx context.Context) (*Conn, error) { + // In worst case this races with Close which is not a very common operation. + for i := 0; i < 1000; i++ { + switch atomic.LoadUint32(&p.state) { + case stateDefault: + cn, err := p.pool.Get(ctx) + if err != nil { + return nil, err + } + if atomic.CompareAndSwapUint32(&p.state, stateDefault, stateInited) { + return cn, nil + } + p.pool.Remove(ctx, cn, ErrClosed) + case stateInited: + if err := p.badConnError(); err != nil { + return nil, err + } + cn, ok := <-p.ch + if !ok { + return nil, ErrClosed + } + return cn, nil + case stateClosed: + return nil, ErrClosed + default: + panic("not reached") + } + } + return nil, fmt.Errorf("redis: StickyConnPool.Get: infinite loop") +} + +func (p *StickyConnPool) Put(ctx context.Context, cn *Conn) { + defer func() { + if recover() != nil { + p.freeConn(ctx, cn) + } + }() + p.ch <- cn +} + +func (p *StickyConnPool) freeConn(ctx context.Context, cn *Conn) { + if err := p.badConnError(); err != nil { + p.pool.Remove(ctx, cn, err) + } else { + p.pool.Put(ctx, cn) + } +} + +func (p *StickyConnPool) Remove(ctx context.Context, cn *Conn, reason error) { + defer func() { + if recover() != nil { + p.pool.Remove(ctx, cn, ErrClosed) + } + }() + p._badConnError.Store(BadConnError{wrapped: reason}) + p.ch <- cn +} + +func (p *StickyConnPool) Close() error { + if shared := atomic.AddInt32(&p.shared, -1); shared > 0 { + return nil + } + + for i := 0; i < 1000; i++ { + state := atomic.LoadUint32(&p.state) + if state == stateClosed { + return ErrClosed + } + if atomic.CompareAndSwapUint32(&p.state, state, stateClosed) { + close(p.ch) + cn, ok := <-p.ch + if ok { + p.freeConn(context.TODO(), cn) + } + return nil + } + } + + return errors.New("redis: StickyConnPool.Close: infinite loop") +} + +func (p *StickyConnPool) Reset(ctx context.Context) error { + if p.badConnError() == nil { + return nil + } + + select { + case cn, ok := <-p.ch: + if !ok { + return ErrClosed + } + p.pool.Remove(ctx, cn, ErrClosed) + p._badConnError.Store(BadConnError{wrapped: nil}) + default: + return errors.New("redis: StickyConnPool does not have a Conn") + } + + if !atomic.CompareAndSwapUint32(&p.state, stateInited, stateDefault) { + state := atomic.LoadUint32(&p.state) + return fmt.Errorf("redis: invalid StickyConnPool state: %d", state) + } + + return nil +} + +func (p *StickyConnPool) badConnError() error { + if v := p._badConnError.Load(); v != nil { + if err := v.(BadConnError); err.wrapped != nil { + return err + } + } + return nil +} + +func (p *StickyConnPool) Len() int { + switch atomic.LoadUint32(&p.state) { + case stateDefault: + return 0 + case stateInited: + return 1 + case stateClosed: + return 0 + default: + panic("not reached") + } +} + +func (p *StickyConnPool) IdleLen() int { + return len(p.ch) +} + +func (p *StickyConnPool) Stats() *Stats { + return &Stats{} +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/pool/ya.make b/vendor/github.com/go-redis/redis/v8/internal/pool/ya.make new file mode 100644 index 0000000000..31e095aba1 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/pool/ya.make @@ -0,0 +1,22 @@ +GO_LIBRARY() + +LICENSE(BSD-2-Clause) + +SRCS( + conn.go + pool.go + pool_single.go + pool_sticky.go +) + +GO_TEST_SRCS(export_test.go) + +GO_XTEST_SRCS( + bench_test.go + main_test.go + pool_test.go +) + +END() + +RECURSE(gotest) diff --git a/vendor/github.com/go-redis/redis/v8/internal/proto/reader.go b/vendor/github.com/go-redis/redis/v8/internal/proto/reader.go new file mode 100644 index 0000000000..0e6ca779b1 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/proto/reader.go @@ -0,0 +1,332 @@ +package proto + +import ( + "bufio" + "fmt" + "io" + + "github.com/go-redis/redis/v8/internal/util" +) + +// redis resp protocol data type. +const ( + ErrorReply = '-' + StatusReply = '+' + IntReply = ':' + StringReply = '$' + ArrayReply = '*' +) + +//------------------------------------------------------------------------------ + +const Nil = RedisError("redis: nil") // nolint:errname + +type RedisError string + +func (e RedisError) Error() string { return string(e) } + +func (RedisError) RedisError() {} + +//------------------------------------------------------------------------------ + +type MultiBulkParse func(*Reader, int64) (interface{}, error) + +type Reader struct { + rd *bufio.Reader + _buf []byte +} + +func NewReader(rd io.Reader) *Reader { + return &Reader{ + rd: bufio.NewReader(rd), + _buf: make([]byte, 64), + } +} + +func (r *Reader) Buffered() int { + return r.rd.Buffered() +} + +func (r *Reader) Peek(n int) ([]byte, error) { + return r.rd.Peek(n) +} + +func (r *Reader) Reset(rd io.Reader) { + r.rd.Reset(rd) +} + +func (r *Reader) ReadLine() ([]byte, error) { + line, err := r.readLine() + if err != nil { + return nil, err + } + if isNilReply(line) { + return nil, Nil + } + return line, nil +} + +// readLine that returns an error if: +// - there is a pending read error; +// - or line does not end with \r\n. +func (r *Reader) readLine() ([]byte, error) { + b, err := r.rd.ReadSlice('\n') + if err != nil { + if err != bufio.ErrBufferFull { + return nil, err + } + + full := make([]byte, len(b)) + copy(full, b) + + b, err = r.rd.ReadBytes('\n') + if err != nil { + return nil, err + } + + full = append(full, b...) //nolint:makezero + b = full + } + if len(b) <= 2 || b[len(b)-1] != '\n' || b[len(b)-2] != '\r' { + return nil, fmt.Errorf("redis: invalid reply: %q", b) + } + return b[:len(b)-2], nil +} + +func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) { + line, err := r.ReadLine() + if err != nil { + return nil, err + } + + switch line[0] { + case ErrorReply: + return nil, ParseErrorReply(line) + case StatusReply: + return string(line[1:]), nil + case IntReply: + return util.ParseInt(line[1:], 10, 64) + case StringReply: + return r.readStringReply(line) + case ArrayReply: + n, err := parseArrayLen(line) + if err != nil { + return nil, err + } + if m == nil { + err := fmt.Errorf("redis: got %.100q, but multi bulk parser is nil", line) + return nil, err + } + return m(r, n) + } + return nil, fmt.Errorf("redis: can't parse %.100q", line) +} + +func (r *Reader) ReadIntReply() (int64, error) { + line, err := r.ReadLine() + if err != nil { + return 0, err + } + switch line[0] { + case ErrorReply: + return 0, ParseErrorReply(line) + case IntReply: + return util.ParseInt(line[1:], 10, 64) + default: + return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line) + } +} + +func (r *Reader) ReadString() (string, error) { + line, err := r.ReadLine() + if err != nil { + return "", err + } + switch line[0] { + case ErrorReply: + return "", ParseErrorReply(line) + case StringReply: + return r.readStringReply(line) + case StatusReply: + return string(line[1:]), nil + case IntReply: + return string(line[1:]), nil + default: + return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line) + } +} + +func (r *Reader) readStringReply(line []byte) (string, error) { + if isNilReply(line) { + return "", Nil + } + + replyLen, err := util.Atoi(line[1:]) + if err != nil { + return "", err + } + + b := make([]byte, replyLen+2) + _, err = io.ReadFull(r.rd, b) + if err != nil { + return "", err + } + + return util.BytesToString(b[:replyLen]), nil +} + +func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) { + line, err := r.ReadLine() + if err != nil { + return nil, err + } + switch line[0] { + case ErrorReply: + return nil, ParseErrorReply(line) + case ArrayReply: + n, err := parseArrayLen(line) + if err != nil { + return nil, err + } + return m(r, n) + default: + return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line) + } +} + +func (r *Reader) ReadArrayLen() (int, error) { + line, err := r.ReadLine() + if err != nil { + return 0, err + } + switch line[0] { + case ErrorReply: + return 0, ParseErrorReply(line) + case ArrayReply: + n, err := parseArrayLen(line) + if err != nil { + return 0, err + } + return int(n), nil + default: + return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line) + } +} + +func (r *Reader) ReadScanReply() ([]string, uint64, error) { + n, err := r.ReadArrayLen() + if err != nil { + return nil, 0, err + } + if n != 2 { + return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n) + } + + cursor, err := r.ReadUint() + if err != nil { + return nil, 0, err + } + + n, err = r.ReadArrayLen() + if err != nil { + return nil, 0, err + } + + keys := make([]string, n) + + for i := 0; i < n; i++ { + key, err := r.ReadString() + if err != nil { + return nil, 0, err + } + keys[i] = key + } + + return keys, cursor, err +} + +func (r *Reader) ReadInt() (int64, error) { + b, err := r.readTmpBytesReply() + if err != nil { + return 0, err + } + return util.ParseInt(b, 10, 64) +} + +func (r *Reader) ReadUint() (uint64, error) { + b, err := r.readTmpBytesReply() + if err != nil { + return 0, err + } + return util.ParseUint(b, 10, 64) +} + +func (r *Reader) ReadFloatReply() (float64, error) { + b, err := r.readTmpBytesReply() + if err != nil { + return 0, err + } + return util.ParseFloat(b, 64) +} + +func (r *Reader) readTmpBytesReply() ([]byte, error) { + line, err := r.ReadLine() + if err != nil { + return nil, err + } + switch line[0] { + case ErrorReply: + return nil, ParseErrorReply(line) + case StringReply: + return r._readTmpBytesReply(line) + case StatusReply: + return line[1:], nil + default: + return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line) + } +} + +func (r *Reader) _readTmpBytesReply(line []byte) ([]byte, error) { + if isNilReply(line) { + return nil, Nil + } + + replyLen, err := util.Atoi(line[1:]) + if err != nil { + return nil, err + } + + buf := r.buf(replyLen + 2) + _, err = io.ReadFull(r.rd, buf) + if err != nil { + return nil, err + } + + return buf[:replyLen], nil +} + +func (r *Reader) buf(n int) []byte { + if n <= cap(r._buf) { + return r._buf[:n] + } + d := n - cap(r._buf) + r._buf = append(r._buf, make([]byte, d)...) + return r._buf +} + +func isNilReply(b []byte) bool { + return len(b) == 3 && + (b[0] == StringReply || b[0] == ArrayReply) && + b[1] == '-' && b[2] == '1' +} + +func ParseErrorReply(line []byte) error { + return RedisError(string(line[1:])) +} + +func parseArrayLen(line []byte) (int64, error) { + if isNilReply(line) { + return 0, Nil + } + return util.ParseInt(line[1:], 10, 64) +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/proto/scan.go b/vendor/github.com/go-redis/redis/v8/internal/proto/scan.go new file mode 100644 index 0000000000..0e994765fe --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/proto/scan.go @@ -0,0 +1,180 @@ +package proto + +import ( + "encoding" + "fmt" + "reflect" + "time" + + "github.com/go-redis/redis/v8/internal/util" +) + +// Scan parses bytes `b` to `v` with appropriate type. +//nolint:gocyclo +func Scan(b []byte, v interface{}) error { + switch v := v.(type) { + case nil: + return fmt.Errorf("redis: Scan(nil)") + case *string: + *v = util.BytesToString(b) + return nil + case *[]byte: + *v = b + return nil + case *int: + var err error + *v, err = util.Atoi(b) + return err + case *int8: + n, err := util.ParseInt(b, 10, 8) + if err != nil { + return err + } + *v = int8(n) + return nil + case *int16: + n, err := util.ParseInt(b, 10, 16) + if err != nil { + return err + } + *v = int16(n) + return nil + case *int32: + n, err := util.ParseInt(b, 10, 32) + if err != nil { + return err + } + *v = int32(n) + return nil + case *int64: + n, err := util.ParseInt(b, 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *uint: + n, err := util.ParseUint(b, 10, 64) + if err != nil { + return err + } + *v = uint(n) + return nil + case *uint8: + n, err := util.ParseUint(b, 10, 8) + if err != nil { + return err + } + *v = uint8(n) + return nil + case *uint16: + n, err := util.ParseUint(b, 10, 16) + if err != nil { + return err + } + *v = uint16(n) + return nil + case *uint32: + n, err := util.ParseUint(b, 10, 32) + if err != nil { + return err + } + *v = uint32(n) + return nil + case *uint64: + n, err := util.ParseUint(b, 10, 64) + if err != nil { + return err + } + *v = n + return nil + case *float32: + n, err := util.ParseFloat(b, 32) + if err != nil { + return err + } + *v = float32(n) + return err + case *float64: + var err error + *v, err = util.ParseFloat(b, 64) + return err + case *bool: + *v = len(b) == 1 && b[0] == '1' + return nil + case *time.Time: + var err error + *v, err = time.Parse(time.RFC3339Nano, util.BytesToString(b)) + return err + case *time.Duration: + n, err := util.ParseInt(b, 10, 64) + if err != nil { + return err + } + *v = time.Duration(n) + return nil + case encoding.BinaryUnmarshaler: + return v.UnmarshalBinary(b) + default: + return fmt.Errorf( + "redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v) + } +} + +func ScanSlice(data []string, slice interface{}) error { + v := reflect.ValueOf(slice) + if !v.IsValid() { + return fmt.Errorf("redis: ScanSlice(nil)") + } + if v.Kind() != reflect.Ptr { + return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice) + } + v = v.Elem() + if v.Kind() != reflect.Slice { + return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice) + } + + next := makeSliceNextElemFunc(v) + for i, s := range data { + elem := next() + if err := Scan([]byte(s), elem.Addr().Interface()); err != nil { + err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %w", i, s, err) + return err + } + } + + return nil +} + +func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value { + elemType := v.Type().Elem() + + if elemType.Kind() == reflect.Ptr { + elemType = elemType.Elem() + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + elem := v.Index(v.Len() - 1) + if elem.IsNil() { + elem.Set(reflect.New(elemType)) + } + return elem.Elem() + } + + elem := reflect.New(elemType) + v.Set(reflect.Append(v, elem)) + return elem.Elem() + } + } + + zero := reflect.Zero(elemType) + return func() reflect.Value { + if v.Len() < v.Cap() { + v.Set(v.Slice(0, v.Len()+1)) + return v.Index(v.Len() - 1) + } + + v.Set(reflect.Append(v, zero)) + return v.Index(v.Len() - 1) + } +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/proto/writer.go b/vendor/github.com/go-redis/redis/v8/internal/proto/writer.go new file mode 100644 index 0000000000..c4260981ed --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/proto/writer.go @@ -0,0 +1,155 @@ +package proto + +import ( + "encoding" + "fmt" + "io" + "strconv" + "time" + + "github.com/go-redis/redis/v8/internal/util" +) + +type writer interface { + io.Writer + io.ByteWriter + // io.StringWriter + WriteString(s string) (n int, err error) +} + +type Writer struct { + writer + + lenBuf []byte + numBuf []byte +} + +func NewWriter(wr writer) *Writer { + return &Writer{ + writer: wr, + + lenBuf: make([]byte, 64), + numBuf: make([]byte, 64), + } +} + +func (w *Writer) WriteArgs(args []interface{}) error { + if err := w.WriteByte(ArrayReply); err != nil { + return err + } + + if err := w.writeLen(len(args)); err != nil { + return err + } + + for _, arg := range args { + if err := w.WriteArg(arg); err != nil { + return err + } + } + + return nil +} + +func (w *Writer) writeLen(n int) error { + w.lenBuf = strconv.AppendUint(w.lenBuf[:0], uint64(n), 10) + w.lenBuf = append(w.lenBuf, '\r', '\n') + _, err := w.Write(w.lenBuf) + return err +} + +func (w *Writer) WriteArg(v interface{}) error { + switch v := v.(type) { + case nil: + return w.string("") + case string: + return w.string(v) + case []byte: + return w.bytes(v) + case int: + return w.int(int64(v)) + case int8: + return w.int(int64(v)) + case int16: + return w.int(int64(v)) + case int32: + return w.int(int64(v)) + case int64: + return w.int(v) + case uint: + return w.uint(uint64(v)) + case uint8: + return w.uint(uint64(v)) + case uint16: + return w.uint(uint64(v)) + case uint32: + return w.uint(uint64(v)) + case uint64: + return w.uint(v) + case float32: + return w.float(float64(v)) + case float64: + return w.float(v) + case bool: + if v { + return w.int(1) + } + return w.int(0) + case time.Time: + w.numBuf = v.AppendFormat(w.numBuf[:0], time.RFC3339Nano) + return w.bytes(w.numBuf) + case time.Duration: + return w.int(v.Nanoseconds()) + case encoding.BinaryMarshaler: + b, err := v.MarshalBinary() + if err != nil { + return err + } + return w.bytes(b) + default: + return fmt.Errorf( + "redis: can't marshal %T (implement encoding.BinaryMarshaler)", v) + } +} + +func (w *Writer) bytes(b []byte) error { + if err := w.WriteByte(StringReply); err != nil { + return err + } + + if err := w.writeLen(len(b)); err != nil { + return err + } + + if _, err := w.Write(b); err != nil { + return err + } + + return w.crlf() +} + +func (w *Writer) string(s string) error { + return w.bytes(util.StringToBytes(s)) +} + +func (w *Writer) uint(n uint64) error { + w.numBuf = strconv.AppendUint(w.numBuf[:0], n, 10) + return w.bytes(w.numBuf) +} + +func (w *Writer) int(n int64) error { + w.numBuf = strconv.AppendInt(w.numBuf[:0], n, 10) + return w.bytes(w.numBuf) +} + +func (w *Writer) float(f float64) error { + w.numBuf = strconv.AppendFloat(w.numBuf[:0], f, 'f', -1, 64) + return w.bytes(w.numBuf) +} + +func (w *Writer) crlf() error { + if err := w.WriteByte('\r'); err != nil { + return err + } + return w.WriteByte('\n') +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/proto/ya.make b/vendor/github.com/go-redis/redis/v8/internal/proto/ya.make new file mode 100644 index 0000000000..6ba8a5f13e --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/proto/ya.make @@ -0,0 +1,20 @@ +GO_LIBRARY() + +LICENSE(BSD-2-Clause) + +SRCS( + reader.go + scan.go + writer.go +) + +GO_XTEST_SRCS( + proto_test.go + reader_test.go + scan_test.go + writer_test.go +) + +END() + +RECURSE(gotest) diff --git a/vendor/github.com/go-redis/redis/v8/internal/rand/rand.go b/vendor/github.com/go-redis/redis/v8/internal/rand/rand.go new file mode 100644 index 0000000000..2edccba94f --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/rand/rand.go @@ -0,0 +1,50 @@ +package rand + +import ( + "math/rand" + "sync" +) + +// Int returns a non-negative pseudo-random int. +func Int() int { return pseudo.Int() } + +// Intn returns, as an int, a non-negative pseudo-random number in [0,n). +// It panics if n <= 0. +func Intn(n int) int { return pseudo.Intn(n) } + +// Int63n returns, as an int64, a non-negative pseudo-random number in [0,n). +// It panics if n <= 0. +func Int63n(n int64) int64 { return pseudo.Int63n(n) } + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n). +func Perm(n int) []int { return pseudo.Perm(n) } + +// Seed uses the provided seed value to initialize the default Source to a +// deterministic state. If Seed is not called, the generator behaves as if +// seeded by Seed(1). +func Seed(n int64) { pseudo.Seed(n) } + +var pseudo = rand.New(&source{src: rand.NewSource(1)}) + +type source struct { + src rand.Source + mu sync.Mutex +} + +func (s *source) Int63() int64 { + s.mu.Lock() + n := s.src.Int63() + s.mu.Unlock() + return n +} + +func (s *source) Seed(seed int64) { + s.mu.Lock() + s.src.Seed(seed) + s.mu.Unlock() +} + +// Shuffle pseudo-randomizes the order of elements. +// n is the number of elements. +// swap swaps the elements with indexes i and j. +func Shuffle(n int, swap func(i, j int)) { pseudo.Shuffle(n, swap) } diff --git a/vendor/github.com/go-redis/redis/v8/internal/rand/ya.make b/vendor/github.com/go-redis/redis/v8/internal/rand/ya.make new file mode 100644 index 0000000000..eb2f7feb15 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/rand/ya.make @@ -0,0 +1,7 @@ +GO_LIBRARY() + +LICENSE(BSD-2-Clause) + +SRCS(rand.go) + +END() diff --git a/vendor/github.com/go-redis/redis/v8/internal/unsafe.go b/vendor/github.com/go-redis/redis/v8/internal/unsafe.go new file mode 100644 index 0000000000..9f2e418f79 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/unsafe.go @@ -0,0 +1,21 @@ +//go:build !appengine +// +build !appengine + +package internal + +import "unsafe" + +// String converts byte slice to string. +func String(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// Bytes converts string to byte slice. +func Bytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/util.go b/vendor/github.com/go-redis/redis/v8/internal/util.go new file mode 100644 index 0000000000..e34a7f0326 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/util.go @@ -0,0 +1,46 @@ +package internal + +import ( + "context" + "time" + + "github.com/go-redis/redis/v8/internal/util" +) + +func Sleep(ctx context.Context, dur time.Duration) error { + t := time.NewTimer(dur) + defer t.Stop() + + select { + case <-t.C: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func ToLower(s string) string { + if isLower(s) { + return s + } + + b := make([]byte, len(s)) + for i := range b { + c := s[i] + if c >= 'A' && c <= 'Z' { + c += 'a' - 'A' + } + b[i] = c + } + return util.BytesToString(b) +} + +func isLower(s string) bool { + for i := 0; i < len(s); i++ { + c := s[i] + if c >= 'A' && c <= 'Z' { + return false + } + } + return true +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/util/strconv.go b/vendor/github.com/go-redis/redis/v8/internal/util/strconv.go new file mode 100644 index 0000000000..db5033802a --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/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) +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/util/unsafe.go b/vendor/github.com/go-redis/redis/v8/internal/util/unsafe.go new file mode 100644 index 0000000000..daa8d7692a --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/util/unsafe.go @@ -0,0 +1,23 @@ +//go:build !appengine +// +build !appengine + +package util + +import ( + "unsafe" +) + +// BytesToString converts byte slice to string. +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +// StringToBytes converts string to byte slice. +func StringToBytes(s string) []byte { + return *(*[]byte)(unsafe.Pointer( + &struct { + string + Cap int + }{s, len(s)}, + )) +} diff --git a/vendor/github.com/go-redis/redis/v8/internal/util/ya.make b/vendor/github.com/go-redis/redis/v8/internal/util/ya.make new file mode 100644 index 0000000000..3497e3cf34 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/util/ya.make @@ -0,0 +1,10 @@ +GO_LIBRARY() + +LICENSE(BSD-2-Clause) + +SRCS( + strconv.go + unsafe.go +) + +END() diff --git a/vendor/github.com/go-redis/redis/v8/internal/ya.make b/vendor/github.com/go-redis/redis/v8/internal/ya.make new file mode 100644 index 0000000000..1cf123fac4 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/internal/ya.make @@ -0,0 +1,26 @@ +GO_LIBRARY() + +LICENSE(BSD-2-Clause) + +SRCS( + arg.go + internal.go + log.go + once.go + unsafe.go + util.go +) + +GO_TEST_SRCS(internal_test.go) + +END() + +RECURSE( + gotest + hashtag + hscan + pool + proto + rand + util +) |