mirror of https://github.com/tidwall/tile38.git
Merge distance updates
This commit is contained in:
parent
aea7d77de5
commit
e60cbac7cf
2
go.mod
2
go.mod
|
@ -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
4
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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-- {
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue