409 lines
11 KiB
C
409 lines
11 KiB
C
/** \file
|
|
* An implementation of locks which governs order of access via a FIFO queue
|
|
*/
|
|
|
|
#include "stored.h"
|
|
#include "lock.h"
|
|
|
|
NFairLockPool *lockpool;
|
|
|
|
/**
|
|
* Initialize the pool of fair locks. Needs to be called once per program
|
|
* in order for fair locks to be available
|
|
*/
|
|
void
|
|
StoreInitializeFairLocks()
|
|
{
|
|
lockpool = MemMalloc(sizeof(NFairLockPool));
|
|
XplOpenLocalSemaphore(lockpool->lock, SEM_UNLOCKED);
|
|
lockpool->first = NULL;
|
|
lockpool->last = NULL;
|
|
}
|
|
|
|
/** \internal
|
|
* Create a string name for a store resource, used for find a specific lock
|
|
* for that resource
|
|
* \param name Usually the name of a store the resource is in
|
|
* \param guid GUID of the resource within that store
|
|
* \return A string name unique to that resource
|
|
*/
|
|
char *
|
|
FairLockName(char *name, uint64_t guid)
|
|
{
|
|
char *result;
|
|
int result_len;
|
|
|
|
if (name == NULL) name = "";
|
|
|
|
result_len = strlen(name) + 33;
|
|
result = MemMalloc(result_len);
|
|
|
|
snprintf(result, result_len, "%s-" GUID_FMT, name, guid);
|
|
|
|
return result;
|
|
}
|
|
|
|
/** \internal
|
|
* Retrieve a lock for a resource from a global pool of locks. This doesn't ascertain
|
|
* or set the actual status of that lock, it simply finds a reference to it.
|
|
* \bug A long list is a bad implementation, we want a big hash of short lists for quick access
|
|
* \bug Should we retire locks who haven't been taken in a while while we're looking?
|
|
* \param store Usually the name of the store the resource resides.
|
|
* \param guid The GUID of the resource within that store
|
|
* \return A fair lock governing access to that resource
|
|
*/
|
|
NFairLock *
|
|
FairLockFind(char *store, uint64_t guid)
|
|
{
|
|
NFairLockList *search;
|
|
NFairLock *result;
|
|
char *name;
|
|
char *new_lock = "";
|
|
|
|
name = FairLockName(store, guid);
|
|
XplWaitOnLocalSemaphore(lockpool->lock);
|
|
|
|
// look for our lock
|
|
result = NULL;
|
|
search = lockpool->first;
|
|
while ((search != NULL) && (result == NULL)) {
|
|
if (strcmp(search->lock->lockname, name) == 0) {
|
|
result = search->lock;
|
|
}
|
|
search = search->next;
|
|
}
|
|
|
|
if (result == NULL) {
|
|
// couldn't find the lock, so add a new one
|
|
NFairLockList *new_entry;
|
|
new_lock = "new ";
|
|
|
|
new_entry = MemMalloc(sizeof(NFairLockList));
|
|
result = MemMalloc(sizeof(NFairLock));
|
|
|
|
new_entry->next = NULL;
|
|
new_entry->lock = result;
|
|
|
|
result->lockname = MemMalloc(strlen(name)+1);
|
|
result->readers = 0;
|
|
result->writer = 0;
|
|
result->queue = NULL;
|
|
XplOpenLocalSemaphore(result->access, SEM_UNLOCKED);
|
|
strcpy(result->lockname, name);
|
|
|
|
if (lockpool->first != NULL) {
|
|
// append to existing list
|
|
lockpool->last->next = new_entry;
|
|
lockpool->last = new_entry;
|
|
} else {
|
|
// start a new list
|
|
lockpool->first = new_entry;
|
|
lockpool->last = new_entry;
|
|
}
|
|
}
|
|
|
|
XplSignalLocalSemaphore(lockpool->lock);
|
|
MemFree(name);
|
|
return result;
|
|
}
|
|
|
|
/** \internal
|
|
* Places a ticket in the queue for a lock. Usually called when someone already
|
|
* has the lock, and the process wishes to queue up for when it's free again.
|
|
* \param lock Fair lock we want access to
|
|
* \param is_writer Whether or not we desire write access to the resource
|
|
* \return A ticket which has been placed in the queue for the lock
|
|
*/
|
|
NFairLockQueueEntry *
|
|
FairLockNewQueueEntry (NFairLock *lock, BOOL is_writer)
|
|
{
|
|
NFairLockQueueEntry *result;
|
|
|
|
result = MemMalloc(sizeof(NFairLockQueueEntry));
|
|
if (result == NULL) return NULL;
|
|
|
|
result->is_writer = is_writer;
|
|
result->next = NULL;
|
|
XplOpenLocalSemaphore(result->signal, SEM_LOCKED);
|
|
|
|
if (lock->queue == NULL) {
|
|
// first entry in the queue
|
|
lock->queue = result;
|
|
} else {
|
|
// find the end of the queue
|
|
NFairLockQueueEntry *list = lock->queue;
|
|
|
|
while(list->next != NULL) { list = list->next; }
|
|
list->next = result;
|
|
}
|
|
return (result);
|
|
}
|
|
|
|
/** \internal
|
|
* Remove a ticket from the from of the queue for a lock. This is called
|
|
* by the process holding the ticket after it has acquired the lock,
|
|
* since the presence of the queue on the lock prevents other threads
|
|
* stealing the lock.
|
|
* The ticket itself isn't provided, we assume it's simply at the front
|
|
* of the queue.
|
|
* \bug Is the above assumption right?
|
|
* \param lock Fair lock whose queue the ticket was in.
|
|
*/
|
|
void
|
|
FairLockRemoveQueueHead (NFairLock *lock)
|
|
{
|
|
NFairLockQueueEntry *entry;
|
|
|
|
if (lock->queue == NULL) return; // no queue to alter?
|
|
entry = lock->queue;
|
|
|
|
lock->queue = entry->next;
|
|
XplCloseLocalSemaphore(entry->signal);
|
|
MemFree(entry);
|
|
}
|
|
|
|
/** \internal
|
|
* Signal the thread who holds the ticket at the front of the queue for a
|
|
* fair lock that it's likely it can now proceed to gain the lock. Doesn't
|
|
* touch the queue itself, as removing it too early could allow another thread
|
|
* to jump the queue.
|
|
* \param lock Fair lock whose queue we want to look at.
|
|
*/
|
|
void
|
|
FairLockSignalQueueHead (NFairLock *lock)
|
|
{
|
|
if (lock->queue == NULL) return; // no-one to signal
|
|
|
|
if (lock->queue->is_writer == FALSE) {
|
|
// a thread is waiting for a read lock
|
|
XplSignalLocalSemaphore(lock->queue->signal);
|
|
XplSignalLocalSemaphore(lock->queue->signal);
|
|
} else {
|
|
// a thread is waiting for a write lock - only signal if readers have left
|
|
// this eventually succeeds when the last read lock is given up
|
|
// new acquirees queue until then
|
|
if (lock->readers == 0) {
|
|
XplSignalLocalSemaphore(lock->queue->signal);
|
|
XplSignalLocalSemaphore(lock->queue->signal);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an Exclusive lock to a store resource. With an exclusive lock, no
|
|
* other thread should read from or write to the resource protected. If
|
|
* other readers/writer already holds the lock, this will wait in a queue
|
|
* until the lock becomes free.
|
|
* \bug Timeout is completely useless atm.
|
|
* \param client Store client accessing the resource
|
|
* \param lock Where we want to store the fair lock we receive
|
|
* \param guid GUID of the resource we wish to access in the store
|
|
* \param timeout How long we're willing to wait for the lock
|
|
* \return 0 if we gained the lock, some error number otherwise.
|
|
*/
|
|
int
|
|
StoreGetExclusiveFairLockQuiet(StoreClient *client, NFairLock **lock,
|
|
uint64_t guid, time_t timeout)
|
|
{
|
|
NFairLock *found_lock;
|
|
int result = 0;
|
|
|
|
if (timeout) {
|
|
// FIXME : unused parameter; should use this somehow!
|
|
}
|
|
|
|
// find the lock
|
|
found_lock = FairLockFind(client->storeName, guid);
|
|
if (found_lock == NULL) return -1; // error
|
|
*lock = found_lock;
|
|
|
|
// try to acquire a write lock
|
|
XplWaitOnLocalSemaphore(found_lock->access);
|
|
if (found_lock->readers == 0) {
|
|
// no readers - we're free
|
|
found_lock->writer = 1;
|
|
} else {
|
|
// need to queue for write access
|
|
NFairLockQueueEntry *ticket;
|
|
ticket = FairLockNewQueueEntry(found_lock, TRUE);
|
|
if (ticket) {
|
|
// since we're now in the queue, give up our access and wait our turn
|
|
XplSignalLocalSemaphore(found_lock->access);
|
|
XplWaitOnLocalSemaphore(ticket->signal); // wait til we reach the head of the queue
|
|
|
|
XplWaitOnLocalSemaphore(found_lock->access);
|
|
|
|
// now it's our turn, remove our ticket and try to get the write lock
|
|
FairLockRemoveQueueHead(found_lock);
|
|
if ((found_lock->readers > 0) || (found_lock->writer > 0)) {
|
|
// this is a bug
|
|
result = -3;
|
|
}
|
|
found_lock->writer = 1;
|
|
} else {
|
|
result = -2;
|
|
}
|
|
}
|
|
XplSignalLocalSemaphore(found_lock->access);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get a Shared lock to a store resource. A shared lock grants read-only
|
|
* access to the resource protected, so many threads may be accessing it
|
|
* simultaneously. If there is a queue already waiting, or the lock has
|
|
* been gained for Exclusive access, this function will cause the calling
|
|
* thread to wait in a queue for shared access to again become available.
|
|
* \bug Timeout simply doesn't work atm.
|
|
* \param client Store client who wishes to access a resource
|
|
* \param lock Where we want to save the lock we gain
|
|
* \param guid GUID of the store resource we want to access
|
|
* \param timeout How long we're willing to wait to gain the resource
|
|
* \return 0 if we successfully got the lock, an error number otherwise
|
|
*/
|
|
int
|
|
StoreGetSharedFairLockQuiet(StoreClient *client, NFairLock **lock,
|
|
uint64_t guid, time_t timeout)
|
|
{
|
|
NFairLock *found_lock;
|
|
int result = 0;
|
|
|
|
if (timeout) {
|
|
// FIXME : unused parameter; should use this somehow!
|
|
}
|
|
|
|
// first, find the lock
|
|
found_lock = FairLockFind(client->store, guid);
|
|
if (found_lock == NULL) return -1; // some error
|
|
*lock = found_lock;
|
|
|
|
// next, try to acquire a shared lock
|
|
XplWaitOnLocalSemaphore(found_lock->access);
|
|
|
|
if ((found_lock->writer == 0) && (found_lock->queue == NULL)) {
|
|
// no writers and no-one waiting, so we can freely read
|
|
found_lock->readers++;
|
|
} else {
|
|
// need to queue for read access
|
|
NFairLockQueueEntry *ticket;
|
|
ticket = FairLockNewQueueEntry(found_lock, FALSE);
|
|
if (ticket) {
|
|
// release our access and wait our turn
|
|
XplSignalLocalSemaphore(found_lock->access);
|
|
XplWaitOnLocalSemaphore(ticket->signal);
|
|
XplWaitOnLocalSemaphore(found_lock->access);
|
|
|
|
FairLockRemoveQueueHead(found_lock); // remove our ticket
|
|
if (found_lock->writer > 0) {
|
|
// bug..
|
|
result = -2;
|
|
} else {
|
|
found_lock->readers++;
|
|
// next person in queue might be a reader as well,
|
|
// so signal them
|
|
FairLockSignalQueueHead(found_lock);
|
|
}
|
|
} else {
|
|
// couldn't queue...
|
|
result = -1;
|
|
}
|
|
}
|
|
XplSignalLocalSemaphore(found_lock->access);
|
|
|
|
// set the lock
|
|
*lock = found_lock;
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Downgrade an existing Exclusive lock to Shared status
|
|
* \param lock Reference to the Exlcusive lock we want to become shared
|
|
* \return 0 if successful
|
|
*/
|
|
int
|
|
StoreDowngradeExclusiveFairLock(NFairLock **lock)
|
|
{
|
|
NFairLock *l = *lock;
|
|
int result = 0;
|
|
|
|
XplWaitOnLocalSemaphore(l->access);
|
|
if (l->writer) {
|
|
if (l->readers > 0) {
|
|
// writing and reading at the same time ... bug.
|
|
}
|
|
l->writer = 0;
|
|
l->readers++;
|
|
} else {
|
|
// this wasn't an exclusive lock.. bug.
|
|
result = -1;
|
|
}
|
|
FairLockSignalQueueHead(l);
|
|
XplSignalLocalSemaphore(l->access);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Release a fair lock which was gained for read-only use.
|
|
* \param lock Reference to the lock we're giving up
|
|
* \return 0 if successful
|
|
*/
|
|
int
|
|
StoreReleaseSharedFairLockQuiet(NFairLock **lock)
|
|
{
|
|
NFairLock *l = *lock;
|
|
|
|
XplWaitOnLocalSemaphore(l->access);
|
|
l->readers--;
|
|
// signal any waiting threads
|
|
FairLockSignalQueueHead(l);
|
|
XplSignalLocalSemaphore(l->access);
|
|
*lock = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Release a fair lock which was gained for exclusive use.
|
|
* \param lock Reference to the lock we're giving up
|
|
* \return 0 if successful
|
|
*/
|
|
int
|
|
StoreReleaseExclusiveFairLockQuiet(NFairLock **lock)
|
|
{
|
|
NFairLock *l = *lock;
|
|
|
|
XplWaitOnLocalSemaphore(l->access);
|
|
l->writer = 0;
|
|
FairLockSignalQueueHead(l);
|
|
XplSignalLocalSemaphore(l->access);
|
|
*lock = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Release a fair lock which could be exclusive or shared.
|
|
* \param lock Reference to the lock we're giving up
|
|
* \return 0 if successful
|
|
*/
|
|
int
|
|
StoreReleaseFairLockQuiet(NFairLock **lock)
|
|
{
|
|
NFairLock *l = *lock;
|
|
|
|
XplWaitOnLocalSemaphore(l->access);
|
|
if (l->writer && l->readers) {
|
|
// bug - shouldn't have both!
|
|
l->writer = l->readers = 0;
|
|
} else if (l->writer) {
|
|
l->writer = 0;
|
|
} else if (l->readers) {
|
|
l->readers--;
|
|
} else {
|
|
// no readers or writers - another bug?
|
|
}
|
|
FairLockSignalQueueHead(l);
|
|
XplSignalLocalSemaphore(l->access);
|
|
*lock = NULL;
|
|
return 0;
|
|
}
|