store: Improved Lock support.

- New interface: `store.CreateLock` returns a `Locker` interface with
  `Lock` and `Unlock` functions.
- Consul: New locking implementation leveraging helper lock
  implementation that comes with the go library.
- ZK: Lock support using the library helpers.

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
Andrea Luzzardi 2015-05-12 19:03:10 -07:00
parent 8e7447c040
commit f4bd5834fa
4 changed files with 63 additions and 56 deletions

View File

@ -15,14 +15,17 @@ var (
ErrSessionUndefined = errors.New("Session does not exist") ErrSessionUndefined = errors.New("Session does not exist")
) )
// Consul embeds the client and watches/lock sessions // Consul embeds the client and watches
type Consul struct { type Consul struct {
config *api.Config config *api.Config
client *api.Client client *api.Client
sessions map[string]*api.Session
watches map[string]*Watch watches map[string]*Watch
} }
type consulLock struct {
lock *api.Lock
}
// Watch embeds the event channel and the // Watch embeds the event channel and the
// refresh interval // refresh interval
type Watch struct { type Watch struct {
@ -34,7 +37,6 @@ type Watch struct {
// a list of endpoints and optional tls config // a list of endpoints and optional tls config
func InitializeConsul(endpoints []string, options Config) (Store, error) { func InitializeConsul(endpoints []string, options Config) (Store, error) {
s := &Consul{} s := &Consul{}
s.sessions = make(map[string]*api.Session)
s.watches = make(map[string]*Watch) s.watches = make(map[string]*Watch)
// Create Consul client // Create Consul client
@ -240,38 +242,30 @@ func (s *Consul) CancelWatchRange(prefix string) error {
return s.CancelWatch(prefix) return s.CancelWatch(prefix)
} }
// Acquire the lock for "key"/"directory" // CreateLock returns a handle to a lock struct which can be used
func (s *Consul) Acquire(key string, value []byte) (string, error) { // to acquire and release the mutex.
key = partialFormat(key) func (s *Consul) CreateLock(key string, value []byte) (Locker, error) {
session := s.client.Session() l, err := s.client.LockOpts(&api.LockOptions{
id, _, err := session.CreateNoChecks(nil, nil) Key: partialFormat(key),
Value: value,
})
if err != nil { if err != nil {
return "", err return nil, err
} }
return &consulLock{lock: l}, nil
// Add session to map
s.sessions[id] = session
p := &api.KVPair{Key: key, Value: value, Session: id}
if work, _, err := s.client.KV().Acquire(p, nil); err != nil {
return "", err
} else if !work {
return "", ErrCannotLock
}
return id, nil
} }
// Release the lock for "key"/"directory" // Lock attempts to acquire the lock and blocks while doing so.
func (s *Consul) Release(id string) error { func (l *consulLock) Lock() error {
if _, ok := s.sessions[id]; !ok { // FIXME: Locks may be lost and we should watch for the returned channel.
log.Error("Lock session does not exist") _, err := l.lock.Lock(nil)
return ErrSessionUndefined return err
} }
session := s.sessions[id]
session.Destroy(id, nil) // Unlock released the lock. It is an error to call this
s.sessions[id] = nil // if the lock is not currently held.
return nil func (l *consulLock) Unlock() error {
return l.lock.Unlock()
} }
// AtomicPut put a value at "key" if the key has not been // AtomicPut put a value at "key" if the key has not been

View File

@ -252,12 +252,8 @@ func (s *Etcd) CancelWatchRange(prefix string) error {
return s.CancelWatch(format(prefix)) return s.CancelWatch(format(prefix))
} }
// Acquire the lock for "key"/"directory" // CreateLock returns a handle to a lock struct which can be used
func (s *Etcd) Acquire(key string, value []byte) (string, error) { // to acquire and release the mutex.
return "", ErrNotImplemented func (s *Etcd) CreateLock(key string, value []byte) (Locker, error) {
} return nil, ErrNotImplemented
// Release the lock for "key"/"directory"
func (s *Etcd) Release(session string) error {
return ErrNotImplemented
} }

View File

@ -36,11 +36,10 @@ type Store interface {
// Cancel watch key // Cancel watch key
CancelWatch(key string) error CancelWatch(key string) error
// Acquire the lock at key // CreateLock for a given key.
Acquire(key string, value []byte) (string, error) // The returned Locker is not held and must be acquired with `.Lock`.
// value is optional.
// Release the lock at key CreateLock(key string, value []byte) (Locker, error)
Release(session string) error
// Get range of keys based on prefix // Get range of keys based on prefix
GetRange(prefix string) ([]KVEntry, error) GetRange(prefix string) ([]KVEntry, error)
@ -68,6 +67,13 @@ type KVEntry interface {
LastIndex() uint64 LastIndex() uint64
} }
// Locker provides locking mechanism on top of the store.
// Similar to `sync.Lock` except it may return errors.
type Locker interface {
Lock() error
Unlock() error
}
var ( var (
// List of Store services // List of Store services
stores map[string]Initialize stores map[string]Initialize

View File

@ -16,6 +16,10 @@ type Zookeeper struct {
watches map[string]<-chan zk.Event watches map[string]<-chan zk.Event
} }
type zookeeperLock struct {
lock *zk.Lock
}
// InitializeZookeeper creates a new Zookeeper client // InitializeZookeeper creates a new Zookeeper client
// given a list of endpoints and optional tls config // given a list of endpoints and optional tls config
func InitializeZookeeper(endpoints []string, options Config) (Store, error) { func InitializeZookeeper(endpoints []string, options Config) (Store, error) {
@ -198,15 +202,22 @@ func (s *Zookeeper) AtomicDelete(key string, oldValue []byte, index uint64) (boo
return false, ErrNotImplemented return false, ErrNotImplemented
} }
// Acquire the lock for "key"/"directory" // CreateLock returns a handle to a lock struct which can be used
func (s *Zookeeper) Acquire(path string, value []byte) (string, error) { // to acquire and release the mutex.
// lock := zk.NewLock(s.client, path, nil) func (s *Zookeeper) CreateLock(key string, value []byte) (Locker, error) {
// locks[path] = lock // FIXME: `value` is not being used since there is no support in zk.NewLock().
// lock.Lock() return &zookeeperLock{
return "", ErrNotImplemented lock: zk.NewLock(s.client, format(key), zk.WorldACL(zk.PermAll)),
}, nil
} }
// Release the lock for "key"/"directory" // Lock attempts to acquire the lock and blocks while doing so.
func (s *Zookeeper) Release(session string) error { func (l *zookeeperLock) Lock() error {
return ErrNotImplemented return l.lock.Lock()
}
// Unlock released the lock. It is an error to call this
// if the lock is not currently held.
func (l *zookeeperLock) Unlock() error {
return l.lock.Unlock()
} }