diff --git a/src/Gopkg.lock b/src/Gopkg.lock index 477da504e..535be5dc5 100644 --- a/src/Gopkg.lock +++ b/src/Gopkg.lock @@ -120,6 +120,12 @@ revision = "47dc60e71eed504e3ef8e77ee3c6fe720f3be57f" version = "v1.3.0" +[[projects]] + name = "github.com/go-redsync/redsync" + packages = ["."] + revision = "98dabdf1c8574561bf976911c14ff652c47b1ddf" + version = "v1.0.1" + [[projects]] name = "github.com/go-sql-driver/mysql" packages = ["."] @@ -258,6 +264,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1b5bec4304c99684de7fddc82ce9c81788e945f82455125110f32441a6ffd4cc" + inputs-digest = "49328284c14629d33811bc9d67012a48314bb52bbb4ad7282699afbd4506f287" solver-name = "gps-cdcl" solver-version = 1 diff --git a/src/Gopkg.toml b/src/Gopkg.toml index ab00c86b1..653e08805 100644 --- a/src/Gopkg.toml +++ b/src/Gopkg.toml @@ -18,7 +18,7 @@ # [[override]] # name = "github.com/x/y" # version = "2.4.0" - +ignored = ["github.com/vmware/harbor"] [[constraint]] name = "github.com/astaxie/beego" @@ -55,3 +55,7 @@ [[constraint]] name = "github.com/stretchr/testify" version = "1.2.0" + +[[constraint]] + name = "github.com/go-redsync/redsync" + version = "1.0.1" diff --git a/src/vendor/github.com/go-redsync/redsync/.gitlab-ci.yml b/src/vendor/github.com/go-redsync/redsync/.gitlab-ci.yml new file mode 100644 index 000000000..497d40b15 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/.gitlab-ci.yml @@ -0,0 +1,24 @@ +before_script: + - mkdir -p $GOPATH/src/github.com/redsync + - ln -s $CI_PROJECT_DIR $GOPATH/src/github.com/redsync/redsync + - cd $GOPATH/src/github.com/redsync/redsync + - apt-get update + - apt-get -y install redis-server + +stages: + - build + - test + +build-go-1.5: + image: golang:1.5 + stage: build + script: + - go get -v + - go build -v + +test-go-1.5: + image: golang:1.5 + stage: test + script: + - go get -v -t + - go test -v diff --git a/src/vendor/github.com/go-redsync/redsync/LICENSE b/src/vendor/github.com/go-redsync/redsync/LICENSE new file mode 100644 index 000000000..8498c11a2 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016, Mahmud Ridwan +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the Redsync nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/vendor/github.com/go-redsync/redsync/README.md b/src/vendor/github.com/go-redsync/redsync/README.md new file mode 100644 index 000000000..b14debb52 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/README.md @@ -0,0 +1,29 @@ +# Redsync + +[![Build Status](https://drone.io/github.com/go-redsync/redsync/status.png)](https://drone.io/github.com/go-redsync/redsync/latest) + +Redsync provides a Redis-based distributed mutual exclusion lock implementation for Go as described in [this post](http://redis.io/topics/distlock). A reference library (by [antirez](https://github.com/antirez)) for Ruby is available at [github.com/antirez/redlock-rb](https://github.com/antirez/redlock-rb). + +## Installation + +Install Redsync using the go get command: + + $ go get gopkg.in/redsync.v1 + +The only dependencies are the Go distribution and [Redigo](github.com/garyburd/redigo). + +## Documentation + +- [Reference](https://godoc.org/gopkg.in/redsync.v1) + +## Contributing + +Contributions are welcome. + +## License + +Redsync is available under the [BSD (3-Clause) License](https://opensource.org/licenses/BSD-3-Clause). + +## Disclaimer + +This code implements an algorithm which is currently a proposal, it was not formally analyzed. Make sure to understand how it works before using it in production environments. diff --git a/src/vendor/github.com/go-redsync/redsync/VERSION b/src/vendor/github.com/go-redsync/redsync/VERSION new file mode 100644 index 000000000..626799f0f --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/VERSION @@ -0,0 +1 @@ +v1 diff --git a/src/vendor/github.com/go-redsync/redsync/doc.go b/src/vendor/github.com/go-redsync/redsync/doc.go new file mode 100644 index 000000000..b215b6ea7 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/doc.go @@ -0,0 +1,4 @@ +// Package redsync provides a Redis-based distributed mutual exclusion lock implementation as described in the post http://redis.io/topics/distlock. +// +// Values containing the types defined in this package should not be copied. +package redsync diff --git a/src/vendor/github.com/go-redsync/redsync/error.go b/src/vendor/github.com/go-redsync/redsync/error.go new file mode 100644 index 000000000..47cc26ac0 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/error.go @@ -0,0 +1,5 @@ +package redsync + +import "errors" + +var ErrFailed = errors.New("redsync: failed to acquire lock") diff --git a/src/vendor/github.com/go-redsync/redsync/mutex.go b/src/vendor/github.com/go-redsync/redsync/mutex.go new file mode 100644 index 000000000..a3c7ff8d5 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/mutex.go @@ -0,0 +1,145 @@ +package redsync + +import ( + "crypto/rand" + "encoding/base64" + "sync" + "time" + + "github.com/garyburd/redigo/redis" +) + +// A Mutex is a distributed mutual exclusion lock. +type Mutex struct { + name string + expiry time.Duration + + tries int + delay time.Duration + + factor float64 + + quorum int + + value string + until time.Time + + nodem sync.Mutex + + pools []Pool +} + +// Lock locks m. In case it returns an error on failure, you may retry to acquire the lock by calling this method again. +func (m *Mutex) Lock() error { + m.nodem.Lock() + defer m.nodem.Unlock() + + value, err := m.genValue() + if err != nil { + return err + } + + for i := 0; i < m.tries; i++ { + if i != 0 { + time.Sleep(m.delay) + } + + start := time.Now() + + n := 0 + for _, pool := range m.pools { + ok := m.acquire(pool, value) + if ok { + n++ + } + } + + until := time.Now().Add(m.expiry - time.Now().Sub(start) - time.Duration(int64(float64(m.expiry)*m.factor)) + 2*time.Millisecond) + if n >= m.quorum && time.Now().Before(until) { + m.value = value + m.until = until + return nil + } + for _, pool := range m.pools { + m.release(pool, value) + } + } + + return ErrFailed +} + +// Unlock unlocks m and returns the status of unlock. It is a run-time error if m is not locked on entry to Unlock. +func (m *Mutex) Unlock() bool { + m.nodem.Lock() + defer m.nodem.Unlock() + + n := 0 + for _, pool := range m.pools { + ok := m.release(pool, m.value) + if ok { + n++ + } + } + return n >= m.quorum +} + +// Extend resets the mutex's expiry and returns the status of expiry extension. It is a run-time error if m is not locked on entry to Extend. +func (m *Mutex) Extend() bool { + m.nodem.Lock() + defer m.nodem.Unlock() + + n := 0 + for _, pool := range m.pools { + ok := m.touch(pool, m.value, int(m.expiry/time.Millisecond)) + if ok { + n++ + } + } + return n >= m.quorum +} + +func (m *Mutex) genValue() (string, error) { + b := make([]byte, 32) + _, err := rand.Read(b) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(b), nil +} + +func (m *Mutex) acquire(pool Pool, value string) bool { + conn := pool.Get() + defer conn.Close() + reply, err := redis.String(conn.Do("SET", m.name, value, "NX", "PX", int(m.expiry/time.Millisecond))) + return err == nil && reply == "OK" +} + +var deleteScript = redis.NewScript(1, ` + if redis.call("GET", KEYS[1]) == ARGV[1] then + return redis.call("DEL", KEYS[1]) + else + return 0 + end +`) + +func (m *Mutex) release(pool Pool, value string) bool { + conn := pool.Get() + defer conn.Close() + status, err := deleteScript.Do(conn, m.name, value) + return err == nil && status != 0 +} + +var touchScript = redis.NewScript(1, ` + if redis.call("GET", KEYS[1]) == ARGV[1] then + return redis.call("SET", KEYS[1], ARGV[1], "XX", "PX", ARGV[2]) + else + return "ERR" + end +`) + +func (m *Mutex) touch(pool Pool, value string, expiry int) bool { + conn := pool.Get() + defer conn.Close() + status, err := redis.String(touchScript.Do(conn, m.name, value, expiry)) + return err == nil && status != "ERR" +} diff --git a/src/vendor/github.com/go-redsync/redsync/mutex_test.go b/src/vendor/github.com/go-redsync/redsync/mutex_test.go new file mode 100644 index 000000000..770ef962e --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/mutex_test.go @@ -0,0 +1,180 @@ +package redsync + +import ( + "strconv" + "testing" + "time" + + "github.com/garyburd/redigo/redis" + "github.com/stvp/tempredis" +) + +func TestMutex(t *testing.T) { + pools := newMockPools(8) + mutexes := newTestMutexes(pools, "test-mutex", 8) + orderCh := make(chan int) + for i, mutex := range mutexes { + go func(i int, mutex *Mutex) { + err := mutex.Lock() + if err != nil { + t.Fatalf("Expected err == nil, got %q", err) + } + defer mutex.Unlock() + + assertAcquired(t, pools, mutex) + + orderCh <- i + }(i, mutex) + } + for range mutexes { + <-orderCh + } +} + +func TestMutexExtend(t *testing.T) { + pools := newMockPools(8) + mutexes := newTestMutexes(pools, "test-mutex-extend", 1) + mutex := mutexes[0] + + err := mutex.Lock() + if err != nil { + t.Fatalf("Expected err == nil, got %q", err) + } + defer mutex.Unlock() + + time.Sleep(1 * time.Second) + + expiries := getPoolExpiries(pools, mutex.name) + ok := mutex.Extend() + if !ok { + t.Fatalf("Expected ok == true, got %v", ok) + } + expiries2 := getPoolExpiries(pools, mutex.name) + + for i, expiry := range expiries { + if expiry >= expiries2[i] { + t.Fatalf("Expected expiries[%d] > expiry, got %d %d", i, expiries2[i], expiry) + } + } +} + +func TestMutexQuorum(t *testing.T) { + pools := newMockPools(4) + for mask := 0; mask < 1<= len(pools)/2+1 { + err := mutex.Lock() + if err != nil { + t.Fatalf("Expected err == nil, got %q", err) + } + assertAcquired(t, pools, mutex) + } else { + err := mutex.Lock() + if err != ErrFailed { + t.Fatalf("Expected err == %q, got %q", ErrFailed, err) + } + } + } +} + +func newMockPools(n int) []Pool { + pools := []Pool{} + for _, server := range servers { + func(server *tempredis.Server) { + pools = append(pools, &redis.Pool{ + MaxIdle: 3, + IdleTimeout: 240 * time.Second, + Dial: func() (redis.Conn, error) { + return redis.Dial("unix", server.Socket()) + }, + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + }) + }(server) + if len(pools) == n { + break + } + } + return pools +} + +func getPoolValues(pools []Pool, name string) []string { + values := []string{} + for _, pool := range pools { + conn := pool.Get() + value, err := redis.String(conn.Do("GET", name)) + conn.Close() + if err != nil && err != redis.ErrNil { + panic(err) + } + values = append(values, value) + } + return values +} + +func getPoolExpiries(pools []Pool, name string) []int { + expiries := []int{} + for _, pool := range pools { + conn := pool.Get() + expiry, err := redis.Int(conn.Do("PTTL", name)) + conn.Close() + if err != nil && err != redis.ErrNil { + panic(err) + } + expiries = append(expiries, expiry) + } + return expiries +} + +func clogPools(pools []Pool, mask int, mutex *Mutex) int { + n := 0 + for i, pool := range pools { + if mask&(1<= %d, got %d", mutex.quorum, n) + } +} diff --git a/src/vendor/github.com/go-redsync/redsync/redis.go b/src/vendor/github.com/go-redsync/redsync/redis.go new file mode 100644 index 000000000..b48d75500 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/redis.go @@ -0,0 +1,8 @@ +package redsync + +import "github.com/garyburd/redigo/redis" + +// A Pool maintains a pool of Redis connections. +type Pool interface { + Get() redis.Conn +} diff --git a/src/vendor/github.com/go-redsync/redsync/redsync.go b/src/vendor/github.com/go-redsync/redsync/redsync.go new file mode 100644 index 000000000..4baa53196 --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/redsync.go @@ -0,0 +1,73 @@ +package redsync + +import "time" + +// Redsync provides a simple method for creating distributed mutexes using multiple Redis connection pools. +type Redsync struct { + pools []Pool +} + +// New creates and returns a new Redsync instance from given Redis connection pools. +func New(pools []Pool) *Redsync { + return &Redsync{ + pools: pools, + } +} + +// NewMutex returns a new distributed mutex with given name. +func (r *Redsync) NewMutex(name string, options ...Option) *Mutex { + m := &Mutex{ + name: name, + expiry: 8 * time.Second, + tries: 32, + delay: 500 * time.Millisecond, + factor: 0.01, + quorum: len(r.pools)/2 + 1, + pools: r.pools, + } + for _, o := range options { + o.Apply(m) + } + return m +} + +// An Option configures a mutex. +type Option interface { + Apply(*Mutex) +} + +// OptionFunc is a function that configures a mutex. +type OptionFunc func(*Mutex) + +// Apply calls f(mutex) +func (f OptionFunc) Apply(mutex *Mutex) { + f(mutex) +} + +// SetExpiry can be used to set the expiry of a mutex to the given value. +func SetExpiry(expiry time.Duration) Option { + return OptionFunc(func(m *Mutex) { + m.expiry = expiry + }) +} + +// SetTries can be used to set the number of times lock acquire is attempted. +func SetTries(tries int) Option { + return OptionFunc(func(m *Mutex) { + m.tries = tries + }) +} + +// SetRetryDelay can be used to set the amount of time to wait between retries. +func SetRetryDelay(delay time.Duration) Option { + return OptionFunc(func(m *Mutex) { + m.delay = delay + }) +} + +// SetDriftFactor can be used to set the clock drift factor. +func SetDriftFactor(factor float64) Option { + return OptionFunc(func(m *Mutex) { + m.factor = factor + }) +} diff --git a/src/vendor/github.com/go-redsync/redsync/redsync_test.go b/src/vendor/github.com/go-redsync/redsync/redsync_test.go new file mode 100644 index 000000000..b0b55dcdf --- /dev/null +++ b/src/vendor/github.com/go-redsync/redsync/redsync_test.go @@ -0,0 +1,38 @@ +package redsync + +import ( + "os" + "testing" + + "github.com/stvp/tempredis" +) + +var servers []*tempredis.Server + +func TestMain(m *testing.M) { + for i := 0; i < 8; i++ { + server, err := tempredis.Start(tempredis.Config{}) + if err != nil { + panic(err) + } + servers = append(servers, server) + } + result := m.Run() + for _, server := range servers { + server.Term() + } + os.Exit(result) +} + +func TestRedsync(t *testing.T) { + pools := newMockPools(8) + rs := New(pools) + + mutex := rs.NewMutex("test-redsync") + err := mutex.Lock() + if err != nil { + + } + + assertAcquired(t, pools, mutex) +}