Merge distance updates

This commit is contained in:
tidwall 2021-07-08 07:03:36 -07:00
parent aea7d77de5
commit e60cbac7cf
40 changed files with 611 additions and 299 deletions

2
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/aws/aws-sdk-go v1.37.3 github.com/aws/aws-sdk-go v1.37.3
github.com/eclipse/paho.mqtt.golang v1.3.1 github.com/eclipse/paho.mqtt.golang v1.3.1
github.com/golang/protobuf v1.4.3 github.com/golang/protobuf v1.4.3
github.com/gomodule/redigo v2.0.1-0.20181026001555-e8fc0692a7e2+incompatible github.com/gomodule/redigo v1.8.3
github.com/mmcloughlin/geohash v0.10.0 github.com/mmcloughlin/geohash v0.10.0
github.com/nats-io/nats-server/v2 v2.1.9 // indirect github.com/nats-io/nats-server/v2 v2.1.9 // indirect
github.com/nats-io/nats.go v1.10.0 github.com/nats-io/nats.go v1.10.0

4
go.sum
View File

@ -156,8 +156,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.1-0.20181026001555-e8fc0692a7e2+incompatible h1:H4S5GVLXZxCnS6q3+HrRBu/ObgobnAHg92tWG8cLfX8= github.com/gomodule/redigo v1.8.3 h1:HR0kYDX2RJZvAup8CsiJwxB4dTCSC0AaUq6S4SiLwUc=
github.com/gomodule/redigo v2.0.1-0.20181026001555-e8fc0692a7e2+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.8.3/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=

View File

@ -164,12 +164,9 @@ func fenceMatch(
break break
} }
sw.mu.Lock() sw.mu.Lock()
var meters float64 var distance float64
distance := Distance{false, meters}
if fence.distance && fence.obj != nil { if fence.distance && fence.obj != nil {
meters = details.obj.Distance(fence.obj) distance = details.obj.Distance(fence.obj)
distance.ready = true
distance.meters = meters
} }
sw.fmap = details.fmap sw.fmap = details.fmap
sw.fullFields = true sw.fullFields = true

View File

@ -60,19 +60,13 @@ type scanWriter struct {
respOut resp.Value respOut resp.Value
} }
// Distance ...
type Distance struct {
ready bool
meters float64
}
// ScanWriterParams ... // ScanWriterParams ...
type ScanWriterParams struct { type ScanWriterParams struct {
id string id string
o geojson.Object o geojson.Object
fields []float64 fields []float64
distance Distance distance float64
distOutput bool distOutput bool // query or fence requested distance output
noLock bool noLock bool
ignoreGlobMatch bool ignoreGlobMatch bool
clip geojson.Object clip geojson.Object
@ -440,8 +434,8 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
wr.WriteString(jsfields) wr.WriteString(jsfields)
if opts.distance.ready { if opts.distOutput || opts.distance > 0 {
wr.WriteString(`,"distance":` + strconv.FormatFloat(opts.distance.meters, 'f', -1, 64)) wr.WriteString(`,"distance":` + strconv.FormatFloat(opts.distance, 'f', -1, 64))
} }
wr.WriteString(`}`) wr.WriteString(`}`)
@ -503,8 +497,8 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
vals = append(vals, resp.ArrayValue(fvals)) vals = append(vals, resp.ArrayValue(fvals))
} }
} }
if opts.distance.ready { if opts.distOutput || opts.distance > 0 {
vals = append(vals, resp.FloatValue(opts.distance.meters)) vals = append(vals, resp.FloatValue(opts.distance))
} }
sw.values = append(sw.values, resp.ArrayValue(vals)) sw.values = append(sw.values, resp.ArrayValue(vals))

View File

