Documentation ¶
Overview ¶
Package redsync provides a Redis-based distributed mutual exclusion lock implementation as described in the post http://redis.io/topics/distlock.
See examples for suggestions on how to use the lock.
Testing with locks ¶
This package uses a combination of testing against real redis servers using tempredis, and in-memory mocking using redigomock. Clients of redsync are expected to test against redigomock, rather than having to run real redis. There are helpers available for testing with locks in the redsync/rstest package. The Mutex examples include usages of rstest, in particular rstest.AddLockExpects. Please refer to them for examples of how to use mocks when testing redsync locks.
Example ¶
package main import ( "github.com/gomodule/redigo/redis" "github.com/rgalanakis/redsync" ) func expensiveOperation() {} func main() { host := "localhost:6379" pool := &redis.Pool{Dial: redsync.TcpDialer(host)} mutex := redsync.New(pool).NewMutex("redsync-example", redsync.NonBlocking()) // Use Mutex#Lock and Mutex#Unlock manually if mutex.Lock() != nil { defer mutex.Unlock() expensiveOperation() } // Or use Mutex#WithLock to execute something conditionally. mutex.WithLock(expensiveOperation) }
Output:
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrFailed = errors.New("redsync: failed to acquire lock")
Functions ¶
Types ¶
type Dialer ¶
Dialer functions return an item with the redis.Conn interface, or an error. It fulfills the interface for the Dial argument to a redis.Pool.
func UnixDialer ¶
UnixDialer connects to an address string, like "/var/folders/6j/xyz/T/abc/redis.sock".
type Mutex ¶
type Mutex struct {
// contains filtered or unexported fields
}
Mutex is a distributed mutual exclusion lock. Note that a redsync.Mutex is not goroutine-safe. Each goroutine should create its own Mutex instance for locking. Note that Redsync instances are threadsafe, so they can be reused across goroutines.
func (*Mutex) Lock ¶
Lock acquires a lock on the mutex with the receiver's Name. If Lock returns nil, the lock is acquired. Callers should make sure Unlock is called, usually via defer m.Unlock(). If Lock returns ErrFailed, the lock could not be acquired because it was held by another mutex. Callers may wish to call Lock() again to retry. If Lock returns any other error, the lock may not be acquire-able do to an unexpected error, like if redis is not running.
Example ¶
package main import ( "fmt" "github.com/rafaeljusto/redigomock" "github.com/rgalanakis/redsync" "github.com/rgalanakis/redsync/rstest" ) func expensiveOperation() {} func main() { conn := redigomock.NewConn() rstest.AddLockExpects(conn, "example-mutex-lock", "OK") pools := rstest.PoolsForConn(conn, 1) mutex := redsync.New(pools...).NewMutex("example-mutex-lock", redsync.NonBlocking()) err := mutex.Lock() if err == redsync.ErrFailed { fmt.Println("Failed to acquire lock.") } else if err != nil { fmt.Println("Lock acquisition had unexpected error") } else { fmt.Println("Acquired lock") defer mutex.Unlock() expensiveOperation() } }
Output: Acquired lock
func (*Mutex) WithLock ¶
WithLock invokes f if the lock was successfully invoked. See Lock for more info. The boolean return value is true if the lock was acquired and f was invoked, false if not. The error is only non-nil if an unexpected error occurred. In other words, if Lock() returns ErrFailed, WithLock returns an error of nil.
Example ¶
package main import ( "fmt" "github.com/rafaeljusto/redigomock" "github.com/rgalanakis/redsync" "github.com/rgalanakis/redsync/rstest" ) func main() { conn := redigomock.NewConn() rstest.AddLockExpects(conn, "example-mutex-with-lock", nil, "OK", "err") pools := rstest.PoolsForConn(conn, 1) rs := redsync.New(pools...) result := "no calls" mutex1 := rs.NewMutex("example-mutex-with-lock", redsync.NonBlocking()) called1, err1 := mutex1.WithLock(func() { panic("Lock will not be acquired because Redis returns nil") }) mutex2 := rs.NewMutex("example-mutex-with-lock", redsync.NonBlocking()) called2, err2 := mutex2.WithLock(func() { result = "mutex2 called" }) mutex3 := rs.NewMutex("example-mutex-with-lock", redsync.NonBlocking()) called3, err3 := mutex3.WithLock(func() { panic("Lock will not be acquired because Redis returns error") }) fmt.Printf("Mutex1: called: %v,\terror: %v\n", called1, err1) fmt.Printf("Mutex2: called: %v,\terror: %v\n", called2, err2) fmt.Printf("Mutex3: called: %v,\terror: %v\n", called3, err3) fmt.Printf("Result: %v\n", result) }
Output: Mutex1: called: false, error: <nil> Mutex2: called: true, error: <nil> Mutex3: called: false, error: <nil> Result: mutex2 called
type MutexOpts ¶
type MutexOpts struct { // Expiry is the amount of time before the lock expires. // Useful to make sure the lock is expired even if the lock is never released, // like if a process dies while the lock is held. Expiry time.Duration // Tries is the number of times a lock acquisition is attempted. Tries int // Delay is the amount of time to wait between retries. Delay time.Duration // Factor is the clock drift Factor. Factor float64 }
MutexOpts are the options for mutex construction. In general, calls should use redsync.Blocking() or redsync.NonBlocking() and customize the result, but they can also create a MutexOpts themselves.
func Blocking ¶
func Blocking() MutexOpts
Blocking returns the default MutexOpts for a blocking mutex. A blocking mutex will not return from Lock until the lock is acquired, or Delay has elapsed (500ms by default).
func NonBlocking ¶
func NonBlocking() MutexOpts
NonBlocking returns the default MutexOpts for a non-blocking mutex. A non-blocking mutex gives up the first time if it cannot acquire a mutex, rather than retrying and spinning.
type Redsync ¶
type Redsync struct {
// contains filtered or unexported fields
}
Redsync is a factory for redsync.Mutex. It wraps a number of redis.Pool instances, each of which can have multiple connections. Use NewMutex to create a mutex.
func New ¶
New creates and returns a new Redsync instance from given Redis connection pools.
Example ¶
package main import ( "fmt" "github.com/gomodule/redigo/redis" "github.com/rafaeljusto/redigomock" "github.com/rgalanakis/redsync" "github.com/rgalanakis/redsync/rstest" ) func main() { var conn redis.Conn conn = redigomock.NewConn() pool := &redis.Pool{Dial: rstest.ConnDialer(conn)} mutex := redsync.New(pool).NewMutex("example-new", redsync.NonBlocking()) fmt.Println(mutex) }
Output: redsync.Mutex{name: example-new, tries: 1, expiry: 8s, poolcnt: 1}