diff options
Diffstat (limited to 'libpod/lock/shm')
-rw-r--r-- | libpod/lock/shm/shm_lock.c | 452 | ||||
-rw-r--r-- | libpod/lock/shm/shm_lock.go | 216 | ||||
-rw-r--r-- | libpod/lock/shm/shm_lock.h | 46 | ||||
-rw-r--r-- | libpod/lock/shm/shm_lock_test.go | 278 |
4 files changed, 0 insertions, 992 deletions
diff --git a/libpod/lock/shm/shm_lock.c b/libpod/lock/shm/shm_lock.c deleted file mode 100644 index 4af58d857..000000000 --- a/libpod/lock/shm/shm_lock.c +++ /dev/null @@ -1,452 +0,0 @@ -#include <errno.h> -#include <fcntl.h> -#include <pthread.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include "shm_lock.h" - -// Compute the size of the SHM struct -static size_t compute_shm_size(uint32_t num_bitmaps) { - return sizeof(shm_struct_t) + (num_bitmaps * sizeof(lock_group_t)); -} - -// Take the given mutex. -// Handles exceptional conditions, including a mutex locked by a process that -// died holding it. -// Returns 0 on success, or positive errno on failure. -static int take_mutex(pthread_mutex_t *mutex) { - int ret_code; - - do { - ret_code = pthread_mutex_lock(mutex); - } while(ret_code == EAGAIN); - - if (ret_code == EOWNERDEAD) { - // The previous owner of the mutex died while holding it - // Take it for ourselves - ret_code = pthread_mutex_consistent(mutex); - if (ret_code != 0) { - // Someone else may have gotten here first and marked the state consistent - // However, the mutex could also be invalid. - // Fail here instead of looping back to trying to lock the mutex. - return ret_code; - } - } else if (ret_code != 0) { - return ret_code; - } - - return 0; -} - -// Release the given mutex. -// Returns 0 on success, or positive errno on failure. -static int release_mutex(pthread_mutex_t *mutex) { - int ret_code; - - do { - ret_code = pthread_mutex_unlock(mutex); - } while(ret_code == EAGAIN); - - if (ret_code != 0) { - return ret_code; - } - - return 0; -} - -// Set up an SHM segment holding locks for libpod. -// num_locks must not be 0. -// Path is the path to the SHM segment. It must begin with a single / and -// container no other / characters, and be at most 255 characters including -// terminating NULL byte. -// Returns a valid pointer on success or NULL on error. -// If an error occurs, negative ERRNO values will be written to error_code. -shm_struct_t *setup_lock_shm(char *path, uint32_t num_locks, int *error_code) { - int shm_fd, i, j, ret_code; - uint32_t num_bitmaps; - size_t shm_size; - shm_struct_t *shm; - pthread_mutexattr_t attr; - - // If error_code doesn't point to anything, we can't reasonably return errors - // So fail immediately - if (error_code == NULL) { - return NULL; - } - - // We need a nonzero number of locks - if (num_locks == 0) { - *error_code = -1 * EINVAL; - return NULL; - } - - if (path == NULL) { - *error_code = -1 * EINVAL; - return NULL; - } - - // Calculate the number of bitmaps required - num_bitmaps = num_locks / BITMAP_SIZE; - if (num_locks % BITMAP_SIZE != 0) { - // The actual number given is not an even multiple of our bitmap size - // So round up - num_bitmaps += 1; - } - - // Calculate size of the shm segment - shm_size = compute_shm_size(num_bitmaps); - - // Create a new SHM segment for us - shm_fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600); - if (shm_fd < 0) { - *error_code = -1 * errno; - return NULL; - } - - // Increase its size to what we need - ret_code = ftruncate(shm_fd, shm_size); - if (ret_code < 0) { - *error_code = -1 * errno; - goto CLEANUP_UNLINK; - } - - // Map the shared memory in - shm = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); - if (shm == MAP_FAILED) { - *error_code = -1 * errno; - goto CLEANUP_UNLINK; - } - - // We have successfully mapped the memory, now initialize the region - shm->magic = MAGIC; - shm->unused = 0; - shm->num_locks = num_bitmaps * BITMAP_SIZE; - shm->num_bitmaps = num_bitmaps; - - // Create an initializer for our pthread mutexes - ret_code = pthread_mutexattr_init(&attr); - if (ret_code != 0) { - *error_code = -1 * ret_code; - goto CLEANUP_UNMAP; - } - - // Set mutexes to pshared - multiprocess-safe - ret_code = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); - if (ret_code != 0) { - *error_code = -1 * ret_code; - goto CLEANUP_FREEATTR; - } - - // Set mutexes to robust - if a process dies while holding a mutex, we'll get - // a special error code on the next attempt to lock it. - // This should prevent panicing processes from leaving the state unusable. - ret_code = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST); - if (ret_code != 0) { - *error_code = -1 * ret_code; - goto CLEANUP_FREEATTR; - } - - // Initialize the mutex that protects the bitmaps using the mutex attributes - ret_code = pthread_mutex_init(&(shm->segment_lock), &attr); - if (ret_code != 0) { - *error_code = -1 * ret_code; - goto CLEANUP_FREEATTR; - } - - // Initialize all bitmaps to 0 initially - // And initialize all semaphores they use - for (i = 0; i < num_bitmaps; i++) { - shm->locks[i].bitmap = 0; - for (j = 0; j < BITMAP_SIZE; j++) { - // Initialize each mutex - ret_code = pthread_mutex_init(&(shm->locks[i].locks[j]), &attr); - if (ret_code != 0) { - *error_code = -1 * ret_code; - goto CLEANUP_FREEATTR; - } - } - } - - // Close the file descriptor, we're done with it - // Ignore errors, it's ok if we leak a single FD and this should only run once - close(shm_fd); - - // Destroy the pthread initializer attribute. - // Again, ignore errors, this will only run once and we might leak a tiny bit - // of memory at worst. - pthread_mutexattr_destroy(&attr); - - return shm; - - // Cleanup after an error - CLEANUP_FREEATTR: - pthread_mutexattr_destroy(&attr); - CLEANUP_UNMAP: - munmap(shm, shm_size); - CLEANUP_UNLINK: - close(shm_fd); - shm_unlink(path); - return NULL; -} - -// Open an existing SHM segment holding libpod locks. -// num_locks is the number of locks that will be configured in the SHM segment. -// num_locks cannot be 0. -// Path is the path to the SHM segment. It must begin with a single / and -// container no other / characters, and be at most 255 characters including -// terminating NULL byte. -// Returns a valid pointer on success or NULL on error. -// If an error occurs, negative ERRNO values will be written to error_code. -shm_struct_t *open_lock_shm(char *path, uint32_t num_locks, int *error_code) { - int shm_fd; - shm_struct_t *shm; - size_t shm_size; - uint32_t num_bitmaps; - - if (error_code == NULL) { - return NULL; - } - - // We need a nonzero number of locks - if (num_locks == 0) { - *error_code = -1 * EINVAL; - return NULL; - } - - if (path == NULL) { - *error_code = -1 * EINVAL; - return NULL; - } - - // Calculate the number of bitmaps required - num_bitmaps = num_locks / BITMAP_SIZE; - if (num_locks % BITMAP_SIZE != 0) { - num_bitmaps += 1; - } - - // Calculate size of the shm segment - shm_size = compute_shm_size(num_bitmaps); - - shm_fd = shm_open(path, O_RDWR, 0600); - if (shm_fd < 0) { - *error_code = -1 * errno; - return NULL; - } - - // Map the shared memory in - shm = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); - if (shm == MAP_FAILED) { - *error_code = -1 * errno; - } - - // Ignore errors, it's ok if we leak a single FD since this only runs once - close(shm_fd); - - // Check if we successfully mmap'd - if (shm == MAP_FAILED) { - return NULL; - } - - // Need to check the SHM to see if it's actually our locks - if (shm->magic != MAGIC) { - *error_code = -1 * errno; - goto CLEANUP; - } - if (shm->num_locks != (num_bitmaps * BITMAP_SIZE)) { - *error_code = -1 * errno; - goto CLEANUP; - } - - return shm; - - CLEANUP: - munmap(shm, shm_size); - return NULL; -} - -// Close an open SHM lock struct, unmapping the backing memory. -// The given shm_struct_t will be rendered unusable as a result. -// On success, 0 is returned. On failure, negative ERRNO values are returned. -int32_t close_lock_shm(shm_struct_t *shm) { - int ret_code; - size_t shm_size; - - // We can't unmap null... - if (shm == NULL) { - return -1 * EINVAL; - } - - shm_size = compute_shm_size(shm->num_bitmaps); - - ret_code = munmap(shm, shm_size); - - if (ret_code != 0) { - return -1 * errno; - } - - return 0; -} - -// Allocate the first available semaphore -// Returns a positive integer guaranteed to be less than UINT32_MAX on success, -// or negative errno values on failure -// On sucess, the returned integer is the number of the semaphore allocated -int64_t allocate_semaphore(shm_struct_t *shm) { - int ret_code, i; - bitmap_t test_map; - int64_t sem_number, num_within_bitmap; - - if (shm == NULL) { - return -1 * EINVAL; - } - - // Lock the semaphore controlling access to our shared memory - ret_code = take_mutex(&(shm->segment_lock)); - if (ret_code != 0) { - return -1 * ret_code; - } - - // Loop through our bitmaps to search for one that is not full - for (i = 0; i < shm->num_bitmaps; i++) { - if (shm->locks[i].bitmap != 0xFFFFFFFF) { - test_map = 0x1; - num_within_bitmap = 0; - while (test_map != 0) { - if ((test_map & shm->locks[i].bitmap) == 0) { - // Compute the number of the semaphore we are allocating - sem_number = (BITMAP_SIZE * i) + num_within_bitmap; - // OR in the bitmap - shm->locks[i].bitmap = shm->locks[i].bitmap | test_map; - - // Clear the mutex - ret_code = release_mutex(&(shm->segment_lock)); - if (ret_code != 0) { - return -1 * ret_code; - } - - // Return the semaphore we've allocated - return sem_number; - } - test_map = test_map << 1; - num_within_bitmap++; - } - // We should never fall through this loop - // TODO maybe an assert() here to panic if we do? - } - } - - // Clear the mutex - ret_code = release_mutex(&(shm->segment_lock)); - if (ret_code != 0) { - return -1 * ret_code; - } - - // All bitmaps are full - // We have no available semaphores, report allocation failure - return -1 * ENOSPC; -} - -// Deallocate a given semaphore -// Returns 0 on success, negative ERRNO values on failure -int32_t deallocate_semaphore(shm_struct_t *shm, uint32_t sem_index) { - bitmap_t test_map; - int bitmap_index, index_in_bitmap, ret_code, i; - - if (shm == NULL) { - return -1 * EINVAL; - } - - // Check if the lock index is valid - if (sem_index >= shm->num_locks) { - return -1 * EINVAL; - } - - bitmap_index = sem_index / BITMAP_SIZE; - index_in_bitmap = sem_index % BITMAP_SIZE; - - // This should never happen if the sem_index test above succeeded, but better - // safe than sorry - if (bitmap_index >= shm->num_bitmaps) { - return -1 * EFAULT; - } - - test_map = 0x1 << index_in_bitmap; - - // Lock the mutex controlling access to our shared memory - ret_code = take_mutex(&(shm->segment_lock)); - if (ret_code != 0) { - return -1 * ret_code; - } - - // Check if the semaphore is allocated - if ((test_map & shm->locks[bitmap_index].bitmap) == 0) { - ret_code = release_mutex(&(shm->segment_lock)); - if (ret_code != 0) { - return -1 * ret_code; - } - - return -1 * ENOENT; - } - - // The semaphore is allocated, clear it - // Invert the bitmask we used to test to clear the bit - test_map = ~test_map; - shm->locks[bitmap_index].bitmap = shm->locks[bitmap_index].bitmap & test_map; - - ret_code = release_mutex(&(shm->segment_lock)); - if (ret_code != 0) { - return -1 * ret_code; - } - - return 0; -} - -// Lock a given semaphore -// Does not check if the semaphore is allocated - this ensures that, even for -// removed containers, we can still successfully lock to check status (and -// subsequently realize they have been removed). -// Returns 0 on success, -1 on failure -int32_t lock_semaphore(shm_struct_t *shm, uint32_t sem_index) { - int bitmap_index, index_in_bitmap, ret_code; - - if (shm == NULL) { - return -1 * EINVAL; - } - - if (sem_index >= shm->num_locks) { - return -1 * EINVAL; - } - - bitmap_index = sem_index / BITMAP_SIZE; - index_in_bitmap = sem_index % BITMAP_SIZE; - - return -1 * take_mutex(&(shm->locks[bitmap_index].locks[index_in_bitmap])); -} - -// Unlock a given semaphore -// Does not check if the semaphore is allocated - this ensures that, even for -// removed containers, we can still successfully lock to check status (and -// subsequently realize they have been removed). -// Returns 0 on success, -1 on failure -int32_t unlock_semaphore(shm_struct_t *shm, uint32_t sem_index) { - int bitmap_index, index_in_bitmap, ret_code; - - if (shm == NULL) { - return -1 * EINVAL; - } - - if (sem_index >= shm->num_locks) { - return -1 * EINVAL; - } - - bitmap_index = sem_index / BITMAP_SIZE; - index_in_bitmap = sem_index % BITMAP_SIZE; - - return -1 * release_mutex(&(shm->locks[bitmap_index].locks[index_in_bitmap])); -} diff --git a/libpod/lock/shm/shm_lock.go b/libpod/lock/shm/shm_lock.go deleted file mode 100644 index be5e5148f..000000000 --- a/libpod/lock/shm/shm_lock.go +++ /dev/null @@ -1,216 +0,0 @@ -package shm - -// #cgo LDFLAGS: -lrt -lpthread -// #include <stdlib.h> -// #include "shm_lock.h" -// const uint32_t bitmap_size_c = BITMAP_SIZE; -import "C" - -import ( - "runtime" - "syscall" - "unsafe" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -var ( - // BitmapSize is the size of the bitmap used when managing SHM locks. - // an SHM lock manager's max locks will be rounded up to a multiple of - // this number. - BitmapSize uint32 = uint32(C.bitmap_size_c) -) - -// SHMLocks is a struct enabling POSIX semaphore locking in a shared memory -// segment. -type SHMLocks struct { // nolint - lockStruct *C.shm_struct_t - maxLocks uint32 - valid bool -} - -// CreateSHMLock sets up a shared-memory segment holding a given number of POSIX -// semaphores, and returns a struct that can be used to operate on those locks. -// numLocks must not be 0, and may be rounded up to a multiple of the bitmap -// size used by the underlying implementation. -func CreateSHMLock(path string, numLocks uint32) (*SHMLocks, error) { - if numLocks == 0 { - return nil, errors.Wrapf(syscall.EINVAL, "number of locks must greater than 0 0") - } - - locks := new(SHMLocks) - - cPath := C.CString(path) - defer C.free(unsafe.Pointer(cPath)) - - var errCode C.int - lockStruct := C.setup_lock_shm(cPath, C.uint32_t(numLocks), &errCode) - if lockStruct == nil { - // We got a null pointer, so something errored - return nil, errors.Wrapf(syscall.Errno(-1*errCode), "failed to create %d locks in %s", numLocks, path) - } - - locks.lockStruct = lockStruct - locks.maxLocks = uint32(lockStruct.num_locks) - locks.valid = true - - logrus.Debugf("Initialized SHM lock manager at path %s", path) - - return locks, nil -} - -// OpenSHMLock opens an existing shared-memory segment holding a given number of -// POSIX semaphores. numLocks must match the number of locks the shared memory -// segment was created with. -func OpenSHMLock(path string, numLocks uint32) (*SHMLocks, error) { - if numLocks == 0 { - return nil, errors.Wrapf(syscall.EINVAL, "number of locks must greater than 0") - } - - locks := new(SHMLocks) - - cPath := C.CString(path) - defer C.free(unsafe.Pointer(cPath)) - - var errCode C.int - lockStruct := C.open_lock_shm(cPath, C.uint32_t(numLocks), &errCode) - if lockStruct == nil { - // We got a null pointer, so something errored - return nil, errors.Wrapf(syscall.Errno(-1*errCode), "failed to open %d locks in %s", numLocks, path) - } - - locks.lockStruct = lockStruct - locks.maxLocks = numLocks - locks.valid = true - - return locks, nil -} - -// GetMaxLocks returns the maximum number of locks in the SHM -func (locks *SHMLocks) GetMaxLocks() uint32 { - return locks.maxLocks -} - -// Close closes an existing shared-memory segment. -// The segment will be rendered unusable after closing. -// WARNING: If you Close() while there are still locks locked, these locks may -// fail to release, causing a program freeze. -// Close() is only intended to be used while testing the locks. -func (locks *SHMLocks) Close() error { - if !locks.valid { - return errors.Wrapf(syscall.EINVAL, "locks have already been closed") - } - - locks.valid = false - - retCode := C.close_lock_shm(locks.lockStruct) - if retCode < 0 { - // Negative errno returned - return syscall.Errno(-1 * retCode) - } - - return nil -} - -// AllocateSemaphore allocates a semaphore from a shared-memory segment for use -// by a container or pod. -// Returns the index of the semaphore that was allocated. -// Allocations past the maximum number of locks given when the SHM segment was -// created will result in an error, and no semaphore will be allocated. -func (locks *SHMLocks) AllocateSemaphore() (uint32, error) { - if !locks.valid { - return 0, errors.Wrapf(syscall.EINVAL, "locks have already been closed") - } - - // This returns a U64, so we have the full u32 range available for - // semaphore indexes, and can still return error codes. - retCode := C.allocate_semaphore(locks.lockStruct) - if retCode < 0 { - // Negative errno returned - return 0, syscall.Errno(-1 * retCode) - } - - return uint32(retCode), nil -} - -// DeallocateSemaphore frees a semaphore in a shared-memory segment so it can be -// reallocated to another container or pod. -// The given semaphore must be already allocated, or an error will be returned. -func (locks *SHMLocks) DeallocateSemaphore(sem uint32) error { - if !locks.valid { - return errors.Wrapf(syscall.EINVAL, "locks have already been closed") - } - - if sem > locks.maxLocks { - return errors.Wrapf(syscall.EINVAL, "given semaphore %d is higher than maximum locks count %d", sem, locks.maxLocks) - } - - retCode := C.deallocate_semaphore(locks.lockStruct, C.uint32_t(sem)) - if retCode < 0 { - // Negative errno returned - return syscall.Errno(-1 * retCode) - } - - return nil -} - -// LockSemaphore locks the given semaphore. -// If the semaphore is already locked, LockSemaphore will block until the lock -// can be acquired. -// There is no requirement that the given semaphore be allocated. -// This ensures that attempts to lock a container after it has been deleted, -// but before the caller has queried the database to determine this, will -// succeed. -func (locks *SHMLocks) LockSemaphore(sem uint32) error { - if !locks.valid { - return errors.Wrapf(syscall.EINVAL, "locks have already been closed") - } - - if sem > locks.maxLocks { - return errors.Wrapf(syscall.EINVAL, "given semaphore %d is higher than maximum locks count %d", sem, locks.maxLocks) - } - - // For pthread mutexes, we have to guarantee lock and unlock happen in - // the same thread. - runtime.LockOSThread() - - retCode := C.lock_semaphore(locks.lockStruct, C.uint32_t(sem)) - if retCode < 0 { - // Negative errno returned - return syscall.Errno(-1 * retCode) - } - - return nil -} - -// UnlockSemaphore unlocks the given semaphore. -// Unlocking a semaphore that is already unlocked with return EBUSY. -// There is no requirement that the given semaphore be allocated. -// This ensures that attempts to lock a container after it has been deleted, -// but before the caller has queried the database to determine this, will -// succeed. -func (locks *SHMLocks) UnlockSemaphore(sem uint32) error { - if !locks.valid { - return errors.Wrapf(syscall.EINVAL, "locks have already been closed") - } - - if sem > locks.maxLocks { - return errors.Wrapf(syscall.EINVAL, "given semaphore %d is higher than maximum locks count %d", sem, locks.maxLocks) - } - - retCode := C.unlock_semaphore(locks.lockStruct, C.uint32_t(sem)) - if retCode < 0 { - // Negative errno returned - return syscall.Errno(-1 * retCode) - } - - // For pthread mutexes, we have to guarantee lock and unlock happen in - // the same thread. - // OK if we take multiple locks - UnlockOSThread() won't actually unlock - // until the number of calls equals the number of calls to - // LockOSThread() - runtime.UnlockOSThread() - - return nil -} diff --git a/libpod/lock/shm/shm_lock.h b/libpod/lock/shm/shm_lock.h deleted file mode 100644 index 8e7e23fb7..000000000 --- a/libpod/lock/shm/shm_lock.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef shm_locks_h_ -#define shm_locks_h_ - -#include <pthread.h> -#include <stdint.h> - -// Magic number to ensure we open the right SHM segment -#define MAGIC 0x87D1 - -// Type for our bitmaps -typedef uint32_t bitmap_t; - -// bitmap size -#define BITMAP_SIZE (sizeof(bitmap_t) * 8) - -// Struct to hold a single bitmap and associated locks -typedef struct lock_group { - bitmap_t bitmap; - pthread_mutex_t locks[BITMAP_SIZE]; -} lock_group_t; - -// Struct to hold our SHM locks. -// Unused is required to be 0 in the current implementation. If we ever make -// changes to this structure in the future, this will be repurposed as a version -// field. -typedef struct shm_struct { - uint16_t magic; - uint16_t unused; - pthread_mutex_t segment_lock; - uint32_t num_bitmaps; - uint32_t num_locks; - lock_group_t locks[]; -} shm_struct_t; - -static size_t compute_shm_size(uint32_t num_bitmaps); -static int take_mutex(pthread_mutex_t *mutex); -static int release_mutex(pthread_mutex_t *mutex); -shm_struct_t *setup_lock_shm(char *path, uint32_t num_locks, int *error_code); -shm_struct_t *open_lock_shm(char *path, uint32_t num_locks, int *error_code); -int32_t close_lock_shm(shm_struct_t *shm); -int64_t allocate_semaphore(shm_struct_t *shm); -int32_t deallocate_semaphore(shm_struct_t *shm, uint32_t sem_index); -int32_t lock_semaphore(shm_struct_t *shm, uint32_t sem_index); -int32_t unlock_semaphore(shm_struct_t *shm, uint32_t sem_index); - -#endif diff --git a/libpod/lock/shm/shm_lock_test.go b/libpod/lock/shm/shm_lock_test.go deleted file mode 100644 index 0f3a96cca..000000000 --- a/libpod/lock/shm/shm_lock_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package shm - -import ( - "fmt" - "os" - "runtime" - "syscall" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// All tests here are in the same process, which somewhat limits their utility -// The big intent of this package it multiprocess locking, which is really hard -// to test without actually having multiple processes... -// We can at least verify that the locks work within the local process. - -var ( - // 4 * BITMAP_SIZE to ensure we have to traverse bitmaps - numLocks = 4 * BitmapSize -) - -const lockPath = "/libpod_test" - -// We need a test main to ensure that the SHM is created before the tests run -func TestMain(m *testing.M) { - shmLock, err := CreateSHMLock(lockPath, numLocks) - if err != nil { - fmt.Fprintf(os.Stderr, "Error creating SHM for tests: %v\n", err) - os.Exit(-1) - } - - // Close the SHM - every subsequent test will reopen - if err := shmLock.Close(); err != nil { - fmt.Fprintf(os.Stderr, "Error closing SHM locks: %v\n", err) - os.Exit(-1) - } - - exitCode := m.Run() - - // We need to remove the SHM segment to clean up after ourselves - os.RemoveAll("/dev/shm/libpod_lock") - - os.Exit(exitCode) -} - -func runLockTest(t *testing.T, testFunc func(*testing.T, *SHMLocks)) { - locks, err := OpenSHMLock(lockPath, numLocks) - if err != nil { - t.Fatalf("Error opening locks: %v", err) - } - defer func() { - // Deallocate all locks - // Ignore ENOENT (lock is not allocated) - var i uint32 - for i = 0; i < numLocks; i++ { - if err := locks.DeallocateSemaphore(i); err != nil && err != syscall.ENOENT { - t.Fatalf("Error deallocating semaphore %d: %v", i, err) - } - } - - if err := locks.Close(); err != nil { - t.Fatalf("Error closing locks: %v", err) - } - }() - - success := t.Run("locks", func(t *testing.T) { - testFunc(t, locks) - }) - if !success { - t.Fail() - } -} - -// Test that creating an SHM with a bad size rounds up to a good size -func TestCreateNewSHMBadSizeRoundsUp(t *testing.T) { - // Odd number, not a power of 2, should never be a word size on a system - lock, err := CreateSHMLock("/test1", 7) - assert.NoError(t, err) - - assert.Equal(t, lock.GetMaxLocks(), BitmapSize) - - if err := lock.Close(); err != nil { - t.Fatalf("Error closing locks: %v", err) - } -} - -// Test that creating an SHM with 0 size fails -func TestCreateNewSHMZeroSize(t *testing.T) { - _, err := CreateSHMLock("/test2", 0) - assert.Error(t, err) -} - -// Test that deallocating an unallocated lock errors -func TestDeallocateUnallocatedLockErrors(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - err := locks.DeallocateSemaphore(0) - assert.Error(t, err) - }) -} - -// Test that unlocking an unlocked lock fails -func TestUnlockingUnlockedLockFails(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - err := locks.UnlockSemaphore(0) - assert.Error(t, err) - }) -} - -// Test that locking and double-unlocking fails -func TestDoubleUnlockFails(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - err := locks.LockSemaphore(0) - assert.NoError(t, err) - - err = locks.UnlockSemaphore(0) - assert.NoError(t, err) - - err = locks.UnlockSemaphore(0) - assert.Error(t, err) - }) -} - -// Test allocating - lock - unlock - deallocate cycle, single lock -func TestLockLifecycleSingleLock(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - sem, err := locks.AllocateSemaphore() - require.NoError(t, err) - - err = locks.LockSemaphore(sem) - assert.NoError(t, err) - - err = locks.UnlockSemaphore(sem) - assert.NoError(t, err) - - err = locks.DeallocateSemaphore(sem) - assert.NoError(t, err) - }) -} - -// Test allocate two locks returns different locks -func TestAllocateTwoLocksGetsDifferentLocks(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - sem1, err := locks.AllocateSemaphore() - assert.NoError(t, err) - - sem2, err := locks.AllocateSemaphore() - assert.NoError(t, err) - - assert.NotEqual(t, sem1, sem2) - }) -} - -// Test allocate all locks successful and all are unique -func TestAllocateAllLocksSucceeds(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - sems := make(map[uint32]bool) - var i uint32 - for i = 0; i < numLocks; i++ { - sem, err := locks.AllocateSemaphore() - assert.NoError(t, err) - - // Ensure the allocate semaphore is unique - _, ok := sems[sem] - assert.False(t, ok) - - sems[sem] = true - } - }) -} - -// Test allocating more than the given max fails -func TestAllocateTooManyLocksFails(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - // Allocate all locks - var i uint32 - for i = 0; i < numLocks; i++ { - _, err := locks.AllocateSemaphore() - assert.NoError(t, err) - } - - // Try and allocate one more - _, err := locks.AllocateSemaphore() - assert.Error(t, err) - }) -} - -// Test allocating max locks, deallocating one, and then allocating again succeeds -func TestAllocateDeallocateCycle(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - // Allocate all locks - var i uint32 - for i = 0; i < numLocks; i++ { - _, err := locks.AllocateSemaphore() - assert.NoError(t, err) - } - - // Now loop through again, deallocating and reallocating. - // Each time we free 1 semaphore, allocate again, and make sure - // we get the same semaphore back. - var j uint32 - for j = 0; j < numLocks; j++ { - err := locks.DeallocateSemaphore(j) - assert.NoError(t, err) - - newSem, err := locks.AllocateSemaphore() - assert.NoError(t, err) - assert.Equal(t, j, newSem) - } - }) -} - -// Test that locks actually lock -func TestLockSemaphoreActuallyLocks(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - // This entire test is very ugly - lots of sleeps to try and get - // things to occur in the right order. - // It also doesn't even exercise the multiprocess nature of the - // locks. - - // Get the current time - startTime := time.Now() - - // Start a goroutine to take the lock and then release it after - // a second. - go func() { - err := locks.LockSemaphore(0) - assert.NoError(t, err) - - time.Sleep(1 * time.Second) - - err = locks.UnlockSemaphore(0) - assert.NoError(t, err) - }() - - // Sleep for a quarter of a second to give the goroutine time - // to kick off and grab the lock - time.Sleep(250 * time.Millisecond) - - // Take the lock - err := locks.LockSemaphore(0) - assert.NoError(t, err) - - // Get the current time - endTime := time.Now() - - // Verify that at least 1 second has passed since start - duration := endTime.Sub(startTime) - assert.True(t, duration.Seconds() > 1.0) - }) -} - -// Test that locking and unlocking two semaphores succeeds -// Ensures that runtime.LockOSThread() is doing its job -func TestLockAndUnlockTwoSemaphore(t *testing.T) { - runLockTest(t, func(t *testing.T, locks *SHMLocks) { - err := locks.LockSemaphore(0) - assert.NoError(t, err) - - err = locks.LockSemaphore(1) - assert.NoError(t, err) - - err = locks.UnlockSemaphore(1) - assert.NoError(t, err) - - // Now yield scheduling - // To try and get us on another OS thread - runtime.Gosched() - - // And unlock the last semaphore - // If we are in a different OS thread, this should fail. - // However, runtime.UnlockOSThread() should guarantee we are not - err = locks.UnlockSemaphore(0) - assert.NoError(t, err) - }) -} |