diff --git a/include/empthread.h b/include/empthread.h index 00c734ba..1c4ff15d 100644 --- a/include/empthread.h +++ b/include/empthread.h @@ -31,7 +31,7 @@ * Sasha Mikheev * Doug Hay, 1998 * Steve McClure, 1998 - * Markus Armbruster, 2005-2006 + * Markus Armbruster, 2005-2007 */ /* @@ -74,6 +74,9 @@ typedef struct lwpProc empth_t; /* empth_sem_t * represents a semaphore */ typedef struct lwpSem empth_sem_t; +/* empth_rwlock_t * represents a read-write lock */ +typedef struct lwp_rwlock empth_rwlock_t; + /* Flags for empth_select(): whether to sleep on input or output */ #define EMPTH_FD_READ LWP_FD_READ #define EMPTH_FD_WRITE LWP_FD_WRITE @@ -95,6 +98,7 @@ typedef struct lwpSem empth_sem_t; typedef struct empth_t empth_t; typedef struct empth_sem_t empth_sem_t; +typedef struct empth_rwlock_t empth_rwlock_t; #endif /* EMPTH_POSIX */ @@ -108,6 +112,7 @@ typedef struct empth_sem_t empth_sem_t; typedef struct loc_Thread_t empth_t; typedef struct loc_Sem_t empth_sem_t; +typedef struct loc_RWLock_t empth_rwlock_t; void empth_request_shutdown(void); #endif /* EMPTH_W32 */ @@ -220,6 +225,42 @@ void empth_sem_signal(empth_sem_t *sem); */ void empth_sem_wait(empth_sem_t *sem); +/* + * Create a read-write lock. + * NAME is its name, it is used for debugging. + * Return the reade-write lock, or NULL on error. + */ +empth_rwlock_t *empth_rwlock_create(char *name); + +/* + * Destroy RWLOCK. + */ +void empth_rwlock_destroy(empth_rwlock_t *rwlock); + +/* + * Lock RWLOCK for writing. + * A read-write lock can be locked for writing only when it is + * unlocked. If this is not the case, put the current thread to sleep + * until it is. + */ +void empth_rwlock_wrlock(empth_rwlock_t *rwlock); + +/* + * Lock RWLOCK for reading. + * A read-write lock can be locked for reading only when it is not + * locked for writing. If this is not the case, put the current + * thread to sleep until it is. Must not starve writers, and may + * sleep to avoid that. + */ +void empth_rwlock_rdlock(empth_rwlock_t *rwlock); + +/* + * Unlock read-write lock RWLOCK. + * The current thread must hold RWLOCK. + * Wake up threads that can now lock it. + */ +void empth_rwlock_unlock(empth_rwlock_t *rwlock); + /* * Stuff for implementations, not for clients. diff --git a/include/lwp.h b/include/lwp.h index ec053be3..6fccd584 100644 --- a/include/lwp.h +++ b/include/lwp.h @@ -31,6 +31,7 @@ struct lwpProc; struct lwpSem; +struct lwp_rwlock; #define LWP_FD_READ 0x1 #define LWP_FD_WRITE 0x2 @@ -57,6 +58,12 @@ struct lwpSem *lwpCreateSem(char *name, int count); void lwpSignal(struct lwpSem *); void lwpWait(struct lwpSem *); +struct lwp_rwlock *lwp_rwlock_create(char *); +void lwp_rwlock_destroy(struct lwp_rwlock *); +void lwp_rwlock_wrlock(struct lwp_rwlock *); +void lwp_rwlock_rdlock(struct lwp_rwlock *); +void lwp_rwlock_unlock(struct lwp_rwlock *); + extern struct lwpProc *LwpCurrent; #endif diff --git a/src/lib/empthread/lwp.c b/src/lib/empthread/lwp.c index 91325a72..c1f7146c 100644 --- a/src/lib/empthread/lwp.c +++ b/src/lib/empthread/lwp.c @@ -29,7 +29,7 @@ * * Known contributors to this file: * Sasha Mikheev - * Markus Armbruster, 2006 + * Markus Armbruster, 2006-2007 */ #include @@ -149,3 +149,33 @@ empth_sem_wait(empth_sem_t *sm) { lwpWait(sm); } + +empth_rwlock_t * +empth_rwlock_create(char *name) +{ + return lwp_rwlock_create(name); +} + +void +empth_rwlock_destroy(empth_rwlock_t *rwlock) +{ + lwp_rwlock_destroy(rwlock); +} + +void +empth_rwlock_wrlock(empth_rwlock_t *rwlock) +{ + lwp_rwlock_wrlock(rwlock); +} + +void +empth_rwlock_rdlock(empth_rwlock_t *rwlock) +{ + lwp_rwlock_rdlock(rwlock); +} + +void +empth_rwlock_unlock(empth_rwlock_t *rwlock) +{ + lwp_rwlock_unlock(rwlock); +} diff --git a/src/lib/empthread/ntthread.c b/src/lib/empthread/ntthread.c index c734a0cd..d87117cb 100644 --- a/src/lib/empthread/ntthread.c +++ b/src/lib/empthread/ntthread.c @@ -30,7 +30,7 @@ * Known contributors to this file: * Doug Hay, 1998 * Steve McClure, 1998 - * Ron Koenderink, 2004-2005 + * Ron Koenderink, 2004-2007 */ /* @@ -108,6 +108,53 @@ struct loc_Sem_t { int count; }; +/************************ + * loc_RWLock_t + * + * Invariants + * must hold at function call, return, sleep + * and resume from sleep. + * + * any state: + * nwrite >= 0 + * nread >= 0 + + * if unlocked: + * can_read set + * can_write set + * nwrite == 0 + * nread == 0 + * + * if read-locked without writers contending: + * can_read set + * can_write clear + * nwrite == 0 + * nread > 0 + * + * if read-locked with writers contending: + * can_read clear + * can_write clear + * nwrite > 0 #writers blocked + * nread > 0 + * + * if write-locked: + * can_read clear + * can_write clear + * nwrite > 0 #writers blocked + 1 + * nread == 0 + * + * To ensure consistency, state normally changes only while the + * thread changing it holds hThreadMutex. + * + */ +struct loc_RWLock_t { + char name[17]; /* The thread name, passed in at create time. */ + HANDLE can_read; /* Manual event -- allows read locks */ + HANDLE can_write; /* Auto-reset event -- allows write locks */ + int nread; /* number of active readers */ + int nwrite; /* total number of writers (active and waiting) */ +}; + /* This is the thread exclusion/non-premption mutex. */ /* The running thread has this MUTEX, and all others are */ /* either blocked on it, or waiting for some OS response. */ @@ -202,8 +249,10 @@ loc_FreeThreadInfo(empth_t *pThread) * info, and the thread owns the MUTEX sem. */ static void -loc_RunThisThread(void) +loc_RunThisThread(HANDLE hWaitObject) { + HANDLE hWaitObjects[2]; + empth_t *pThread = TlsGetValue(dwTLSIndex); if (pThread->bKilled) { @@ -214,8 +263,11 @@ loc_RunThisThread(void) } } - /* Get the MUTEX semaphore, wait forever. */ - WaitForSingleObject(hThreadMutex, INFINITE); + hWaitObjects[0] = hThreadMutex; + hWaitObjects[1] = hWaitObject; + + WaitForMultipleObjects(hWaitObject ? 2 : 1, hWaitObjects, + TRUE, INFINITE); if (!pCurThread) { /* Set the globals to this thread. */ @@ -306,7 +358,7 @@ empth_threadMain(void *pvData) srand(now ^ (unsigned)pThread); /* Switch to this thread context */ - loc_RunThisThread(); + loc_RunThisThread(NULL); /* Run the thread. */ if (pThread->pfnEntry) @@ -375,7 +427,7 @@ empth_init(void **ctx_ptr, int flags) TlsSetValue(dwTLSIndex, pThread); /* Make this the running thread. */ - loc_RunThisThread(); + loc_RunThisThread(NULL); logerror("NT pthreads initialized"); return 0; @@ -481,7 +533,7 @@ void empth_yield(void) { loc_BlockThisThread(); - loc_RunThisThread(); + loc_RunThisThread(NULL); } /************************ @@ -534,7 +586,7 @@ empth_select(int fd, int flags) WSACloseEvent(hEventObject[0]); - loc_RunThisThread(); + loc_RunThisThread(NULL); } /************************ @@ -570,7 +622,7 @@ empth_sleep(time_t until) loc_debug("sleep done. Waiting to run."); - loc_RunThisThread(); + loc_RunThisThread(NULL); } /************************ @@ -593,7 +645,7 @@ empth_wait_for_signal(void) /* Get the MUTEX semaphore, wait the number of MS */ WaitForSingleObject(hShutdownEvent, INFINITE); - loc_RunThisThread(); + loc_RunThisThread(NULL); return 0; } @@ -670,5 +722,81 @@ empth_sem_wait(empth_sem_t *pSem) } else ReleaseMutex(pSem->hMutex); - loc_RunThisThread(); + loc_RunThisThread(NULL); +} + +empth_rwlock_t * +empth_rwlock_create(char *name) +{ + empth_rwlock_t *rwlock; + + rwlock = malloc(sizeof(*rwlock)); + if (!rwlock) + return NULL; + + memset(rwlock, 0, sizeof(*rwlock)); + strncpy(rwlock->name, name, sizeof(rwlock->name) - 1); + + if ((rwlock->can_read = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) { + logerror("rwlock_create: failed to create reader event %s at %s:%d", + name, __FILE__, __LINE__); + free(rwlock); + return NULL; + } + + if ((rwlock->can_write = CreateEvent(NULL, FALSE, TRUE, NULL)) == NULL) { + logerror("rwlock_create: failed to create writer event %s at %s:%d", + name, __FILE__, __LINE__); + CloseHandle(rwlock->can_read); + free(rwlock); + return NULL; + } + return rwlock; +} + +void +empth_rwlock_destroy(empth_rwlock_t *rwlock) +{ + if (CANT_HAPPEN(rwlock->nread || rwlock->nwrite)) + return; + CloseHandle(rwlock->can_read); + CloseHandle(rwlock->can_write); + free(rwlock); +} + +void +empth_rwlock_wrlock(empth_rwlock_t *rwlock) +{ + /* block any new readers */ + ResetEvent(rwlock->can_read); + rwlock->nwrite++; + loc_BlockThisThread(); + loc_RunThisThread(rwlock->can_write); + CANT_HAPPEN(rwlock->nread != 0); +} + +void +empth_rwlock_rdlock(empth_rwlock_t *rwlock) +{ + loc_BlockThisThread(); + loc_RunThisThread(rwlock->can_read); + ResetEvent(rwlock->can_write); + rwlock->nread++; +} + +void +empth_rwlock_unlock(empth_rwlock_t *rwlock) +{ + if (CANT_HAPPEN(!rwlock->nread && !rwlock->nwrite)) + return; + if (rwlock->nread) { /* holding read lock */ + rwlock->nread--; + if (rwlock->nread == 0) + SetEvent(rwlock->can_write); + } else { + rwlock->nwrite--; + SetEvent(rwlock->can_write); + } + if (rwlock->nwrite == 0) + SetEvent(rwlock->can_read); } diff --git a/src/lib/empthread/pthread.c b/src/lib/empthread/pthread.c index f532f763..7378e766 100644 --- a/src/lib/empthread/pthread.c +++ b/src/lib/empthread/pthread.c @@ -30,7 +30,8 @@ * Known contributors to this file: * Sasha Mikheev * Steve McClure, 1998 - * Markus Armbruster, 2005-2006 + * Markus Armbruster, 2005-2007 + * Ron Koenderink, 2007 */ /* Required for PTHREAD_STACK_MIN on some systems, e.g. Solaris: */ @@ -75,6 +76,11 @@ struct empth_sem_t { pthread_cond_t cnd_sem; }; +struct empth_rwlock_t { + char *name; + pthread_rwlock_t lock; +}; + /* Thread-specific data key */ static pthread_key_t ctx_key; @@ -455,3 +461,52 @@ empth_sem_wait(empth_sem_t *sm) } else pthread_mutex_unlock(&sm->mtx_update); } + +empth_rwlock_t * +empth_rwlock_create(char *name) +{ + empth_rwlock_t *rwlock; + + rwlock = malloc(sizeof(*rwlock)); + if (!rwlock) + return NULL; + + if (pthread_rwlock_init(&rwlock->lock, NULL) != 0) { + free(rwlock); + return NULL; + } + + rwlock->name = strdup(name); + return rwlock; +} + +void +empth_rwlock_destroy(empth_rwlock_t *rwlock) +{ + pthread_rwlock_destroy(&rwlock->lock); + free(rwlock); +} + +void +empth_rwlock_wrlock(empth_rwlock_t *rwlock) +{ + pthread_mutex_unlock(&mtx_ctxsw); + pthread_rwlock_wrlock(&rwlock->lock); + pthread_mutex_lock(&mtx_ctxsw); + empth_restorectx(); +} + +void +empth_rwlock_rdlock(empth_rwlock_t *rwlock) +{ + pthread_mutex_unlock(&mtx_ctxsw); + pthread_rwlock_rdlock(&rwlock->lock); + pthread_mutex_lock(&mtx_ctxsw); + empth_restorectx(); +} + +void +empth_rwlock_unlock(empth_rwlock_t *rwlock) +{ + pthread_rwlock_unlock(&rwlock->lock); +} diff --git a/src/lib/lwp/rwlock.c b/src/lib/lwp/rwlock.c new file mode 100644 index 00000000..314a310d --- /dev/null +++ b/src/lib/lwp/rwlock.c @@ -0,0 +1,134 @@ +/* + * Empire - A multi-player, client/server Internet based war game. + * Copyright (C) 1994-2007, Dave Pare, Jeff Bailey, Thomas Ruschak, + * Ken Stevens, Steve McClure + * Copyright (C) 1991-3 Stephen Crane + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * --- + * + * See files README, COPYING and CREDITS in the root of the source + * tree for related information and legal notices. It is expected + * that future projects/authors will amend these files as needed. + * + * --- + * + * rwlock.c: Read-write locks + * + * Known contributors to this file: + * Ron Koenderink, 2007 + * Markus Armbruster, 2007 + */ + +#include + +#include +#include + +#include "lwp.h" +#include "lwpint.h" + +struct lwp_rwlock { + /* + * Lock counter + * 0: unlocked + * -1: locked for writing + * >0: locked for reading that many times + */ + int count; + struct lwpQueue rq; /* read lock sleepers */ + struct lwpQueue wq; /* write lock sleepers */ + char *name; +}; + +struct lwp_rwlock * +lwp_rwlock_create(char *name) +{ + struct lwp_rwlock *rwlock; + + rwlock = malloc(sizeof(*rwlock)); + if (!rwlock) + return NULL; + + memset(rwlock, 0, sizeof(*rwlock)); + rwlock->name = strdup(name); + return rwlock; +} + +void +lwp_rwlock_destroy(struct lwp_rwlock *rwlock) +{ + if (CANT_HAPPEN(rwlock->count)) + return; + free(rwlock); +} + +void +lwp_rwlock_wrlock(struct lwp_rwlock *rwlock) +{ + if (rwlock->count) { + lwpAddTail(&rwlock->wq, LwpCurrent); + lwpStatus(LwpCurrent, "blocked to acquire rwlock %s for writing", + rwlock->name); + lwpReschedule(); + } + CANT_HAPPEN(rwlock->count != 0); + rwlock->count = -1; + lwpStatus(LwpCurrent, "acquired rwlock %s for writing", rwlock->name); +} + +void +lwp_rwlock_rdlock(struct lwp_rwlock *rwlock) +{ + if (rwlock->count < 0 || rwlock->wq.head) { + lwpStatus(LwpCurrent, "blocked to acquire rwlock %s for reading", + rwlock->name); + lwpAddTail(&rwlock->rq, LwpCurrent); + lwpReschedule(); + } + CANT_HAPPEN(rwlock->count < 0); + rwlock->count++; + lwpStatus(LwpCurrent, "acquired rwlock %s for reading", rwlock->name); +} + +void +lwp_rwlock_unlock(struct lwp_rwlock *rwlock) +{ + struct lwpProc *p; + + lwpStatus(LwpCurrent, "unlocking rwlock %s", rwlock->name); + if (CANT_HAPPEN(rwlock->count == 0)) + return; + if (rwlock->count < 0) + rwlock->count = 0; + else + rwlock->count--; + + if (rwlock->count == 0 && rwlock->wq.head) { + p = lwpGetFirst(&rwlock->wq); + lwpStatus(p, "wake up next writer of rwlock %s", rwlock->name); + } else if (rwlock->count >= 0 && rwlock->rq.head && !rwlock->wq.head) { + p = lwpGetFirst(&rwlock->rq); + lwpStatus(p, "wake up next reader of rwlock %s", rwlock->name); + } else + return; + + lwpReady(p); + if (LwpCurrent->pri < p->pri) { + lwpStatus(LwpCurrent, "yielding to thread with higher priority"); + lwpYield(); + } +}