1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
// Copyright (c) 2014-2019 Ludovic Fauvet
// Licensed under the MIT license
package database
import (
"errors"
"math/rand"
"strconv"
"sync"
"time"
"github.com/gomodule/redigo/redis"
)
var (
ErrInvalidLockName = errors.New("invalid lock name")
ErrAlreadyLocked = errors.New("lock already acquired")
)
type Lock struct {
sync.RWMutex
redis *Redis
name string
value string
held bool
}
func init() {
rand.Seed(time.Now().UnixNano())
}
func (r *Redis) AcquireLock(name string) (*Lock, error) {
if len(name) == 0 {
return nil, ErrInvalidLockName
}
l := &Lock{
redis: r,
name: "LOCK_" + name,
value: strconv.Itoa(rand.Int()),
held: true,
}
conn := r.UnblockedGet()
defer conn.Close()
_, err := redis.String(conn.Do("SET", l.name, l.value, "NX", "PX", "5000"))
if err == redis.ErrNil {
return nil, ErrAlreadyLocked
} else if err != nil {
return nil, err
}
// Start the lock keepalive
go l.keepalive()
return l, nil
}
func (l *Lock) keepalive() {
for {
l.Lock()
if l.held == false {
l.Unlock()
return
}
l.Unlock()
valid, err := l.isValid()
if err != nil {
continue
}
if !valid {
l.Lock()
l.held = false
l.Unlock()
return
}
conn := l.redis.UnblockedGet()
ok, err := redis.Bool(conn.Do("PEXPIRE", l.name, "5000"))
conn.Close()
if err != nil {
continue
}
if !ok {
l.held = false
return
}
time.Sleep(1 * time.Second)
}
}
func (l *Lock) isValid() (bool, error) {
conn := l.redis.UnblockedGet()
defer conn.Close()
value, err := redis.String(conn.Do("GET", l.name))
if err != nil && err != redis.ErrNil {
return false, err
}
if value != l.value {
return false, nil
}
return true, nil
}
func (l *Lock) Release() {
l.Lock()
if l.held == false {
l.Unlock()
return
}
l.held = false
l.Unlock()
conn := l.redis.UnblockedGet()
defer conn.Close()
v, _ := redis.String(conn.Do("GET", l.name))
if v == l.value {
// Delete the key only if we are still the owner
conn.Do("DEL", l.name)
}
}
func (l *Lock) Held() bool {
l.RLock()
defer l.RUnlock()
return l.held
}
|