@ -372,18 +372,15 @@ func (server *Server) cmdNearby(msg *Message) (res resp.Value, err error) {
if sw.col != nil { if sw.col != nil {
iter := func(id string, o geojson.Object, fields []float64, dist float64) bool { iter := func(id string, o geojson.Object, fields []float64, dist float64) bool {
meters := 0.0 meters := 0.0
distance := Distance{false, meters}
if s.distance { if s.distance {
meters = geo.DistanceFromHaversine(dist) meters = geo.DistanceFromHaversine(dist)
distance.ready = true
distance.meters = meters
} }
return sw.writeObject(ScanWriterParams{ return sw.writeObject(ScanWriterParams{
id: id, id: id,
o: o, o: o,
fields: fields, fields: fields,
distance: distance, distance: meters,
distOutput: s.distance,
noLock: true, noLock: true,
ignoreGlobMatch: true, ignoreGlobMatch: true,
skipTesting: true, skipTesting: true,

View File

@ -173,3 +173,5 @@
defend, and hold each Contributor harmless for any liability defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability. of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -17,6 +17,7 @@ package redis
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -74,15 +75,27 @@ type DialOption struct {
} }
type dialOptions struct { type dialOptions struct {
readTimeout time.Duration readTimeout time.Duration
writeTimeout time.Duration writeTimeout time.Duration
dialer *net.Dialer tlsHandshakeTimeout time.Duration
dial func(network, addr string) (net.Conn, error) dialer *net.Dialer
db int dialContext func(ctx context.Context, network, addr string) (net.Conn, error)
password string db int
useTLS bool username string
skipVerify bool password string
tlsConfig *tls.Config clientName string
useTLS bool
skipVerify bool
tlsConfig *tls.Config
}
// DialTLSHandshakeTimeout specifies the maximum amount of time waiting to
// wait for a TLS handshake. Zero means no timeout.
// If no DialTLSHandshakeTimeout option is specified then the default is 30 seconds.
func DialTLSHandshakeTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
do.tlsHandshakeTimeout = d
}}
} }
// DialReadTimeout specifies the timeout for reading a single command reply. // DialReadTimeout specifies the timeout for reading a single command reply.
@ -101,6 +114,7 @@ func DialWriteTimeout(d time.Duration) DialOption {
// DialConnectTimeout specifies the timeout for connecting to the Redis server when // DialConnectTimeout specifies the timeout for connecting to the Redis server when
// no DialNetDial option is specified. // no DialNetDial option is specified.
// If no DialConnectTimeout option is specified then the default is 30 seconds.
func DialConnectTimeout(d time.Duration) DialOption { func DialConnectTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) { return DialOption{func(do *dialOptions) {
do.dialer.Timeout = d do.dialer.Timeout = d
@ -122,7 +136,18 @@ func DialKeepAlive(d time.Duration) DialOption {
// DialNetDial overrides DialConnectTimeout and DialKeepAlive. // DialNetDial overrides DialConnectTimeout and DialKeepAlive.
func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
return DialOption{func(do *dialOptions) { return DialOption{func(do *dialOptions) {
do.dial = dial do.dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dial(network, addr)
}
}}
}
// DialContextFunc specifies a custom dial function with context for creating TCP
// connections, otherwise a net.Dialer customized via the other options is used.
// DialContextFunc overrides DialConnectTimeout and DialKeepAlive.
func DialContextFunc(f func(ctx context.Context, network, addr string) (net.Conn, error)) DialOption {
return DialOption{func(do *dialOptions) {
do.dialContext = f
}} }}
} }
@ -141,6 +166,22 @@ func DialPassword(password string) DialOption {
}} }}
} }
// DialUsername specifies the username to use when connecting to
// the Redis server when Redis ACLs are used.
func DialUsername(username string) DialOption {
return DialOption{func(do *dialOptions) {
do.username = username
}}
}
// DialClientName specifies a client name to be used
// by the Redis server connection.
func DialClientName(name string) DialOption {
return DialOption{func(do *dialOptions) {
do.clientName = name
}}
}
// DialTLSConfig specifies the config to use when a TLS connection is dialed. // DialTLSConfig specifies the config to use when a TLS connection is dialed.
// Has no effect when not dialing a TLS connection. // Has no effect when not dialing a TLS connection.
func DialTLSConfig(c *tls.Config) DialOption { func DialTLSConfig(c *tls.Config) DialOption {
@ -168,19 +209,33 @@ func DialUseTLS(useTLS bool) DialOption {
// Dial connects to the Redis server at the given network and // Dial connects to the Redis server at the given network and
// address using the specified options. // address using the specified options.
func Dial(network, address string, options ...DialOption) (Conn, error) { func Dial(network, address string, options ...DialOption) (Conn, error) {
return DialContext(context.Background(), network, address, options...)
}
type tlsHandshakeTimeoutError struct{}
func (tlsHandshakeTimeoutError) Timeout() bool { return true }
func (tlsHandshakeTimeoutError) Temporary() bool { return true }
func (tlsHandshakeTimeoutError) Error() string { return "TLS handshake timeout" }
// DialContext connects to the Redis server at the given network and
// address using the specified options and context.
func DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{ do := dialOptions{
dialer: &net.Dialer{ dialer: &net.Dialer{
Timeout: time.Second * 30,
KeepAlive: time.Minute * 5, KeepAlive: time.Minute * 5,
}, },
tlsHandshakeTimeout: time.Second * 10,
} }
for _, option := range options { for _, option := range options {
option.f(&do) option.f(&do)
} }
if do.dial == nil { if do.dialContext == nil {
do.dial = do.dialer.Dial do.dialContext = do.dialer.DialContext
} }
netConn, err := do.dial(network, address) netConn, err := do.dialContext(ctx, network, address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -202,10 +257,22 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
} }
tlsConn := tls.Client(netConn, tlsConfig) tlsConn := tls.Client(netConn, tlsConfig)
if err := tlsConn.Handshake(); err != nil { errc := make(chan error, 2) // buffered so we don't block timeout or Handshake
netConn.Close() if d := do.tlsHandshakeTimeout; d != 0 {
timer := time.AfterFunc(d, func() {
errc <- tlsHandshakeTimeoutError{}
})
defer timer.Stop()
}
go func() {
errc <- tlsConn.Handshake()
}()
if err := <-errc; err != nil {
// Timeout or Handshake error.
netConn.Close() // nolint: errcheck
return nil, err return nil, err
} }
netConn = tlsConn netConn = tlsConn
} }
@ -218,7 +285,19 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
} }
if do.password != "" { if do.password != "" {
if _, err := c.Do("AUTH", do.password); err != nil { authArgs := make([]interface{}, 0, 2)
if do.username != "" {
authArgs = append(authArgs, do.username)
}
authArgs = append(authArgs, do.password)
if _, err := c.Do("AUTH", authArgs...); err != nil {
netConn.Close()
return nil, err
}
}
if do.clientName != "" {
if _, err := c.Do("CLIENT", "SETNAME", do.clientName); err != nil {
netConn.Close() netConn.Close()
return nil, err return nil, err
} }
@ -249,6 +328,10 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) {
return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
} }
if u.Opaque != "" {
return nil, fmt.Errorf("invalid redis URL, url is opaque: %s", rawurl)
}
// As per the IANA draft spec, the host defaults to localhost and // As per the IANA draft spec, the host defaults to localhost and
// the port defaults to 6379. // the port defaults to 6379.
host, port, err := net.SplitHostPort(u.Host) host, port, err := net.SplitHostPort(u.Host)
@ -265,7 +348,7 @@ func DialURL(rawurl string, options ...DialOption) (Conn, error) {
if u.User != nil { if u.User != nil {
password, isSet := u.User.Password() password, isSet := u.User.Password()
if isSet { if isSet {
options = append(options, DialPassword(password)) options = append(options, DialUsername(u.User.Username()), DialPassword(password))
} }
} }
@ -427,10 +510,21 @@ func (pe protocolError) Error() string {
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
} }
// readLine reads a line of input from the RESP stream.
func (c *conn) readLine() ([]byte, error) { func (c *conn) readLine() ([]byte, error) {
// To avoid allocations, attempt to read the line using ReadSlice. This
// call typically succeeds. The known case where the call fails is when
// reading the output from the MONITOR command.
p, err := c.br.ReadSlice('\n') p, err := c.br.ReadSlice('\n')
if err == bufio.ErrBufferFull { if err == bufio.ErrBufferFull {
return nil, protocolError("long response line") // The line does not fit in the bufio.Reader's buffer. Fall back to
// allocating a buffer for the line.
buf := append([]byte{}, p...)
for err == bufio.ErrBufferFull {
p, err = c.br.ReadSlice('\n')
buf = append(buf, p...)
}
p = buf
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -510,11 +604,11 @@ func (c *conn) readReply() (interface{}, error) {
} }
switch line[0] { switch line[0] {
case '+': case '+':
switch { switch string(line[1:]) {
case len(line) == 3 && line[1] == 'O' && line[2] == 'K': case "OK":
// Avoid allocation for frequent "+OK" response. // Avoid allocation for frequent "+OK" response.
return okReply, nil return okReply, nil
case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': case "PONG":
// Avoid allocation in PING command benchmarks :) // Avoid allocation in PING command benchmarks :)
return pongReply, nil return pongReply, nil
default: default:

View File

@ -101,7 +101,7 @@
// //
// Connections support one concurrent caller to the Receive method and one // Connections support one concurrent caller to the Receive method and one
// concurrent caller to the Send and Flush methods. No other concurrency is // concurrent caller to the Send and Flush methods. No other concurrency is
// supported including concurrent calls to the Do method. // supported including concurrent calls to the Do and Close methods.
// //
// For full concurrent access to Redis, use the thread-safe Pool to get, use // For full concurrent access to Redis, use the thread-safe Pool to get, use
// and release a connection from within a goroutine. Connections returned from // and release a connection from within a goroutine. Connections returned from

View File

@ -1,27 +0,0 @@
// +build !go1.7
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,
}
}

View File

@ -16,13 +16,13 @@ package redis
import ( import (
"bytes" "bytes"
"context"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1"
"errors" "errors"
"io" "io"
"strconv" "strconv"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
@ -56,6 +56,7 @@ var (
// return &redis.Pool{ // return &redis.Pool{
// MaxIdle: 3, // MaxIdle: 3,
// IdleTimeout: 240 * time.Second, // IdleTimeout: 240 * time.Second,
// // Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial.
// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, // Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
// } // }
// } // }
@ -125,6 +126,13 @@ type Pool struct {
// (subscribed to pubsub channel, transaction started, ...). // (subscribed to pubsub channel, transaction started, ...).
Dial func() (Conn, error) Dial func() (Conn, error)
// 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
// (subscribed to pubsub channel, transaction started, ...).
DialContext func(ctx context.Context) (Conn, error)
// TestOnBorrow is an optional application supplied function for checking // TestOnBorrow is an optional application supplied function for checking
// the health of an idle connection before the connection is used again by // 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 t is the time that the connection was returned
@ -152,18 +160,19 @@ type Pool struct {
// the pool does not close connections based on age. // the pool does not close connections based on age.
MaxConnLifetime time.Duration MaxConnLifetime time.Duration
chInitialized uint32 // set to 1 when field ch is initialized mu sync.Mutex // mu protects the following fields
closed bool // set to true when the pool is closed.
mu sync.Mutex // mu protects the following fields active int // the number of open connections in the pool
closed bool // set to true when the pool is closed. initOnce sync.Once // the init ch once func
active int // the number of open connections in the pool ch chan struct{} // limits open connections when p.Wait is true
ch chan struct{} // limits open connections when p.Wait is true idle idleList // idle connections
idle idleList // idle connections waitCount int64 // total number of connections waited for.
waitDuration time.Duration // total time waited for new connections.
} }
// NewPool creates a new pool. // NewPool creates a new pool.
// //
// Deprecated: Initialize the Pool directory as shown in the example. // Deprecated: Initialize the Pool directly as shown in the example.
func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
return &Pool{Dial: newFn, MaxIdle: maxIdle} return &Pool{Dial: newFn, MaxIdle: maxIdle}
} }
@ -174,11 +183,87 @@ func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
// getting an underlying connection, then the connection Err, Do, Send, Flush // getting an underlying connection, then the connection Err, Do, Send, Flush
// and Receive methods return that error. // and Receive methods return that error.
func (p *Pool) Get() Conn { func (p *Pool) Get() Conn {
pc, err := p.get(nil) // GetContext returns errorConn in the first argument when an error occurs.
c, _ := p.GetContext(context.Background())
return c
}
// GetContext gets a connection using the provided context.
//
// The provided Context must be non-nil. If the context expires before the
// connection is complete, an error is returned. Any expiration on the context
// will not affect the returned connection.
//
// If the function completes without error, then the application must close the
// returned connection.
func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
// Wait until there is a vacant connection in the pool.
waited, err := p.waitVacantConn(ctx)
if err != nil { if err != nil {
return errorConn{err} return errorConn{err}, err
} }
return &activeConn{p: p, pc: pc}
p.mu.Lock()
if waited > 0 {
p.waitCount++
p.waitDuration += waited
}
// Prune stale connections at the back of the idle list.
if p.IdleTimeout > 0 {
n := p.idle.count
for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ {
pc := p.idle.back
p.idle.popBack()
p.mu.Unlock()
pc.c.Close()
p.mu.Lock()
p.active--
}
}
// Get idle connection from the front of idle list.
for p.idle.front != nil {
pc := p.idle.front
p.idle.popFront()
p.mu.Unlock()
if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) &&
(p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) {
return &activeConn{p: p, pc: pc}, nil
}
pc.c.Close()
p.mu.Lock()
p.active--
}
// Check for pool closed before dialing a new connection.
if p.closed {
p.mu.Unlock()
err := errors.New("redigo: get on closed pool")
return errorConn{err}, err
}
// Handle limit for p.Wait == false.
if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive {
p.mu.Unlock()
return errorConn{ErrPoolExhausted}, ErrPoolExhausted
}
p.active++
p.mu.Unlock()
c, err := p.dial(ctx)
if err != nil {
c = nil
p.mu.Lock()
p.active--
if p.ch != nil && !p.closed {
p.ch <- struct{}{}
}
p.mu.Unlock()
return errorConn{err}, err
}
return &activeConn{p: p, pc: &poolConn{c: c, created: nowFunc()}}, nil
} }
// PoolStats contains pool statistics. // PoolStats contains pool statistics.
@ -188,14 +273,24 @@ type PoolStats struct {
ActiveCount int ActiveCount int
// IdleCount is the number of idle connections in the pool. // IdleCount is the number of idle connections in the pool.
IdleCount int IdleCount int
// WaitCount is the total number of connections waited for.
// This value is currently not guaranteed to be 100% accurate.
WaitCount int64
// WaitDuration is the total time blocked waiting for a new connection.
// This value is currently not guaranteed to be 100% accurate.
WaitDuration time.Duration
} }
// Stats returns pool's statistics. // Stats returns pool's statistics.
func (p *Pool) Stats() PoolStats { func (p *Pool) Stats() PoolStats {
p.mu.Lock() p.mu.Lock()
stats := PoolStats{ stats := PoolStats{
ActiveCount: p.active, ActiveCount: p.active,
IdleCount: p.idle.count, IdleCount: p.idle.count,
WaitCount: p.waitCount,
WaitDuration: p.waitDuration,
} }
p.mu.Unlock() p.mu.Unlock()
@ -242,13 +337,7 @@ func (p *Pool) Close() error {
} }
func (p *Pool) lazyInit() { func (p *Pool) lazyInit() {
// Fast path. p.initOnce.Do(func() {
if atomic.LoadUint32(&p.chInitialized) == 1 {
return
}
// Slow path.
p.mu.Lock()
if p.chInitialized == 0 {
p.ch = make(chan struct{}, p.MaxActive) p.ch = make(chan struct{}, p.MaxActive)
if p.closed { if p.closed {
close(p.ch) close(p.ch)
@ -257,86 +346,59 @@ func (p *Pool) lazyInit() {
p.ch <- struct{}{} p.ch <- struct{}{}
} }
} }
atomic.StoreUint32(&p.chInitialized, 1) })
}
p.mu.Unlock()
} }
// get prunes stale connections and returns a connection from the idle list or // waitVacantConn waits for a vacant connection in pool if waiting
// creates a new connection. // is enabled and pool size is limited, otherwise returns instantly.
func (p *Pool) get(ctx interface { // If ctx expires before that, an error is returned.
Done() <-chan struct{} //
Err() error // If there were no vacant connection in the pool right away it returns the time spent waiting
}) (*poolConn, error) { // for that connection to appear in the pool.
func (p *Pool) waitVacantConn(ctx context.Context) (waited time.Duration, err error) {
// Handle limit for p.Wait == true. if !p.Wait || p.MaxActive <= 0 {
if p.Wait && p.MaxActive > 0 { // No wait or no connection limit.
p.lazyInit() return 0, nil
if ctx == nil {
<-p.ch
} else {
select {
case <-p.ch:
case <-ctx.Done():
return nil, ctx.Err()
}
}
} }
p.mu.Lock() p.lazyInit()
// Prune stale connections at the back of the idle list. // wait indicates if we believe it will block so its not 100% accurate
if p.IdleTimeout > 0 { // however for stats it should be good enough.
n := p.idle.count wait := len(p.ch) == 0
for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { var start time.Time
pc := p.idle.back if wait {
p.idle.popBack() start = time.Now()
p.mu.Unlock()
pc.c.Close()
p.mu.Lock()
p.active--
}
} }
// Get idle connection from the front of idle list. select {
for p.idle.front != nil { case <-p.ch:
pc := p.idle.front // Additionally check that context hasn't expired while we were waiting,
p.idle.popFront() // because `select` picks a random `case` if several of them are "ready".
p.mu.Unlock() select {
if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && case <-ctx.Done():
(p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) {
return pc, nil
}
pc.c.Close()
p.mu.Lock()
p.active--
}
// Check for pool closed before dialing a new connection.
if p.closed {
p.mu.Unlock()
return nil, errors.New("redigo: get on closed pool")
}
// Handle limit for p.Wait == false.
if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive {
p.mu.Unlock()
return nil, ErrPoolExhausted
}
p.active++
p.mu.Unlock()
c, err := p.Dial()
if err != nil {
c = nil
p.mu.Lock()
p.active--
if p.ch != nil && !p.closed {
p.ch <- struct{}{} p.ch <- struct{}{}
return 0, ctx.Err()
default:
} }
p.mu.Unlock() case <-ctx.Done():
return 0, ctx.Err()
} }
return &poolConn{c: c, created: nowFunc()}, err
if wait {
return time.Since(start), nil
}
return 0, nil
}
func (p *Pool) dial(ctx context.Context) (Conn, error) {
if p.DialContext != nil {
return p.DialContext(ctx)
}
if p.Dial != nil {
return p.Dial()
}
return nil, errors.New("redigo: must pass Dial or DialContext to pool")
} }
func (p *Pool) put(pc *poolConn, forceClose bool) error { func (p *Pool) put(pc *poolConn, forceClose bool) error {

View File

@ -1,35 +0,0 @@
// Copyright 2018 Gary Burd
//
// 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.
// +build go1.7
package redis
import "context"
// GetContext gets a connection using the provided context.
//
// The provided Context must be non-nil. If the context expires before the
// connection is complete, an error is returned. Any expiration on the context
// will not affect the returned connection.
//
// If the function completes without error, then the application must close the
// returned connection.
func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
pc, err := p.get(ctx)
if err != nil {
return errorConn{err}, err
}
return &activeConn{p: p, pc: pc}, nil
}

View File

@ -115,3 +115,24 @@ func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) {
} }
return cwt.ReceiveWithTimeout(timeout) return cwt.ReceiveWithTimeout(timeout)
} }
// SlowLog represents a redis SlowLog
type SlowLog struct {
// ID is a unique progressive identifier for every slow log entry.
ID int64
// Time is the unix timestamp at which the logged command was processed.
Time time.Time
// ExecutationTime is the amount of time needed for the command execution.
ExecutionTime time.Duration
// Args is the command name and arguments
Args []string
// ClientAddr is the client IP address (4.0 only).
ClientAddr string
// ClientName is the name set via the CLIENT SETNAME command (4.0 only).
ClientName string
}

