From 7f6002caba3f0a6749820c2772161caf55b8d267 Mon Sep 17 00:00:00 2001 From: neonloop Date: Fri, 7 May 2021 20:00:12 +0000 Subject: Initial commit (uqm-0.8.0) --- src/libs/threads/pthread/posixthreads.c | 672 ++++++++++++++++++++++++++++++++ 1 file changed, 672 insertions(+) create mode 100644 src/libs/threads/pthread/posixthreads.c (limited to 'src/libs/threads/pthread/posixthreads.c') diff --git a/src/libs/threads/pthread/posixthreads.c b/src/libs/threads/pthread/posixthreads.c new file mode 100644 index 0000000..4f0d2e8 --- /dev/null +++ b/src/libs/threads/pthread/posixthreads.c @@ -0,0 +1,672 @@ +/* + * 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. + */ + +#include +#include "libs/misc.h" +#include "libs/memlib.h" +#include "posixthreads.h" +#include +#include + +#include + +#include "libs/log/uqmlog.h" + +typedef struct _thread { + pthread_t native; +#ifdef NAMED_SYNCHRO + const char *name; +#endif + ThreadLocal *localData; + struct _thread *next; +} *TrueThread; + +static volatile TrueThread threadQueue = NULL; +static pthread_mutex_t threadQueueMutex; + +struct ThreadStartInfo +{ + ThreadFunction func; + void *data; + sem_t sem; + TrueThread thread; +}; + +void +InitThreadSystem_PT (void) +{ + pthread_mutex_init (&threadQueueMutex, NULL); +} + +void +UnInitThreadSystem_PT (void) +{ + pthread_mutex_destroy (&threadQueueMutex); +} + +static void +QueueThread (TrueThread thread) +{ + pthread_mutex_lock (&threadQueueMutex); + thread->next = threadQueue; + threadQueue = thread; + pthread_mutex_unlock (&threadQueueMutex); +} + +static void +UnQueueThread (TrueThread thread) +{ + volatile TrueThread *ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = &threadQueue; + while (*ptr != thread) + { +#ifdef DEBUG_THREADS + if (*ptr == NULL) + { + // Should not happen. + log_add (log_Debug, "Error: Trying to remove non-present thread " + "from thread queue."); + fflush (stderr); + explode (); + } +#endif /* DEBUG_THREADS */ + ptr = &(*ptr)->next; + } + *ptr = (*ptr)->next; + pthread_mutex_unlock (&threadQueueMutex); +} + +static TrueThread +FindThreadInfo (pthread_t threadID) +{ + TrueThread ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = threadQueue; + while (ptr) + { + if (ptr->native == threadID) + { + pthread_mutex_unlock (&threadQueueMutex); + return ptr; + } + ptr = ptr->next; + } + pthread_mutex_unlock (&threadQueueMutex); + return NULL; +} + +#ifdef NAMED_SYNCHRO +static const char * +MyThreadName (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->name : "Unknown (probably renderer)"; +} +#endif + +static void * +ThreadHelper (void *startInfo) { + ThreadFunction func; + void *data; + sem_t *sem; + TrueThread thread; + int result; + + //log_add (log_Debug, "ThreadHelper()"); + + func = ((struct ThreadStartInfo *) startInfo)->func; + data = ((struct ThreadStartInfo *) startInfo)->data; + sem = &((struct ThreadStartInfo *) startInfo)->sem; + + // Wait until the Thread structure is available. + if (sem_wait (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_wait fail"); + exit(EXIT_FAILURE); + } + if (sem_destroy (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_destroy fail"); + exit(EXIT_FAILURE); + } + + thread = ((struct ThreadStartInfo *) startInfo)->thread; + HFree (startInfo); + + result = (*func) (data); + +#ifdef DEBUG_THREADS + log_add (log_Debug, "Thread '%s' done (returned %d).", + thread->name, result); + fflush (stderr); +#endif + + UnQueueThread (thread); + DestroyThreadLocal (thread->localData); + FinishThread (thread); + /* Destroying the thread is the responsibility of ProcessThreadLifecycles() */ + return (void*)result; +} + +void +DestroyThread_PT (Thread t) +{ + HFree (t); +} + +Thread +CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize +#ifdef NAMED_SYNCHRO + , const char *name +#endif + ) +{ + TrueThread thread; + struct ThreadStartInfo *startInfo; + pthread_attr_t attr; + + + //log_add (log_Debug, "CreateThread_PT '%s'", name); + + thread = (struct _thread *) HMalloc (sizeof *thread); +#ifdef NAMED_SYNCHRO + thread->name = name; +#endif + + thread->localData = CreateThreadLocal (); + + startInfo = (struct ThreadStartInfo *) HMalloc (sizeof (*startInfo)); + startInfo->func = func; + startInfo->data = data; + if (sem_init(&startInfo->sem, 0, 0) < 0) + { + log_add (log_Fatal, "createthread seminit fail"); + exit(EXIT_FAILURE); + } + startInfo->thread = thread; + + pthread_attr_init(&attr); + if (pthread_attr_setstacksize(&attr, 75000)) + { + log_add (log_Debug, "pthread stacksize fail"); + } + if (pthread_create(&thread->native, &attr, ThreadHelper, (void *)startInfo)) + { + log_add (log_Debug, "pthread create fail"); + DestroyThreadLocal (thread->localData); + HFree (startInfo); + HFree (thread); + return NULL; + } + // The responsibility to free 'startInfo' and 'thread' is now by the new + // thread. + + QueueThread (thread); + +#ifdef DEBUG_THREADS +//#if 0 + log_add (log_Debug, "Thread '%s' created.", thread->name); + fflush (stderr); +//#endif +#endif + + // Signal to the new thread that the thread structure is ready + // and it can begin to use it. + if (sem_post (&startInfo->sem)) + { + log_add(log_Fatal, "CreateThread sem_post fail"); + exit(EXIT_FAILURE); + } + + (void) stackSize; /* Satisfying compiler (unused parameter) */ + return thread; +} + +void +SleepThread_PT (TimeCount sleepTime) +{ + usleep (sleepTime * 1000000 / ONE_SECOND); +} + +void +SleepThreadUntil_PT (TimeCount wakeTime) { + TimeCount now; + + now = GetTimeCounter (); + if (wakeTime <= now) + TaskSwitch_PT (); + else + usleep ((wakeTime - now) * 1000000 / ONE_SECOND); +} + +void +TaskSwitch_PT (void) { + usleep (1000); +} + +void +WaitThread_PT (Thread thread, int *status) { + //log_add(log_Debug, "WaitThread_PT '%s', status %x", ((TrueThread)thread)->name, status); + //pthread_join(((TrueThread)thread)->native, status); + pthread_join(((TrueThread)thread)->native, NULL); + //log_add(log_Debug, "WaitThread_PT '%s' complete", ((TrueThread)thread)->name); +} + +ThreadLocal * +GetMyThreadLocal_PT (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->localData : NULL; +} + +/* These are the pthread implementations of the UQM synchronization objects. */ + +/* Mutexes. */ +/* TODO. The w_memlib uses Mutexes right now, so we can't use HMalloc + * or HFree. Once that goes, this needs to change. */ + +typedef struct _mutex { + pthread_mutex_t mutex; +#ifdef TRACK_CONTENTION + pthread_t owner; +#endif +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Mut; + + +Mutex +#ifdef NAMED_SYNCHRO +CreateMutex_PT (const char *name, DWORD syncClass) +#else +CreateMutex_PT (void) +#endif +{ + Mut *mutex = malloc (sizeof (Mut)); + + if (mutex != NULL) + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&mutex->mutex, &attr)) + { +#ifdef NAMED_SYNCHRO + /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Fatal, "Could not initialize mutex '%s':" + "aborting.", name); +#else + log_add_nothread (log_Fatal, "Could not initialize mutex:" + "aborting."); +#endif + exit (EXIT_FAILURE); + } + pthread_mutexattr_destroy(&attr); + +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif +#ifdef NAMED_SYNCHRO + mutex->name = name; + mutex->syncClass = syncClass; +#endif + } + + return mutex; +} + +void +DestroyMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; + //log_add_nothread(log_Debug, "Destroying mutex '%s'", mutex->name); + pthread_mutex_destroy (&mutex->mutex); + free (mutex); +} + +void +LockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + /* This code isn't really quite right; race conditions between + * check and lock remain and can produce reports of contention + * where the thread never sleeps, or fail to report in + * situations where it does. If tracking with perfect + * accuracy becomes important, the TRACK_CONTENTION mutex will + * need to handle its own wake/sleep cycles with condition + * variables (check the history of this file for the + * CrossThreadMutex code). This almost-measure is being added + * because for the most part it should suffice. */ + if (mutex->owner && (mutex->syncClass & TRACK_CONTENTION_CLASSES)) + { /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Debug, "Thread '%s' blocking on mutex '%s'", + MyThreadName (), mutex->name); + } +#endif + + while (pthread_mutex_lock (&mutex->mutex) != 0) + { + //log_add_nothread (log_Debug, "Attempt to acquire mutex '%s' failretry", mutex->name); + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + mutex->owner = pthread_self(); +#endif +} + +void +UnlockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif + while (pthread_mutex_unlock (&mutex->mutex) != 0) + { + TaskSwitch_PT (); + } +} + +/* Semaphores. */ + +typedef struct _sem { + sem_t sem; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Sem; + +Semaphore +CreateSemaphore_PT (DWORD initial +#ifdef NAMED_SYNCHRO + , const char *name, DWORD syncClass +#endif + ) +{ + Sem *sem = (Sem *) HMalloc (sizeof (struct _sem)); +#ifdef NAMED_SYNCHRO + sem->name = name; + sem->syncClass = syncClass; +#endif + + //log_add (log_Debug, "Creating semaphore '%s'", sem->name); + + if (sem_init(&sem->sem, 0, initial) < 0) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize semaphore '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize semaphore:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } + //log_add (log_Debug, "Creating semaphore '%s' success", sem->name); + return sem; +} + +void +DestroySemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Destroying semaphore '%s'", sem->name); + if (sem_destroy (&sem->sem)) + { + log_add (log_Debug, "Destroying semaphore '%s' failed", sem->name); + } + HFree (sem); +} + +void +SetSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; +#ifdef TRACK_CONTENTION + int contention = 0; + sem_getvalue(&sem->sem, &contention); + contention = !contention; + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on semaphore '%s'", + MyThreadName (), sem->name); + } +#endif + //log_add (log_Debug, "Attempt to set semaphore '%s'", sem->name); + while (sem_wait (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to set semaphore '%s' failretry", sem->name); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to set semaphore '%s' success", sem->name); +#ifdef TRACK_CONTENTION + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' awakens," + " released from semaphore '%s'", MyThreadName (), sem->name); + } +#endif +} + +void +ClearSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Attempt to clear semaphore '%s' %x", sem->name, sem); + while (sem_post (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to clear semaphore %x failretry", sem); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to clear semaphore %x success", sem); +} + +/* Recursive mutexes. Adapted from mixSDL code, which was adapted from + the original DCQ code. */ + +typedef struct _recm { + pthread_mutex_t mutex; + pthread_t thread_id; + unsigned int locks; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} RecM; + +RecursiveMutex +#ifdef NAMED_SYNCHRO +CreateRecursiveMutex_PT (const char *name, DWORD syncClass) +#else +CreateRecursiveMutex_PT (void) +#endif +{ + RecM *mtx = (RecM *) HMalloc (sizeof (struct _recm)); + + mtx->thread_id = 0; + if (pthread_mutex_init(&mtx->mutex, NULL)) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize recursive " + "mutex '%s': aborting.", name); +#else + log_add (log_Fatal, "Could not initialize recursive " + "mutex: aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + mtx->name = name; + mtx->syncClass = syncClass; +#endif + mtx->locks = 0; + return (RecursiveMutex) mtx; +} + +void +DestroyRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_mutex_destroy(&mtx->mutex); + HFree (mtx); +} + +void +LockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef TRACK_CONTENTION + if (mtx->thread_id && (mtx->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on '%s'", + MyThreadName (), mtx->name); + } +#endif + while (pthread_mutex_lock (&mtx->mutex)) + TaskSwitch_PT (); + mtx->thread_id = thread_id; + } + mtx->locks++; +} + +void +UnlockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef NAMED_SYNCHRO + log_add (log_Debug, "'%s' attempted to unlock %s when it " + "didn't hold it", MyThreadName (), mtx->name); +#endif + } + else + { + mtx->locks--; + if (!mtx->locks) + { + mtx->thread_id = 0; + pthread_mutex_unlock (&mtx->mutex); + } + } +} + +int +GetRecursiveMutexDepth_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + return mtx->locks; +} + +typedef struct _cond { + pthread_cond_t cond; + pthread_mutex_t mutex; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} cvar; + +CondVar +#ifdef NAMED_SYNCHRO +CreateCondVar_PT (const char *name, DWORD syncClass) +#else +CreateCondVar_PT (void) +#endif +{ + int err1, err2; + cvar *cv = (cvar *) HMalloc (sizeof (cvar)); + err1 = pthread_cond_init(&cv->cond, NULL); + err2 = pthread_mutex_init(&cv->mutex, NULL); + if (err1 || err2) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize condition variable '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize condition variable:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + cv->name = name; + cv->syncClass = syncClass; +#endif + return cv; +} + +void +DestroyCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_destroy(&cv->cond); + pthread_mutex_destroy(&cv->mutex); + HFree (cv); +} + +void +WaitCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_mutex_lock (&cv->mutex); +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' waiting for signal from '%s'", + MyThreadName (), cv->name); + } +#endif + while (pthread_cond_wait (&cv->cond, &cv->mutex) != 0) + { + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' received signal from '%s'," + " awakening.", MyThreadName (), cv->name); + } +#endif + pthread_mutex_unlock (&cv->mutex); +} + +void +SignalCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_signal(&cv->cond); +} + +void +BroadcastCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_broadcast(&cv->cond); +} -- cgit v1.2.3