Add an SHM-backed Lock Manager implementation

Signed-off-by: Matthew Heon <matthew.heon@gmail.com>
This commit is contained in:
Matthew Heon
2018-08-08 12:26:01 -04:00
committed by Matthew Heon
parent 185136cf0e
commit 3ed81051e8
4 changed files with 119 additions and 11 deletions

View File

@@ -1,7 +1,7 @@
package lock
// LockManager provides an interface for allocating multiprocess locks.
// Locks returned by LockManager MUST be multiprocess - allocating a lock in
// Manager provides an interface for allocating multiprocess locks.
// Locks returned by Manager MUST be multiprocess - allocating a lock in
// process A and retrieving that lock's ID in process B must return handles for
// the same lock, and locking the lock in A should exclude B from the lock until
// it is unlocked in A.
@@ -13,7 +13,7 @@ package lock
// AllocateLock() must fail once all available locks have been allocated.
// Locks are returned to use by calls to Free(), and can subsequently be
// reallocated.
type LockManager interface {
type Manager interface {
// AllocateLock returns an unallocated lock.
// It is guaranteed that the same lock will not be returned again by
// AllocateLock until the returned lock has Free() called on it.
@@ -35,7 +35,7 @@ type LockManager interface {
type Locker interface {
// ID retrieves the lock's ID.
// ID is guaranteed to uniquely identify the lock within the
// LockManager - that is, calling RetrieveLock with this ID will return
// Manager - that is, calling RetrieveLock with this ID will return
// another instance of the same lock.
ID() string
// Lock locks the lock.

View File

@@ -27,13 +27,13 @@ type SHMLocks struct {
// semaphores, and returns a struct that can be used to operate on those locks.
// numLocks must be a multiple of the lock bitmap size (by default, 32).
func CreateSHMLock(numLocks uint32) (*SHMLocks, error) {
if numLocks % bitmapSize != 0 || numLocks == 0 {
if numLocks%bitmapSize != 0 || numLocks == 0 {
return nil, errors.Wrapf(syscall.EINVAL, "number of locks must be a multiple of %d", C.bitmap_size_c)
}
locks := new(SHMLocks)
var errCode C.int = 0
var errCode C.int
lockStruct := C.setup_lock_shm(C.uint32_t(numLocks), &errCode)
if lockStruct == nil {
// We got a null pointer, so something errored
@@ -52,13 +52,13 @@ func CreateSHMLock(numLocks uint32) (*SHMLocks, error) {
// segment was created with and be a multiple of the lock bitmap size (default
// 32).
func OpenSHMLock(numLocks uint32) (*SHMLocks, error) {
if numLocks % bitmapSize != 0 || numLocks == 0 {
if numLocks%bitmapSize != 0 || numLocks == 0 {
return nil, errors.Wrapf(syscall.EINVAL, "number of locks must be a multiple of %d", C.bitmap_size_c)
}
locks := new(SHMLocks)
var errCode C.int = 0
var errCode C.int
lockStruct := C.open_lock_shm(C.uint32_t(numLocks), &errCode)
if lockStruct == nil {
// We got a null pointer, so something errored

View File

@@ -0,0 +1,109 @@
package lock
import (
"fmt"
"math"
"strconv"
"syscall"
"github.com/pkg/errors"
)
// SHMLockManager manages shared memory locks.
type SHMLockManager struct {
locks *SHMLocks
}
// NewSHMLockManager makes a new SHMLockManager with the given number of locks.
func NewSHMLockManager(numLocks uint32) (Manager, error) {
locks, err := CreateSHMLock(numLocks)
if err != nil {
return nil, err
}
manager := new(SHMLockManager)
manager.locks = locks
return manager, nil
}
// OpenSHMLockManager opens an existing SHMLockManager with the given number of
// locks.
func OpenSHMLockManager(numLocks uint32) (LockManager, error) {
locks, err := OpenSHMLock(numLocks)
if err != nil {
return nil, err
}
manager := new(SHMLockManager)
manager.locks = locks
return manager, nil
}
// AllocateLock allocates a new lock from the manager.
func (m *SHMLockManager) AllocateLock() (Locker, error) {
semIndex, err := m.locks.AllocateSemaphore()
if err != nil {
return nil, err
}
lock := new(SHMLock)
lock.lockID = semIndex
lock.manager = m
return lock, nil
}
// RetrieveLock retrieves a lock from the manager given its ID.
func (m *SHMLockManager) RetrieveLock(id string) (Locker, error) {
intID, err := strconv.ParseInt(id, 16, 64)
if err != nil {
return errors.Wrapf(err, "given ID %q is not a valid SHMLockManager ID - cannot be parsed as int", id)
}
if intID < 0 {
return errors.Wrapf(syscall.EINVAL, "given ID %q is not a valid SHMLockManager ID - must be positive", id)
}
if intID > math.MaxUint32 {
return errors.Wrapf(syscall.EINVAL, "given ID %q is not a valid SHMLockManager ID - too large", id)
}
var u32ID uint32 = uint32(intID)
if u32ID >= m.locks.maxLocks {
return errors.Wrapf(syscall.EINVAL, "given ID %q is not a valid SHMLockManager ID - too large to fit", id)
}
lock := new(SHMLock)
lock.lockID = u32ID
lock.manager = m
return lock, nil
}
// SHMLock is an individual shared memory lock.
type SHMLock struct {
lockID uint32
manager *SHMLockManager
}
// ID returns the ID of the lock.
func (l *SHMLock) ID() string {
return fmt.Sprintf("%x", l.lockID)
}
// Lock acquires the lock.
func (l *SHMLock) Lock() error {
return l.manager.locks.LockSemaphore(l.lockID)
}
// Unlock releases the lock.
func (l *SHMLock) Unlock() error {
return l.manager.locks.UnlockSemaphore(l.lockID)
}
// Free releases the lock, allowing it to be reused.
func (l *SHMLock) Free() error {
return l.manager.locks.DeallocateSemaphore(l.lockID)
}

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"os"
"syscall"
"time"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -41,7 +41,6 @@ func TestMain(m *testing.M) {
os.Exit(exitCode)
}
func runLockTest(t *testing.T, testFunc func(*testing.T, *SHMLocks)) {
locks, err := OpenSHMLock(numLocks)
if err != nil {
@@ -66,7 +65,7 @@ func runLockTest(t *testing.T, testFunc func(*testing.T, *SHMLocks)) {
}
}()
success := t.Run("locks", func (t *testing.T) {
success := t.Run("locks", func(t *testing.T) {
testFunc(t, locks)
})
if !success {