View File

@ -18,6 +18,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
"time"
) )
// ErrNil indicates that a reply value is nil. // ErrNil indicates that a reply value is nil.
@ -55,7 +56,7 @@ func Int(reply interface{}, err error) (int, error) {
} }
// Int64 is a helper that converts a command reply to 64 bit integer. If err is // Int64 is a helper that converts a command reply to 64 bit integer. If err is
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the // not equal to nil, then Int64 returns 0, err. Otherwise, Int64 converts the
// reply to an int64 as follows: // reply to an int64 as follows:
// //
// Reply type Result // Reply type Result
@ -81,14 +82,16 @@ func Int64(reply interface{}, err error) (int64, error) {
return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
} }
var errNegativeInt = errors.New("redigo: unexpected value for Uint64") func errNegativeInt(v int64) error {
return fmt.Errorf("redigo: unexpected negative value %v for Uint64", v)
}
// Uint64 is a helper that converts a command reply to 64 bit integer. If err is // Uint64 is a helper that converts a command reply to 64 bit unsigned integer.
// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the // If err is not equal to nil, then Uint64 returns 0, err. Otherwise, Uint64 converts the
// reply to an int64 as follows: // reply to an uint64 as follows:
// //
// Reply type Result // Reply type Result
// integer reply, nil // +integer reply, nil
// bulk string parsed reply, nil // bulk string parsed reply, nil
// nil 0, ErrNil // nil 0, ErrNil
// other 0, error // other 0, error
@ -99,7 +102,7 @@ func Uint64(reply interface{}, err error) (uint64, error) {
switch reply := reply.(type) { switch reply := reply.(type) {
case int64: case int64:
if reply < 0 { if reply < 0 {
return 0, errNegativeInt return 0, errNegativeInt(reply)
} }
return uint64(reply), nil return uint64(reply), nil
case []byte: case []byte:
@ -115,7 +118,7 @@ func Uint64(reply interface{}, err error) (uint64, error) {
// Float64 is a helper that converts a command reply to 64 bit float. If err is // Float64 is a helper that converts a command reply to 64 bit float. If err is
// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
// the reply to an int as follows: // the reply to a float64 as follows:
// //
// Reply type Result // Reply type Result
// bulk string parsed reply, nil // bulk string parsed reply, nil
@ -345,7 +348,7 @@ func Int64s(reply interface{}, err error) ([]int64, error) {
return result, err return result, err
} }
// Ints is a helper that converts an array command reply to a []in. // Ints is a helper that converts an array command reply to a []int.
// If err is not equal to nil, then Ints returns nil, err. Nil array // If err is not equal to nil, then Ints returns nil, err. Nil array
// items are stay nil. Ints returns an error if an array item is not a // items are stay nil. Ints returns an error if an array item is not a
// bulk string or nil. // bulk string or nil.
@ -477,3 +480,104 @@ func Positions(result interface{}, err error) ([]*[2]float64, error) {
} }
return positions, nil return positions, nil
} }
// Uint64s is a helper that converts an array command reply to a []uint64.
// If err is not equal to nil, then Uint64s returns nil, err. Nil array
// items are stay nil. Uint64s returns an error if an array item is not a
// bulk string or nil.
func Uint64s(reply interface{}, err error) ([]uint64, error) {
var result []uint64
err = sliceHelper(reply, err, "Uint64s", func(n int) { result = make([]uint64, n) }, func(i int, v interface{}) error {
switch v := v.(type) {
case uint64:
result[i] = v
return nil
case []byte:
n, err := strconv.ParseUint(string(v), 10, 64)
result[i] = n
return err
default:
return fmt.Errorf("redigo: unexpected element type for Uint64s, got type %T", v)
}
})
return result, err
}
// Uint64Map is a helper that converts an array of strings (alternating key, value)
// 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
}
// SlowLogs is a helper that parse the SLOWLOG GET command output and
// return the array of SlowLog
func SlowLogs(result interface{}, err error) ([]SlowLog, error) {
rawLogs, err := Values(result, err)
if err != nil {
return nil, err
}
logs := make([]SlowLog, len(rawLogs))
for i, rawLog := range rawLogs {
rawLog, ok := rawLog.([]interface{})
if !ok {
return nil, errors.New("redigo: slowlog element is not an array")
}
var log SlowLog
if len(rawLog) < 4 {
return nil, errors.New("redigo: slowlog element has less than four elements")
}
log.ID, ok = rawLog[0].(int64)
if !ok {
return nil, errors.New("redigo: slowlog element[0] not an int64")
}
timestamp, ok := rawLog[1].(int64)
if !ok {
return nil, errors.New("redigo: slowlog element[1] not an int64")
}
log.Time = time.Unix(timestamp, 0)
duration, ok := rawLog[2].(int64)
if !ok {
return nil, errors.New("redigo: slowlog element[2] not an int64")
}
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())
}
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())
}
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())
}
}
logs[i] = log
}
return logs, nil
}

