api: Adding Destroy to cleanup a lock

This commit is contained in:
Armon Dadgar 2015-01-13 14:01:50 -08:00
parent 18ebb1a8e9
commit a12883a5be
2 changed files with 112 additions and 0 deletions

View File

@ -33,6 +33,10 @@ var (
// ErrLockNotHeld is returned if we attempt to unlock a lock // ErrLockNotHeld is returned if we attempt to unlock a lock
// that we do not hold. // that we do not hold.
ErrLockNotHeld = fmt.Errorf("Lock not held") ErrLockNotHeld = fmt.Errorf("Lock not held")
// ErrLockInUse is returned if we attempt to destroy a lock
// that is in use.
ErrLockInUse = fmt.Errorf("Lock in use")
) )
// Lock is used to implement client-side leader election. It is follows the // Lock is used to implement client-side leader election. It is follows the
@ -217,6 +221,46 @@ func (l *Lock) Unlock() error {
return nil return nil
} }
// Destroy is used to cleanup the lock entry. It is not necessary
// to invoke. It will fail if the lock is in use.
func (l *Lock) Destroy() error {
// Hold the lock as we try to release
l.l.Lock()
defer l.l.Unlock()
// Check if we already hold the lock
if l.isHeld {
return ErrLockHeld
}
// Look for an existing lock
kv := l.c.KV()
pair, _, err := kv.Get(l.opts.Key, nil)
if err != nil {
return fmt.Errorf("failed to read lock: %v", err)
}
// Nothing to do if the lock does not exist
if pair == nil {
return nil
}
// Check if it is in use
if pair.Session != "" {
return ErrLockInUse
}
// Attempt the delete
didRemove, _, err := kv.DeleteCAS(pair, nil)
if err != nil {
return fmt.Errorf("failed to remove lock: %v", err)
}
if !didRemove {
return ErrLockInUse
}
return nil
}
// createSession is used to create a new managed session // createSession is used to create a new managed session
func (l *Lock) createSession() (string, error) { func (l *Lock) createSession() (string, error) {
session := l.c.Session() session := l.c.Session()

View File

@ -182,3 +182,71 @@ func TestLock_Contend(t *testing.T) {
} }
} }
} }
func TestLock_Destroy(t *testing.T) {
c, s := makeClient(t)
defer s.stop()
lock, err := c.LockKey("test/lock")
if err != nil {
t.Fatalf("err: %v", err)
}
// Should work
leaderCh, err := lock.Lock(nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if leaderCh == nil {
t.Fatalf("not leader")
}
// Destroy should fail
if err := lock.Destroy(); err != ErrLockHeld {
t.Fatalf("err: %v", err)
}
// Should be able to release
err = lock.Unlock()
if err != nil {
t.Fatalf("err: %v", err)
}
// Acquire with a different lock
l2, err := c.LockKey("test/lock")
if err != nil {
t.Fatalf("err: %v", err)
}
// Should work
leaderCh, err = l2.Lock(nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if leaderCh == nil {
t.Fatalf("not leader")
}
// Destroy should still fail
if err := lock.Destroy(); err != ErrLockInUse {
t.Fatalf("err: %v", err)
}
// Should relese
err = l2.Unlock()
if err != nil {
t.Fatalf("err: %v", err)
}
// Destroy should work
err = lock.Destroy()
if err != nil {
t.Fatalf("err: %v", err)
}
// Double destroy should work
err = l2.Destroy()
if err != nil {
t.Fatalf("err: %v", err)
}
}