diff --git a/go.mod b/go.mod index 617b157..08f60b3 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,10 @@ go 1.15 require ( github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b - github.com/gomodule/redigo v1.8.4 + github.com/gomodule/redigo v1.9.2 github.com/google/btree v1.0.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/peterbourgon/diskv v2.0.1+incompatible github.com/syndtr/goleveldb v1.0.0 + gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index 1880911..7440ef3 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,16 @@ github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.8.4 h1:Z5JUg94HMTR1XpwBaSH4vq3+PNSIykBLxMdglbw10gg= -github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s= +github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= @@ -26,8 +27,12 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= @@ -47,3 +52,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/gomodule/redigo/redis/conn.go b/vendor/github.com/gomodule/redigo/redis/conn.go index 1398b4d..753644b 100644 --- a/vendor/github.com/gomodule/redigo/redis/conn.go +++ b/vendor/github.com/gomodule/redigo/redis/conn.go @@ -168,6 +168,7 @@ func DialPassword(password string) DialOption { // DialUsername specifies the username to use when connecting to // the Redis server when Redis ACLs are used. +// A DialPassword must also be passed otherwise this option will have no effect. func DialUsername(username string) DialOption { return DialOption{func(do *dialOptions) { do.username = username @@ -245,7 +246,7 @@ func DialContext(ctx context.Context, network, address string, options ...DialOp if do.tlsConfig == nil { tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} } else { - tlsConfig = cloneTLSConfig(do.tlsConfig) + tlsConfig = do.tlsConfig.Clone() } if tlsConfig.ServerName == "" { host, _, err := net.SplitHostPort(address) @@ -290,21 +291,21 @@ func DialContext(ctx context.Context, network, address string, options ...DialOp authArgs = append(authArgs, do.username) } authArgs = append(authArgs, do.password) - if _, err := c.Do("AUTH", authArgs...); err != nil { + if _, err := c.DoContext(ctx, "AUTH", authArgs...); err != nil { netConn.Close() return nil, err } } if do.clientName != "" { - if _, err := c.Do("CLIENT", "SETNAME", do.clientName); err != nil { + if _, err := c.DoContext(ctx, "CLIENT", "SETNAME", do.clientName); err != nil { netConn.Close() return nil, err } } if do.db != 0 { - if _, err := c.Do("SELECT", do.db); err != nil { + if _, err := c.DoContext(ctx, "SELECT", do.db); err != nil { netConn.Close() return nil, err } @@ -315,10 +316,17 @@ func DialContext(ctx context.Context, network, address string, options ...DialOp var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) -// DialURL connects to a Redis server at the given URL using the Redis +// DialURL wraps DialURLContext using context.Background. +func DialURL(rawurl string, options ...DialOption) (Conn, error) { + ctx := context.Background() + + return DialURLContext(ctx, rawurl, options...) +} + +// DialURLContext connects to a Redis server at the given URL using the Redis // URI scheme. URLs should follow the draft IANA specification for the // scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). -func DialURL(rawurl string, options ...DialOption) (Conn, error) { +func DialURLContext(ctx context.Context, rawurl string, options ...DialOption) (Conn, error) { u, err := url.Parse(rawurl) if err != nil { return nil, err @@ -347,8 +355,18 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) { if u.User != nil { password, isSet := u.User.Password() + username := u.User.Username() if isSet { - options = append(options, DialUsername(u.User.Username()), DialPassword(password)) + if username != "" { + // ACL + options = append(options, DialUsername(username), DialPassword(password)) + } else { + // requirepass - user-info username:password with blank username + options = append(options, DialPassword(password)) + } + } else if username != "" { + // requirepass - redis-cli compatibility which treats as single arg in user-info as a password + options = append(options, DialPassword(username)) } } @@ -370,7 +388,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) { options = append(options, DialUseTLS(u.Scheme == "rediss")) - return Dial("tcp", address, options...) + return DialContext(ctx, "tcp", address, options...) } // NewConn returns a new Redigo connection for the given net connection. @@ -692,6 +710,36 @@ func (c *conn) Receive() (interface{}, error) { return c.ReceiveWithTimeout(c.readTimeout) } +func (c *conn) ReceiveContext(ctx context.Context) (interface{}, error) { + var realTimeout time.Duration + if dl, ok := ctx.Deadline(); ok { + timeout := time.Until(dl) + if timeout >= c.readTimeout && c.readTimeout != 0 { + realTimeout = c.readTimeout + } else if timeout <= 0 { + return nil, c.fatal(context.DeadlineExceeded) + } else { + realTimeout = timeout + } + } else { + realTimeout = c.readTimeout + } + endch := make(chan struct{}) + var r interface{} + var e error + go func() { + defer close(endch) + + r, e = c.ReceiveWithTimeout(realTimeout) + }() + select { + case <-ctx.Done(): + return nil, c.fatal(ctx.Err()) + case <-endch: + return r, e + } +} + func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { var deadline time.Time if timeout != 0 { @@ -726,6 +774,36 @@ func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { return c.DoWithTimeout(c.readTimeout, cmd, args...) } +func (c *conn) DoContext(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) { + var realTimeout time.Duration + if dl, ok := ctx.Deadline(); ok { + timeout := time.Until(dl) + if timeout >= c.readTimeout && c.readTimeout != 0 { + realTimeout = c.readTimeout + } else if timeout <= 0 { + return nil, c.fatal(context.DeadlineExceeded) + } else { + realTimeout = timeout + } + } else { + realTimeout = c.readTimeout + } + endch := make(chan struct{}) + var r interface{} + var e error + go func() { + defer close(endch) + + r, e = c.DoWithTimeout(realTimeout, cmd, args...) + }() + select { + case <-ctx.Done(): + return nil, c.fatal(ctx.Err()) + case <-endch: + return r, e + } +} + func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { c.mu.Lock() pending := c.pending diff --git a/vendor/github.com/gomodule/redigo/redis/go17.go b/vendor/github.com/gomodule/redigo/redis/go17.go deleted file mode 100644 index 5f36379..0000000 --- a/vendor/github.com/gomodule/redigo/redis/go17.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build go1.7,!go1.8 - -package redis - -import "crypto/tls" - -func cloneTLSConfig(cfg *tls.Config) *tls.Config { - return &tls.Config{ - Rand: cfg.Rand, - Time: cfg.Time, - Certificates: cfg.Certificates, - NameToCertificate: cfg.NameToCertificate, - GetCertificate: cfg.GetCertificate, - RootCAs: cfg.RootCAs, - NextProtos: cfg.NextProtos, - ServerName: cfg.ServerName, - ClientAuth: cfg.ClientAuth, - ClientCAs: cfg.ClientCAs, - InsecureSkipVerify: cfg.InsecureSkipVerify, - CipherSuites: cfg.CipherSuites, - PreferServerCipherSuites: cfg.PreferServerCipherSuites, - ClientSessionCache: cfg.ClientSessionCache, - MinVersion: cfg.MinVersion, - MaxVersion: cfg.MaxVersion, - CurvePreferences: cfg.CurvePreferences, - DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, - Renegotiation: cfg.Renegotiation, - } -} diff --git a/vendor/github.com/gomodule/redigo/redis/go18.go b/vendor/github.com/gomodule/redigo/redis/go18.go deleted file mode 100644 index 558363b..0000000 --- a/vendor/github.com/gomodule/redigo/redis/go18.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build go1.8 - -package redis - -import "crypto/tls" - -func cloneTLSConfig(cfg *tls.Config) *tls.Config { - return cfg.Clone() -} diff --git a/vendor/github.com/gomodule/redigo/redis/log.go b/vendor/github.com/gomodule/redigo/redis/log.go index ef8cd7a..72e054f 100644 --- a/vendor/github.com/gomodule/redigo/redis/log.go +++ b/vendor/github.com/gomodule/redigo/redis/log.go @@ -16,6 +16,7 @@ package redis import ( "bytes" + "context" "fmt" "log" "time" @@ -121,6 +122,12 @@ func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, return reply, err } +func (c *loggingConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) { + reply, err := DoContext(c.Conn, ctx, commandName, args...) + c.print("DoContext", commandName, args, reply, err) + return reply, err +} + func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) c.print("DoWithTimeout", commandName, args, reply, err) @@ -139,6 +146,12 @@ func (c *loggingConn) Receive() (interface{}, error) { return reply, err } +func (c *loggingConn) ReceiveContext(ctx context.Context) (interface{}, error) { + reply, err := ReceiveContext(c.Conn, ctx) + c.print("ReceiveContext", "", nil, reply, err) + return reply, err +} + func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { reply, err := ReceiveWithTimeout(c.Conn, timeout) c.print("ReceiveWithTimeout", "", nil, reply, err) diff --git a/vendor/github.com/gomodule/redigo/redis/pool.go b/vendor/github.com/gomodule/redigo/redis/pool.go index c7a2f19..8e22f66 100644 --- a/vendor/github.com/gomodule/redigo/redis/pool.go +++ b/vendor/github.com/gomodule/redigo/redis/pool.go @@ -128,16 +128,24 @@ type Pool struct { // DialContext is an application supplied function for creating and configuring a // connection with the given context. // - // The connection returned from Dial must not be in a special state + // The connection returned from DialContext must not be in a special state // (subscribed to pubsub channel, transaction started, ...). DialContext func(ctx context.Context) (Conn, error) // TestOnBorrow is an optional application supplied function for checking // the health of an idle connection before the connection is used again by - // the application. Argument t is the time that the connection was returned + // the application. Argument lastUsed is the time when the connection was returned + // to the pool. If the function returns an error, then the connection is + // closed. + TestOnBorrow func(c Conn, lastUsed time.Time) error + + // TestOnBorrowContext is an optional application supplied function + // for checking the health of an idle connection with the given context + // before the connection is used again by the application. + // Argument lastUsed is the time when the connection was returned // to the pool. If the function returns an error, then the connection is // closed. - TestOnBorrow func(c Conn, t time.Time) error + TestOnBorrowContext func(ctx context.Context, c Conn, lastUsed time.Time) error // Maximum number of idle connections in the pool. MaxIdle int @@ -228,6 +236,7 @@ func (p *Pool) GetContext(ctx context.Context) (Conn, error) { p.idle.popFront() p.mu.Unlock() if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && + (p.TestOnBorrowContext == nil || p.TestOnBorrowContext(ctx, pc.c, pc.t) == nil) && (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { return &activeConn{p: p, pc: pc}, nil } @@ -512,6 +521,20 @@ func (ac *activeConn) Err() error { return pc.c.Err() } +func (ac *activeConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithContext) + if !ok { + return nil, errContextNotSupported + } + ci := lookupCommandInfo(commandName) + ac.state = (ac.state | ci.Set) &^ ci.Clear + return cwt.DoContext(ctx, commandName, args...) +} + func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { pc := ac.pc if pc == nil { @@ -562,6 +585,18 @@ func (ac *activeConn) Receive() (reply interface{}, err error) { return pc.c.Receive() } +func (ac *activeConn) ReceiveContext(ctx context.Context) (reply interface{}, err error) { + pc := ac.pc + if pc == nil { + return nil, errConnClosed + } + cwt, ok := pc.c.(ConnWithContext) + if !ok { + return nil, errContextNotSupported + } + return cwt.ReceiveContext(ctx) +} + func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { pc := ac.pc if pc == nil { @@ -577,6 +612,9 @@ func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface type errorConn struct{ err error } func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } +func (ec errorConn) DoContext(context.Context, string, ...interface{}) (interface{}, error) { + return nil, ec.err +} func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { return nil, ec.err } @@ -585,6 +623,7 @@ func (ec errorConn) Err() error { ret func (ec errorConn) Close() error { return nil } func (ec errorConn) Flush() error { return ec.err } func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } +func (ec errorConn) ReceiveContext(context.Context) (interface{}, error) { return nil, ec.err } func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } type idleList struct { diff --git a/vendor/github.com/gomodule/redigo/redis/pubsub.go b/vendor/github.com/gomodule/redigo/redis/pubsub.go index cc58575..67b885b 100644 --- a/vendor/github.com/gomodule/redigo/redis/pubsub.go +++ b/vendor/github.com/gomodule/redigo/redis/pubsub.go @@ -15,6 +15,7 @@ package redis import ( + "context" "errors" "time" ) @@ -116,6 +117,13 @@ func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) } +// ReceiveContext is like Receive, but it allows termination of the receive +// via a Context. If the call returns due to closure of the context's Done +// channel the underlying Conn will have been closed. +func (c PubSubConn) ReceiveContext(ctx context.Context) interface{} { + return c.receiveInternal(ReceiveContext(c.Conn, ctx)) +} + func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { reply, err := Values(replyArg, errArg) if err != nil { diff --git a/vendor/github.com/gomodule/redigo/redis/redis.go b/vendor/github.com/gomodule/redigo/redis/redis.go index e446487..e3a3968 100644 --- a/vendor/github.com/gomodule/redigo/redis/redis.go +++ b/vendor/github.com/gomodule/redigo/redis/redis.go @@ -15,6 +15,7 @@ package redis import ( + "context" "errors" "time" ) @@ -33,6 +34,7 @@ type Conn interface { Err() error // Do sends a command to the server and returns the received reply. + // This function will use the timeout which was set when the connection is created Do(commandName string, args ...interface{}) (reply interface{}, err error) // Send writes the command to the client's output buffer. @@ -82,17 +84,52 @@ type Scanner interface { type ConnWithTimeout interface { Conn - // Do sends a command to the server and returns the received reply. - // The timeout overrides the read timeout set when dialing the - // connection. + // DoWithTimeout sends a command to the server and returns the received reply. + // The timeout overrides the readtimeout set when dialing the connection. DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) - // Receive receives a single reply from the Redis server. The timeout - // overrides the read timeout set when dialing the connection. + // ReceiveWithTimeout receives a single reply from the Redis server. + // The timeout overrides the readtimeout set when dialing the connection. ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) } +// ConnWithContext is an optional interface that allows the caller to control the command's life with context. +type ConnWithContext interface { + Conn + + // DoContext sends a command to server and returns the received reply. + // min(ctx,DialReadTimeout()) will be used as the deadline. + // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. + // DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). + // ctx timeout return err context.DeadlineExceeded. + // ctx canceled return err context.Canceled. + DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) + + // ReceiveContext receives a single reply from the Redis server. + // min(ctx,DialReadTimeout()) will be used as the deadline. + // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. + // DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). + // ctx timeout return err context.DeadlineExceeded. + // ctx canceled return err context.Canceled. + ReceiveContext(ctx context.Context) (reply interface{}, err error) +} + var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") +var errContextNotSupported = errors.New("redis: connection does not support ConnWithContext") + +// DoContext sends a command to server and returns the received reply. +// min(ctx,DialReadTimeout()) will be used as the deadline. +// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. +// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). +// ctx timeout return err context.DeadlineExceeded. +// ctx canceled return err context.Canceled. +func DoContext(c Conn, ctx context.Context, cmd string, args ...interface{}) (interface{}, error) { + cwt, ok := c.(ConnWithContext) + if !ok { + return nil, errContextNotSupported + } + return cwt.DoContext(ctx, cmd, args...) +} // DoWithTimeout executes a Redis command with the specified read timeout. If // the connection does not satisfy the ConnWithTimeout interface, then an error @@ -105,6 +142,20 @@ func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{ return cwt.DoWithTimeout(timeout, cmd, args...) } +// ReceiveContext receives a single reply from the Redis server. +// min(ctx,DialReadTimeout()) will be used as the deadline. +// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. +// DialReadTimeout() timeout return err can be checked by strings.Contains(e.Error(), "io/timeout"). +// ctx timeout return err context.DeadlineExceeded. +// ctx canceled return err context.Canceled. +func ReceiveContext(c Conn, ctx context.Context) (interface{}, error) { + cwt, ok := c.(ConnWithContext) + if !ok { + return nil, errContextNotSupported + } + return cwt.ReceiveContext(ctx) +} + // ReceiveWithTimeout receives a reply with the specified read timeout. If the // connection does not satisfy the ConnWithTimeout interface, then an error is // returned. @@ -136,3 +187,27 @@ type SlowLog struct { // ClientName is the name set via the CLIENT SETNAME command (4.0 only). ClientName string } + +// Latency represents a redis LATENCY LATEST. +type Latency struct { + // Name of the latest latency spike event. + Name string + + // Time of the latest latency spike for the event. + Time time.Time + + // Latest is the latest recorded latency for the named event. + Latest time.Duration + + // Max is the maximum latency for the named event. + Max time.Duration +} + +// LatencyHistory represents a redis LATENCY HISTORY. +type LatencyHistory struct { + // Time is the unix timestamp at which the event was processed. + Time time.Time + + // ExecutationTime is the amount of time needed for the command execution. + ExecutionTime time.Duration +} diff --git a/vendor/github.com/gomodule/redigo/redis/reflect.go b/vendor/github.com/gomodule/redigo/redis/reflect.go new file mode 100644 index 0000000..e135aed --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/reflect.go @@ -0,0 +1,48 @@ +package redis + +import ( + "reflect" + "runtime" +) + +// methodName returns the name of the calling method, +// assumed to be two stack frames above. +func methodName() string { + pc, _, _, _ := runtime.Caller(2) + f := runtime.FuncForPC(pc) + if f == nil { + return "unknown method" + } + return f.Name() +} + +// mustBe panics if f's kind is not expected. +func mustBe(v reflect.Value, expected reflect.Kind) { + if v.Kind() != expected { + panic(&reflect.ValueError{Method: methodName(), Kind: v.Kind()}) + } +} + +// fieldByIndexCreate returns the nested field corresponding +// to index creating elements that are nil when stepping through. +// It panics if v is not a struct. +func fieldByIndexCreate(v reflect.Value, index []int) reflect.Value { + if len(index) == 1 { + return v.Field(index[0]) + } + + mustBe(v, reflect.Struct) + for i, x := range index { + if i > 0 { + if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + v = v.Field(x) + } + + return v +} diff --git a/vendor/github.com/gomodule/redigo/redis/reflect_go117.go b/vendor/github.com/gomodule/redigo/redis/reflect_go117.go new file mode 100644 index 0000000..e985192 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/reflect_go117.go @@ -0,0 +1,34 @@ +//go:build !go1.18 +// +build !go1.18 + +package redis + +import ( + "errors" + "reflect" +) + +// fieldByIndexErr returns the nested field corresponding to index. +// It returns an error if evaluation requires stepping through a nil +// pointer, but panics if it must step through a field that +// is not a struct. +func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) { + if len(index) == 1 { + return v.Field(index[0]), nil + } + + mustBe(v, reflect.Struct) + for i, x := range index { + if i > 0 { + if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { + if v.IsNil() { + return reflect.Value{}, errors.New("reflect: indirection through nil pointer to embedded struct field " + v.Type().Elem().Name()) + } + v = v.Elem() + } + } + v = v.Field(x) + } + + return v, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/reflect_go118.go b/vendor/github.com/gomodule/redigo/redis/reflect_go118.go new file mode 100644 index 0000000..3356e76 --- /dev/null +++ b/vendor/github.com/gomodule/redigo/redis/reflect_go118.go @@ -0,0 +1,16 @@ +//go:build go1.18 +// +build go1.18 + +package redis + +import ( + "reflect" +) + +// fieldByIndexErr returns the nested field corresponding to index. +// It returns an error if evaluation requires stepping through a nil +// pointer, but panics if it must step through a field that +// is not a struct. +func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) { + return v.FieldByIndexErr(index) +} diff --git a/vendor/github.com/gomodule/redigo/redis/reply.go b/vendor/github.com/gomodule/redigo/redis/reply.go index dfe6aff..aabf598 100644 --- a/vendor/github.com/gomodule/redigo/redis/reply.go +++ b/vendor/github.com/gomodule/redigo/redis/reply.go @@ -277,13 +277,16 @@ func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), func Float64s(reply interface{}, err error) ([]float64, error) { var result []float64 err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { - p, ok := v.([]byte) - if !ok { - return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) + switch v := v.(type) { + case []byte: + f, err := strconv.ParseFloat(string(v), 64) + result[i] = f + return err + case Error: + return v + default: + return fmt.Errorf("redigo: unexpected element type for Float64s, got type %T", v) } - f, err := strconv.ParseFloat(string(p), 64) - result[i] = f - return err }) return result, err } @@ -302,6 +305,8 @@ func Strings(reply interface{}, err error) ([]string, error) { case []byte: result[i] = string(v) return nil + case Error: + return v default: return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) } @@ -316,12 +321,15 @@ func Strings(reply interface{}, err error) ([]string, error) { func ByteSlices(reply interface{}, err error) ([][]byte, error) { var result [][]byte err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { - p, ok := v.([]byte) - if !ok { + switch v := v.(type) { + case []byte: + result[i] = v + return nil + case Error: + return v + default: return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) } - result[i] = p - return nil }) return result, err } @@ -341,6 +349,8 @@ func Int64s(reply interface{}, err error) ([]int64, error) { n, err := strconv.ParseInt(string(v), 10, 64) result[i] = n return err + case Error: + return v default: return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) } @@ -367,6 +377,8 @@ func Ints(reply interface{}, err error) ([]int, error) { n, err := strconv.Atoi(string(v)) result[i] = n return err + case Error: + return v default: return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) } @@ -374,79 +386,122 @@ func Ints(reply interface{}, err error) ([]int, error) { return result, err } -// StringMap is a helper that converts an array of strings (alternating key, value) -// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. -// Requires an even number of values in result. -func StringMap(result interface{}, err error) (map[string]string, error) { - values, err := Values(result, err) +// mapHelper builds a map from the data in reply. +func mapHelper(reply interface{}, err error, name string, makeMap func(int), assign func(key string, value interface{}) error) error { + values, err := Values(reply, err) if err != nil { - return nil, err + return err } + if len(values)%2 != 0 { - return nil, errors.New("redigo: StringMap expects even number of values result") + return fmt.Errorf("redigo: %s expects even number of values result, got %d", name, len(values)) } - m := make(map[string]string, len(values)/2) + + makeMap(len(values) / 2) for i := 0; i < len(values); i += 2 { - key, okKey := values[i].([]byte) - value, okValue := values[i+1].([]byte) - if !okKey || !okValue { - return nil, errors.New("redigo: StringMap key not a bulk string value") + key, ok := values[i].([]byte) + if !ok { + return fmt.Errorf("redigo: %s key[%d] not a bulk string value, got %T", name, i, values[i]) + } + + if err := assign(string(key), values[i+1]); err != nil { + return err } - m[string(key)] = string(value) } - return m, nil + + return nil +} + +// StringMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. +// Requires an even number of values in result. +func StringMap(reply interface{}, err error) (map[string]string, error) { + var result map[string]string + err = mapHelper(reply, err, "StringMap", + func(n int) { + result = make(map[string]string, n) + }, func(key string, v interface{}) error { + value, ok := v.([]byte) + if !ok { + return fmt.Errorf("redigo: StringMap for %q not a bulk string value, got %T", key, v) + } + + result[key] = string(value) + + return nil + }, + ) + + return result, err } // IntMap is a helper that converts an array of strings (alternating key, value) // into a map[string]int. The HGETALL commands return replies in this format. // Requires an even number of values in result. func IntMap(result interface{}, err error) (map[string]int, error) { - values, err := Values(result, err) - if err != nil { - return nil, err - } - if len(values)%2 != 0 { - return nil, errors.New("redigo: IntMap expects even number of values result") - } - m := make(map[string]int, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].([]byte) - if !ok { - return nil, errors.New("redigo: IntMap key not a bulk string value") - } - value, err := Int(values[i+1], nil) - if err != nil { - return nil, err - } - m[string(key)] = value - } - return m, nil + var m map[string]int + err = mapHelper(result, err, "IntMap", + func(n int) { + m = make(map[string]int, n) + }, func(key string, v interface{}) error { + value, err := Int(v, nil) + if err != nil { + return err + } + + m[key] = value + + return nil + }, + ) + + return m, err } // Int64Map is a helper that converts an array of strings (alternating key, value) // into a map[string]int64. The HGETALL commands return replies in this format. // Requires an even number of values in result. func Int64Map(result interface{}, err error) (map[string]int64, error) { - values, err := Values(result, err) - if err != nil { - return nil, err - } - if len(values)%2 != 0 { - return nil, errors.New("redigo: Int64Map expects even number of values result") - } - m := make(map[string]int64, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].([]byte) - if !ok { - return nil, errors.New("redigo: Int64Map key not a bulk string value") - } - value, err := Int64(values[i+1], nil) - if err != nil { - return nil, err - } - m[string(key)] = value - } - return m, nil + var m map[string]int64 + err = mapHelper(result, err, "Int64Map", + func(n int) { + m = make(map[string]int64, n) + }, func(key string, v interface{}) error { + value, err := Int64(v, nil) + if err != nil { + return err + } + + m[key] = value + + return nil + }, + ) + + return m, err +} + +// Float64Map is a helper that converts an array of strings (alternating key, value) +// into a map[string]float64. The HGETALL commands return replies in this format. +// Requires an even number of values in result. +func Float64Map(result interface{}, err error) (map[string]float64, error) { + var m map[string]float64 + err = mapHelper(result, err, "Float64Map", + func(n int) { + m = make(map[string]float64, n) + }, func(key string, v interface{}) error { + value, err := Float64(v, nil) + if err != nil { + return err + } + + m[key] = value + + return nil + }, + ) + + return m, err } // Positions is a helper that converts an array of positions (lat, long) @@ -461,21 +516,26 @@ func Positions(result interface{}, err error) ([]*[2]float64, error) { if values[i] == nil { continue } + p, ok := values[i].([]interface{}) if !ok { return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) } + if len(p) != 2 { return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) } + lat, err := Float64(p[0], nil) if err != nil { return nil, err } + long, err := Float64(p[1], nil) if err != nil { return nil, err } + positions[i] = &[2]float64{lat, long} } return positions, nil @@ -496,6 +556,8 @@ func Uint64s(reply interface{}, err error) ([]uint64, error) { n, err := strconv.ParseUint(string(v), 10, 64) result[i] = n return err + case Error: + return v default: return fmt.Errorf("redigo: unexpected element type for Uint64s, got type %T", v) } @@ -507,26 +569,23 @@ func Uint64s(reply interface{}, err error) ([]uint64, error) { // into a map[string]uint64. The HGETALL commands return replies in this format. // Requires an even number of values in result. func Uint64Map(result interface{}, err error) (map[string]uint64, error) { - values, err := Values(result, err) - if err != nil { - return nil, err - } - if len(values)%2 != 0 { - return nil, errors.New("redigo: Uint64Map expects even number of values result") - } - m := make(map[string]uint64, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].([]byte) - if !ok { - return nil, errors.New("redigo: Uint64Map key not a bulk string value") - } - value, err := Uint64(values[i+1], nil) - if err != nil { - return nil, err - } - m[string(key)] = value - } - return m, nil + var m map[string]uint64 + err = mapHelper(result, err, "Uint64Map", + func(n int) { + m = make(map[string]uint64, n) + }, func(key string, v interface{}) error { + value, err := Uint64(v, nil) + if err != nil { + return err + } + + m[key] = value + + return nil + }, + ) + + return m, err } // SlowLogs is a helper that parse the SLOWLOG GET command output and @@ -537,47 +596,140 @@ func SlowLogs(result interface{}, err error) ([]SlowLog, error) { return nil, err } logs := make([]SlowLog, len(rawLogs)) - for i, rawLog := range rawLogs { - rawLog, ok := rawLog.([]interface{}) + for i, e := range rawLogs { + rawLog, ok := e.([]interface{}) if !ok { - return nil, errors.New("redigo: slowlog element is not an array") + return nil, fmt.Errorf("redigo: slowlog element is not an array, got %T", e) } var log SlowLog - if len(rawLog) < 4 { - return nil, errors.New("redigo: slowlog element has less than four elements") + return nil, fmt.Errorf("redigo: slowlog element has %d elements, expected at least 4", len(rawLog)) } + log.ID, ok = rawLog[0].(int64) if !ok { - return nil, errors.New("redigo: slowlog element[0] not an int64") + return nil, fmt.Errorf("redigo: slowlog element[0] not an int64, got %T", rawLog[0]) } + timestamp, ok := rawLog[1].(int64) if !ok { - return nil, errors.New("redigo: slowlog element[1] not an int64") + return nil, fmt.Errorf("redigo: slowlog element[1] not an int64, got %T", rawLog[1]) } + log.Time = time.Unix(timestamp, 0) duration, ok := rawLog[2].(int64) if !ok { - return nil, errors.New("redigo: slowlog element[2] not an int64") + return nil, fmt.Errorf("redigo: slowlog element[2] not an int64, got %T", rawLog[2]) } + log.ExecutionTime = time.Duration(duration) * time.Microsecond log.Args, err = Strings(rawLog[3], nil) if err != nil { - return nil, fmt.Errorf("redigo: slowlog element[3] is not array of string. actual error is : %s", err.Error()) + return nil, fmt.Errorf("redigo: slowlog element[3] is not array of strings: %w", err) } + if len(rawLog) >= 6 { log.ClientAddr, err = String(rawLog[4], nil) if err != nil { - return nil, fmt.Errorf("redigo: slowlog element[4] is not a string. actual error is : %s", err.Error()) + return nil, fmt.Errorf("redigo: slowlog element[4] is not a string: %w", err) } + log.ClientName, err = String(rawLog[5], nil) if err != nil { - return nil, fmt.Errorf("redigo: slowlog element[5] is not a string. actual error is : %s", err.Error()) + return nil, fmt.Errorf("redigo: slowlog element[5] is not a string: %w", err) } } logs[i] = log } return logs, nil } + +// Latencies is a helper that parses the LATENCY LATEST command output and +// return the slice of Latency values. +func Latencies(result interface{}, err error) ([]Latency, error) { + rawLatencies, err := Values(result, err) + if err != nil { + return nil, err + } + + latencies := make([]Latency, len(rawLatencies)) + for i, e := range rawLatencies { + rawLatency, ok := e.([]interface{}) + if !ok { + return nil, fmt.Errorf("redigo: latencies element is not slice, got %T", e) + } + + var event Latency + if len(rawLatency) != 4 { + return nil, fmt.Errorf("redigo: latencies element has %d elements, expected 4", len(rawLatency)) + } + + event.Name, err = String(rawLatency[0], nil) + if err != nil { + return nil, fmt.Errorf("redigo: latencies element[0] is not a string: %w", err) + } + + timestamp, ok := rawLatency[1].(int64) + if !ok { + return nil, fmt.Errorf("redigo: latencies element[1] not an int64, got %T", rawLatency[1]) + } + + event.Time = time.Unix(timestamp, 0) + + latestDuration, ok := rawLatency[2].(int64) + if !ok { + return nil, fmt.Errorf("redigo: latencies element[2] not an int64, got %T", rawLatency[2]) + } + + event.Latest = time.Duration(latestDuration) * time.Millisecond + + maxDuration, ok := rawLatency[3].(int64) + if !ok { + return nil, fmt.Errorf("redigo: latencies element[3] not an int64, got %T", rawLatency[3]) + } + + event.Max = time.Duration(maxDuration) * time.Millisecond + + latencies[i] = event + } + + return latencies, nil +} + +// LatencyHistories is a helper that parse the LATENCY HISTORY command output and +// returns a LatencyHistory slice. +func LatencyHistories(result interface{}, err error) ([]LatencyHistory, error) { + rawLogs, err := Values(result, err) + if err != nil { + return nil, err + } + + latencyHistories := make([]LatencyHistory, len(rawLogs)) + for i, e := range rawLogs { + rawLog, ok := e.([]interface{}) + if !ok { + return nil, fmt.Errorf("redigo: latency history element is not an slice, got %T", e) + } + + var event LatencyHistory + timestamp, ok := rawLog[0].(int64) + if !ok { + return nil, fmt.Errorf("redigo: latency history element[0] not an int64, got %T", rawLog[0]) + } + + event.Time = time.Unix(timestamp, 0) + + duration, ok := rawLog[1].(int64) + if !ok { + return nil, fmt.Errorf("redigo: latency history element[1] not an int64, got %T", rawLog[1]) + } + + event.ExecutionTime = time.Duration(duration) * time.Millisecond + + latencyHistories[i] = event + } + + return latencyHistories, nil +} diff --git a/vendor/github.com/gomodule/redigo/redis/scan.go b/vendor/github.com/gomodule/redigo/redis/scan.go index 379206e..8212101 100644 --- a/vendor/github.com/gomodule/redigo/redis/scan.go +++ b/vendor/github.com/gomodule/redigo/redis/scan.go @@ -355,7 +355,13 @@ func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { return ss.m[string(name)] } -func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { +func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec, seen map[reflect.Type]struct{}) error { + if _, ok := seen[t]; ok { + // Protect against infinite recursion. + return fmt.Errorf("recursive struct definition for %v", t) + } + + seen[t] = struct{}{} LOOP: for i := 0; i < t.NumField(); i++ { f := t.Field(i) @@ -365,20 +371,21 @@ LOOP: case f.Anonymous: switch f.Type.Kind() { case reflect.Struct: - compileStructSpec(f.Type, depth, append(index, i), ss) + if err := compileStructSpec(f.Type, depth, append(index, i), ss, seen); err != nil { + return err + } case reflect.Ptr: - // TODO(steve): Protect against infinite recursion. if f.Type.Elem().Kind() == reflect.Struct { - compileStructSpec(f.Type.Elem(), depth, append(index, i), ss) + if err := compileStructSpec(f.Type.Elem(), depth, append(index, i), ss, seen); err != nil { + return err + } } } default: fs := &fieldSpec{name: f.Name} tag := f.Tag.Get("redis") - var ( - p string - ) + var p string first := true for len(tag) > 0 { i := strings.IndexByte(tag, ',') @@ -402,10 +409,12 @@ LOOP: } } } + d, found := depth[fs.name] if !found { d = 1 << 30 } + switch { case len(index) == d: // At same depth, remove from result. @@ -428,6 +437,8 @@ LOOP: } } } + + return nil } var ( @@ -435,26 +446,27 @@ var ( structSpecCache = make(map[reflect.Type]*structSpec) ) -func structSpecForType(t reflect.Type) *structSpec { - +func structSpecForType(t reflect.Type) (*structSpec, error) { structSpecMutex.RLock() ss, found := structSpecCache[t] structSpecMutex.RUnlock() if found { - return ss + return ss, nil } structSpecMutex.Lock() defer structSpecMutex.Unlock() ss, found = structSpecCache[t] if found { - return ss + return ss, nil } ss = &structSpec{m: make(map[string]*fieldSpec)} - compileStructSpec(t, make(map[string]int), nil, ss) + if err := compileStructSpec(t, make(map[string]int), nil, ss, make(map[reflect.Type]struct{})); err != nil { + return nil, fmt.Errorf("compile struct: %s: %w", t, err) + } structSpecCache[t] = ss - return ss + return ss, nil } var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") @@ -480,30 +492,38 @@ func ScanStruct(src []interface{}, dest interface{}) error { if d.Kind() != reflect.Ptr || d.IsNil() { return errScanStructValue } + d = d.Elem() if d.Kind() != reflect.Struct { return errScanStructValue } - ss := structSpecForType(d.Type()) if len(src)%2 != 0 { return errors.New("redigo.ScanStruct: number of values not a multiple of 2") } + ss, err := structSpecForType(d.Type()) + if err != nil { + return fmt.Errorf("redigo.ScanStruct: %w", err) + } + for i := 0; i < len(src); i += 2 { s := src[i+1] if s == nil { continue } + name, ok := src[i].([]byte) if !ok { return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) } + fs := ss.fieldSpec(name) if fs == nil { continue } - if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + + if err := convertAssignValue(fieldByIndexCreate(d, fs.index), s); err != nil { return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) } } @@ -555,7 +575,11 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error return nil } - ss := structSpecForType(t) + ss, err := structSpecForType(t) + if err != nil { + return fmt.Errorf("redigo.ScanSlice: %w", err) + } + fss := ss.l if len(fieldNames) > 0 { fss = make([]*fieldSpec, len(fieldNames)) @@ -618,6 +642,7 @@ func (args Args) Add(value ...interface{}) Args { // for more information on the use of the 'redis' field tag. // // Other types are appended to args as is. +// panics if v includes a recursive anonymous struct. func (args Args) AddFlat(v interface{}) Args { rv := reflect.ValueOf(v) switch rv.Kind() { @@ -646,9 +671,17 @@ func (args Args) AddFlat(v interface{}) Args { } func flattenStruct(args Args, v reflect.Value) Args { - ss := structSpecForType(v.Type()) + ss, err := structSpecForType(v.Type()) + if err != nil { + panic(fmt.Errorf("redigo.AddFlat: %w", err)) + } + for _, fs := range ss.l { - fv := v.FieldByIndex(fs.index) + fv, err := fieldByIndexErr(v, fs.index) + if err != nil { + // Nil item ignore. + continue + } if fs.omitEmpty { var empty = false switch fv.Kind() { diff --git a/vendor/github.com/gomodule/redigo/redis/script.go b/vendor/github.com/gomodule/redigo/redis/script.go index d0cec1e..c585518 100644 --- a/vendor/github.com/gomodule/redigo/redis/script.go +++ b/vendor/github.com/gomodule/redigo/redis/script.go @@ -15,6 +15,7 @@ package redis import ( + "context" "crypto/sha1" "encoding/hex" "io" @@ -60,13 +61,25 @@ func (s *Script) Hash() string { return s.hash } +func (s *Script) DoContext(ctx context.Context, c Conn, keysAndArgs ...interface{}) (interface{}, error) { + cwt, ok := c.(ConnWithContext) + if !ok { + return nil, errContextNotSupported + } + v, err := cwt.DoContext(ctx, "EVALSHA", s.args(s.hash, keysAndArgs)...) + if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { + v, err = cwt.DoContext(ctx, "EVAL", s.args(s.src, keysAndArgs)...) + } + return v, err +} + // Do evaluates the script. Under the covers, Do optimistically evaluates the // script using the EVALSHA command. If the command fails because the script is // not loaded, then Do evaluates the script using the EVAL command (thus // causing the script to load). func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) - if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { + if err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) } return v, err diff --git a/vendor/modules.txt b/vendor/modules.txt index 5e7490a..af3ac5a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -3,7 +3,7 @@ github.com/bradfitz/gomemcache/memcache # github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db github.com/golang/snappy -# github.com/gomodule/redigo v1.8.4 +# github.com/gomodule/redigo v1.9.2 ## explicit github.com/gomodule/redigo/redis # github.com/google/btree v1.0.0 @@ -30,3 +30,5 @@ github.com/syndtr/goleveldb/leveldb/opt github.com/syndtr/goleveldb/leveldb/storage github.com/syndtr/goleveldb/leveldb/table github.com/syndtr/goleveldb/leveldb/util +# gopkg.in/yaml.v2 v2.2.2 +## explicit