View File

@ -23,6 +23,10 @@ import (
"sync" "sync"
) )
var (
scannerType = reflect.TypeOf((*Scanner)(nil)).Elem()
)
func ensureLen(d reflect.Value, n int) { func ensureLen(d reflect.Value, n int) {
if n > d.Cap() { if n > d.Cap() {
d.Set(reflect.MakeSlice(d.Type(), n, n)) d.Set(reflect.MakeSlice(d.Type(), n, n))
@ -44,44 +48,105 @@ func cannotConvert(d reflect.Value, s interface{}) error {
sname = "Redis bulk string" sname = "Redis bulk string"
case []interface{}: case []interface{}:
sname = "Redis array" sname = "Redis array"
case nil:
sname = "Redis nil"
default: default:
sname = reflect.TypeOf(s).String() sname = reflect.TypeOf(s).String()
} }
return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
} }
func convertAssignBulkString(d reflect.Value, s []byte) (err error) { func convertAssignNil(d reflect.Value) (err error) {
switch d.Type().Kind() {
case reflect.Slice, reflect.Interface:
d.Set(reflect.Zero(d.Type()))
default:
err = cannotConvert(d, nil)
}
return err
}
func convertAssignError(d reflect.Value, s Error) (err error) {
if d.Kind() == reflect.String {
d.SetString(string(s))
} else if d.Kind() == reflect.Slice && d.Type().Elem().Kind() == reflect.Uint8 {
d.SetBytes([]byte(s))
} else {
err = cannotConvert(d, s)
}
return
}
func convertAssignString(d reflect.Value, s string) (err error) {
switch d.Type().Kind() { switch d.Type().Kind() {
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
var x float64 var x float64
x, err = strconv.ParseFloat(string(s), d.Type().Bits()) x, err = strconv.ParseFloat(s, d.Type().Bits())
d.SetFloat(x) d.SetFloat(x)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var x int64 var x int64
x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) x, err = strconv.ParseInt(s, 10, d.Type().Bits())
d.SetInt(x) d.SetInt(x)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
var x uint64 var x uint64
x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) x, err = strconv.ParseUint(s, 10, d.Type().Bits())
d.SetUint(x) d.SetUint(x)
case reflect.Bool: case reflect.Bool:
var x bool var x bool
x, err = strconv.ParseBool(string(s)) x, err = strconv.ParseBool(s)
d.SetBool(x) d.SetBool(x)
case reflect.String: case reflect.String:
d.SetString(string(s)) d.SetString(s)
case reflect.Slice: case reflect.Slice:
if d.Type().Elem().Kind() != reflect.Uint8 { if d.Type().Elem().Kind() == reflect.Uint8 {
err = cannotConvert(d, s) d.SetBytes([]byte(s))
} else { } else {
d.SetBytes(s) err = cannotConvert(d, s)
} }
case reflect.Ptr:
err = convertAssignString(d.Elem(), s)
default: default:
err = cannotConvert(d, s) err = cannotConvert(d, s)
} }
return return
} }
func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
switch d.Type().Kind() {
case reflect.Slice:
// Handle []byte destination here to avoid unnecessary
// []byte -> string -> []byte converion.
if d.Type().Elem().Kind() == reflect.Uint8 {
d.SetBytes(s)
} else {
err = cannotConvert(d, s)
}
case reflect.Ptr:
if d.CanInterface() && d.CanSet() {
if s == nil {
if d.IsNil() {
return nil
}
d.Set(reflect.Zero(d.Type()))
return nil
}
if d.IsNil() {
d.Set(reflect.New(d.Type().Elem()))
}
if sc, ok := d.Interface().(Scanner); ok {
return sc.RedisScan(s)
}
}
err = convertAssignString(d, string(s))
default:
err = convertAssignString(d, string(s))
}
return err
}
func convertAssignInt(d reflect.Value, s int64) (err error) { func convertAssignInt(d reflect.Value, s int64) (err error) {
switch d.Type().Kind() { switch d.Type().Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@ -130,10 +195,16 @@ func convertAssignValue(d reflect.Value, s interface{}) (err error) {
} }
switch s := s.(type) { switch s := s.(type) {
case nil:
err = convertAssignNil(d)
case []byte: case []byte:
err = convertAssignBulkString(d, s) err = convertAssignBulkString(d, s)
case int64: case int64:
err = convertAssignInt(d, s) err = convertAssignInt(d, s)
case string:
err = convertAssignString(d, s)
case Error:
err = convertAssignError(d, s)
default: default:
err = cannotConvert(d, s) err = cannotConvert(d, s)
} }
@ -285,34 +356,49 @@ func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
} }
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) {
LOOP:
for i := 0; i < t.NumField(); i++ { for i := 0; i < t.NumField(); i++ {
f := t.Field(i) f := t.Field(i)
switch { switch {
case f.PkgPath != "" && !f.Anonymous: case f.PkgPath != "" && !f.Anonymous:
// Ignore unexported fields. // Ignore unexported fields.
case f.Anonymous: case f.Anonymous:
// TODO: Handle pointers. Requires change to decoder and switch f.Type.Kind() {
// protection against infinite recursion. case reflect.Struct:
if f.Type.Kind() == reflect.Struct {
compileStructSpec(f.Type, depth, append(index, i), ss) compileStructSpec(f.Type, depth, append(index, i), ss)
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)
}
} }
default: default:
fs := &fieldSpec{name: f.Name} fs := &fieldSpec{name: f.Name}
tag := f.Tag.Get("redis") tag := f.Tag.Get("redis")
p := strings.Split(tag, ",")
if len(p) > 0 { var (
if p[0] == "-" { p string
continue )
first := true
for len(tag) > 0 {
i := strings.IndexByte(tag, ',')
if i < 0 {
p, tag = tag, ""
} else {
p, tag = tag[:i], tag[i+1:]
} }
if len(p[0]) > 0 { if p == "-" {
fs.name = p[0] continue LOOP
} }
for _, s := range p[1:] { if first && len(p) > 0 {
switch s { fs.name = p
first = false
} else {
switch p {
case "omitempty": case "omitempty":
fs.omitEmpty = true fs.omitEmpty = true
default: default:
panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) panic(fmt.Errorf("redigo: unknown field tag %s for type %s", p, t.Name()))
} }
} }
} }
@ -429,9 +515,13 @@ var (
errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
) )
// ScanSlice scans src to the slice pointed to by dest. The elements the dest // ScanSlice scans src to the slice pointed to by dest.
// slice must be integer, float, boolean, string, struct or pointer to struct //
// values. // If the target is a slice of types which implement Scanner then the custom
// RedisScan method is used otherwise the following rules apply:
//
// The elements in the dest slice must be integer, float, boolean, string, struct
// or pointer to struct values.
// //
// Struct fields must be integer, float, boolean or string values. All struct // Struct fields must be integer, float, boolean or string values. All struct
// fields are used unless a subset is specified using fieldNames. // fields are used unless a subset is specified using fieldNames.
@ -447,12 +537,13 @@ func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error
isPtr := false isPtr := false
t := d.Type().Elem() t := d.Type().Elem()
st := t
if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
isPtr = true isPtr = true
t = t.Elem() t = t.Elem()
} }
if t.Kind() != reflect.Struct { if t.Kind() != reflect.Struct || st.Implements(scannerType) {
ensureLen(d, len(src)) ensureLen(d, len(src))
for i, s := range src { for i, s := range src {
if s == nil { if s == nil {
@ -579,7 +670,15 @@ func flattenStruct(args Args, v reflect.Value) Args {
continue continue
} }
} }
args = append(args, fs.name, fv.Interface()) if arg, ok := fv.Interface().(Argument); ok {
args = append(args, fs.name, arg.RedisArg())
} else if fv.Kind() == reflect.Ptr {
if !fv.IsNil() {
args = append(args, fs.name, fv.Elem().Interface())
}
} else {
args = append(args, fs.name, fv.Interface())
}
} }
return args return args
} }

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// Package cmp determines equality of values. // Package cmp determines equality of values.
// //
@ -100,8 +100,8 @@ func Equal(x, y interface{}, opts ...Option) bool {
// same input values and options. // same input values and options.
// //
// The output is displayed as a literal in pseudo-Go syntax. // The output is displayed as a literal in pseudo-Go syntax.
// At the start of each line, a "-" prefix indicates an element removed from y, // At the start of each line, a "-" prefix indicates an element removed from x,
// a "+" prefix to indicates an element added to y, and the lack of a prefix // a "+" prefix to indicates an element added from y, and the lack of a prefix
// indicates an element common to both x and y. If possible, the output // indicates an element common to both x and y. If possible, the output
// uses fmt.Stringer.String or error.Error methods to produce more humanly // uses fmt.Stringer.String or error.Error methods to produce more humanly
// readable outputs. In such cases, the string is prefixed with either an // readable outputs. In such cases, the string is prefixed with either an

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build purego // +build purego

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build !purego // +build !purego

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build !cmp_debug // +build !cmp_debug

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build cmp_debug // +build cmp_debug

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// Package diff implements an algorithm for producing edit-scripts. // Package diff implements an algorithm for producing edit-scripts.
// The edit-script is a sequence of operations needed to transform one list // The edit-script is a sequence of operations needed to transform one list
@ -119,7 +119,7 @@ func (r Result) Similar() bool {
return r.NumSame+1 >= r.NumDiff return r.NumSame+1 >= r.NumDiff
} }
var randInt = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0
// Difference reports whether two lists of lengths nx and ny are equal // Difference reports whether two lists of lengths nx and ny are equal
// given the definition of equality provided as f. // given the definition of equality provided as f.
@ -168,17 +168,6 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// A vertical edge is equivalent to inserting a symbol from list Y. // A vertical edge is equivalent to inserting a symbol from list Y.
// A diagonal edge is equivalent to a matching symbol between both X and Y. // A diagonal edge is equivalent to a matching symbol between both X and Y.
// To ensure flexibility in changing the algorithm in the future,
// introduce some degree of deliberate instability.
// This is achieved by fiddling the zigzag iterator to start searching
// the graph starting from the bottom-right versus than the top-left.
// The result may differ depending on the starting search location,
// but still produces a valid edit script.
zigzagInit := randInt // either 0 or 1
if flags.Deterministic {
zigzagInit = 0
}
// Invariants: // Invariants:
// • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx // • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
// • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny // • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
@ -197,6 +186,11 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// approximately the square-root of the search budget. // approximately the square-root of the search budget.
searchBudget := 4 * (nx + ny) // O(n) searchBudget := 4 * (nx + ny) // O(n)
// Running the tests with the "cmp_debug" build tag prints a visualization
// of the algorithm running in real-time. This is educational for
// understanding how the algorithm works. See debug_enable.go.
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es)
// The algorithm below is a greedy, meet-in-the-middle algorithm for // The algorithm below is a greedy, meet-in-the-middle algorithm for
// computing sub-optimal edit-scripts between two lists. // computing sub-optimal edit-scripts between two lists.
// //
@ -214,22 +208,28 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
// frontier towards the opposite corner. // frontier towards the opposite corner.
// • This algorithm terminates when either the X coordinates or the // • This algorithm terminates when either the X coordinates or the
// Y coordinates of the forward and reverse frontier points ever intersect. // Y coordinates of the forward and reverse frontier points ever intersect.
//
// This algorithm is correct even if searching only in the forward direction // This algorithm is correct even if searching only in the forward direction
// or in the reverse direction. We do both because it is commonly observed // or in the reverse direction. We do both because it is commonly observed
// that two lists commonly differ because elements were added to the front // that two lists commonly differ because elements were added to the front
// or end of the other list. // or end of the other list.
// //
// Running the tests with the "cmp_debug" build tag prints a visualization // Non-deterministically start with either the forward or reverse direction
// of the algorithm running in real-time. This is educational for // to introduce some deliberate instability so that we have the flexibility
// understanding how the algorithm works. See debug_enable.go. // to change this algorithm in the future.
f = debug.Begin(nx, ny, f, &fwdPath.es, &revPath.es) if flags.Deterministic || randBool {
for { goto forwardSearch
} else {
goto reverseSearch
}
forwardSearch:
{
// Forward search from the beginning. // Forward search from the beginning.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 { if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
break goto finishSearch
} }
for stop1, stop2, i := false, false, zigzagInit; !(stop1 && stop2) && searchBudget > 0; i++ { for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match. // Search in a diagonal pattern for a match.
z := zigzag(i) z := zigzag(i)
p := point{fwdFrontier.X + z, fwdFrontier.Y - z} p := point{fwdFrontier.X + z, fwdFrontier.Y - z}
@ -262,10 +262,14 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
} else { } else {
fwdFrontier.Y++ fwdFrontier.Y++
} }
goto reverseSearch
}
reverseSearch:
{
// Reverse search from the end. // Reverse search from the end.
if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 { if fwdFrontier.X >= revFrontier.X || fwdFrontier.Y >= revFrontier.Y || searchBudget == 0 {
break goto finishSearch
} }
for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ { for stop1, stop2, i := false, false, 0; !(stop1 && stop2) && searchBudget > 0; i++ {
// Search in a diagonal pattern for a match. // Search in a diagonal pattern for a match.
@ -300,8 +304,10 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) {
} else { } else {
revFrontier.Y-- revFrontier.Y--
} }
goto forwardSearch
} }
finishSearch:
// Join the forward and reverse paths and then append the reverse path. // Join the forward and reverse paths and then append the reverse path.
fwdPath.connect(revPath.point, f) fwdPath.connect(revPath.point, f)
for i := len(revPath.es) - 1; i >= 0; i-- { for i := len(revPath.es) - 1; i >= 0; i-- {

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package flags package flags

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build !go1.10 // +build !go1.10

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build go1.10 // +build go1.10

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// Package function provides functionality for identifying function types. // Package function provides functionality for identifying function types.
package function package function

View File

@ -1,6 +1,6 @@
// Copyright 2020, The Go Authors. All rights reserved. // Copyright 2020, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package value package value

View File

@ -1,6 +1,6 @@
// Copyright 2018, The Go Authors. All rights reserved. // Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build purego // +build purego

View File

@ -1,6 +1,6 @@
// Copyright 2018, The Go Authors. All rights reserved. // Copyright 2018, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
// +build !purego // +build !purego

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package value package value

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package value package value

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -1,6 +1,6 @@
// Copyright 2017, The Go Authors. All rights reserved. // Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -1,6 +1,6 @@
// Copyright 2020, The Go Authors. All rights reserved. // Copyright 2020, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp
@ -351,6 +351,8 @@ func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) s
opts.PrintAddresses = disambiguate opts.PrintAddresses = disambiguate
opts.AvoidStringer = disambiguate opts.AvoidStringer = disambiguate
opts.QualifiedNames = disambiguate opts.QualifiedNames = disambiguate
opts.VerbosityLevel = maxVerbosityPreset
opts.LimitVerbosity = true
s := opts.FormatValue(v, reflect.Map, ptrs).String() s := opts.FormatValue(v, reflect.Map, ptrs).String()
return strings.TrimSpace(s) return strings.TrimSpace(s)
} }

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -1,6 +1,6 @@
// Copyright 2019, The Go Authors. All rights reserved. // Copyright 2019, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file. // license that can be found in the LICENSE file.
package cmp package cmp

View File

@ -67,7 +67,12 @@ func (s *Weighted) Acquire(ctx context.Context, n int64) error {
// fix up the queue, just pretend we didn't notice the cancelation. // fix up the queue, just pretend we didn't notice the cancelation.
err = nil err = nil
default: default:
isFront := s.waiters.Front() == elem
s.waiters.Remove(elem) s.waiters.Remove(elem)
// If we're at the front and there're extra tokens left, notify other waiters.
if isFront && s.size > s.cur {
s.notifyWaiters()
}
} }
s.mu.Unlock() s.mu.Unlock()
return err return err
@ -97,6 +102,11 @@ func (s *Weighted) Release(n int64) {
s.mu.Unlock() s.mu.Unlock()
panic("semaphore: released more than held") panic("semaphore: released more than held")
} }
s.notifyWaiters()
s.mu.Unlock()
}
func (s *Weighted) notifyWaiters() {
for { for {
next := s.waiters.Front() next := s.waiters.Front()
if next == nil { if next == nil {
@ -123,5 +133,4 @@ func (s *Weighted) Release(n int64) {
s.waiters.Remove(next) s.waiters.Remove(next)
close(w.ready) close(w.ready)
} }
s.mu.Unlock()
} }

19
vendor/modules.txt vendored
View File

@ -90,10 +90,10 @@ github.com/golang/protobuf/ptypes/empty
github.com/golang/protobuf/ptypes/timestamp github.com/golang/protobuf/ptypes/timestamp
# github.com/golang/snappy v0.0.1 # github.com/golang/snappy v0.0.1
github.com/golang/snappy github.com/golang/snappy
# github.com/gomodule/redigo v2.0.1-0.20181026001555-e8fc0692a7e2+incompatible # github.com/gomodule/redigo v1.8.3
## explicit ## explicit
github.com/gomodule/redigo/redis github.com/gomodule/redigo/redis
# github.com/google/go-cmp v0.5.2 # github.com/google/go-cmp v0.5.4
github.com/google/go-cmp/cmp github.com/google/go-cmp/cmp
github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/diff
github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/flags
@ -231,7 +231,6 @@ github.com/yuin/gopher-lua
github.com/yuin/gopher-lua/ast github.com/yuin/gopher-lua/ast
github.com/yuin/gopher-lua/parse github.com/yuin/gopher-lua/parse
github.com/yuin/gopher-lua/pm github.com/yuin/gopher-lua/pm
<<<<<<< HEAD
# go.opencensus.io v0.22.3 # go.opencensus.io v0.22.3
go.opencensus.io go.opencensus.io
go.opencensus.io/internal go.opencensus.io/internal
@ -248,25 +247,17 @@ go.opencensus.io/trace
go.opencensus.io/trace/internal go.opencensus.io/trace/internal
go.opencensus.io/trace/propagation go.opencensus.io/trace/propagation
go.opencensus.io/trace/tracestate go.opencensus.io/trace/tracestate
# golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
## explicit
=======
# golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a # golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
>>>>>>> 6b08f7fa (Code cleanup)
golang.org/x/crypto/ed25519 golang.org/x/crypto/ed25519
golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/ed25519/internal/edwards25519
golang.org/x/crypto/md4 golang.org/x/crypto/md4
golang.org/x/crypto/pbkdf2 golang.org/x/crypto/pbkdf2
<<<<<<< HEAD
golang.org/x/crypto/ssh/terminal
# golang.org/x/lint v0.0.0-20200302205851-738671d3881b # golang.org/x/lint v0.0.0-20200302205851-738671d3881b
golang.org/x/lint golang.org/x/lint
golang.org/x/lint/golint golang.org/x/lint/golint
# golang.org/x/mod v0.2.0 # golang.org/x/mod v0.2.0
golang.org/x/mod/module golang.org/x/mod/module
golang.org/x/mod/semver golang.org/x/mod/semver
=======
>>>>>>> 6b08f7fa (Code cleanup)
# golang.org/x/net v0.0.0-20210119194325-5f4716e94777 # golang.org/x/net v0.0.0-20210119194325-5f4716e94777
## explicit ## explicit
golang.org/x/net/context golang.org/x/net/context
@ -279,20 +270,16 @@ golang.org/x/net/internal/socks
golang.org/x/net/internal/timeseries golang.org/x/net/internal/timeseries
golang.org/x/net/proxy golang.org/x/net/proxy
golang.org/x/net/trace golang.org/x/net/trace
<<<<<<< HEAD
# golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d # golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/oauth2 golang.org/x/oauth2
golang.org/x/oauth2/google golang.org/x/oauth2/google
golang.org/x/oauth2/internal golang.org/x/oauth2/internal
golang.org/x/oauth2/jws golang.org/x/oauth2/jws
golang.org/x/oauth2/jwt golang.org/x/oauth2/jwt
# golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e # golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sync/errgroup golang.org/x/sync/errgroup
golang.org/x/sync/semaphore golang.org/x/sync/semaphore
# golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
=======
# golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 # golang.org/x/sys v0.0.0-20210309074719-68d13333faf2
>>>>>>> 13ede90b (vendor dir)
golang.org/x/sys/internal/unsafeheader golang.org/x/sys/internal/unsafeheader
golang.org/x/sys/plan9 golang.org/x/sys/plan9
golang.org/x/sys/unix golang.org/x/sys/unix