src/thread/SDL_thread.c
author Ryan C. Gordon <icculus@icculus.org>
Mon, 09 Dec 2013 16:03:18 -0500
changeset 8094 9efaae827924
parent 8093 b43765095a6f
child 8149 681eb46b8ac4
permissions -rw-r--r--
Implemented the Dynamic API magic.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../SDL_internal.h"
    22 
    23 /* System independent thread management routines for SDL */
    24 
    25 #include "SDL_assert.h"
    26 #include "SDL_thread.h"
    27 #include "SDL_thread_c.h"
    28 #include "SDL_systhread.h"
    29 #include "../SDL_error_c.h"
    30 
    31 
    32 SDL_TLSID
    33 SDL_TLSCreate()
    34 {
    35     static SDL_atomic_t SDL_tls_id;
    36     return SDL_AtomicIncRef(&SDL_tls_id)+1;
    37 }
    38 
    39 void *
    40 SDL_TLSGet(SDL_TLSID id)
    41 {
    42     SDL_TLSData *storage;
    43 
    44     storage = SDL_SYS_GetTLSData();
    45     if (!storage || id == 0 || id > storage->limit) {
    46         return NULL;
    47     }
    48     return storage->array[id-1].data;
    49 }
    50 
    51 int
    52 SDL_TLSSet(SDL_TLSID id, const void *value, void (*destructor)(void *))
    53 {
    54     SDL_TLSData *storage;
    55 
    56     if (id == 0) {
    57         return SDL_InvalidParamError("id");
    58     }
    59 
    60     storage = SDL_SYS_GetTLSData();
    61     if (!storage || (id > storage->limit)) {
    62         unsigned int i, oldlimit, newlimit;
    63 
    64         oldlimit = storage ? storage->limit : 0;
    65         newlimit = (id + TLS_ALLOC_CHUNKSIZE);
    66         storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage)+(newlimit-1)*sizeof(storage->array[0]));
    67         if (!storage) {
    68             return SDL_OutOfMemory();
    69         }
    70         storage->limit = newlimit;
    71         for (i = oldlimit; i < newlimit; ++i) {
    72             storage->array[i].data = NULL;
    73             storage->array[i].destructor = NULL;
    74         }
    75         if (SDL_SYS_SetTLSData(storage) != 0) {
    76             return -1;
    77         }
    78     }
    79 
    80     storage->array[id-1].data = SDL_const_cast(void*, value);
    81     storage->array[id-1].destructor = destructor;
    82     return 0;
    83 }
    84 
    85 static void
    86 SDL_TLSCleanup()
    87 {
    88     SDL_TLSData *storage;
    89 
    90     storage = SDL_SYS_GetTLSData();
    91     if (storage) {
    92         unsigned int i;
    93         for (i = 0; i < storage->limit; ++i) {
    94             if (storage->array[i].destructor) {
    95                 storage->array[i].destructor(storage->array[i].data);
    96             }
    97         }
    98         SDL_SYS_SetTLSData(NULL);
    99         SDL_free(storage);
   100     }
   101 }
   102 
   103 
   104 /* This is a generic implementation of thread-local storage which doesn't
   105    require additional OS support.
   106 
   107    It is not especially efficient and doesn't clean up thread-local storage
   108    as threads exit.  If there is a real OS that doesn't support thread-local
   109    storage this implementation should be improved to be production quality.
   110 */
   111 
   112 typedef struct SDL_TLSEntry {
   113     SDL_threadID thread;
   114     SDL_TLSData *storage;
   115     struct SDL_TLSEntry *next;
   116 } SDL_TLSEntry;
   117 
   118 static SDL_mutex *SDL_generic_TLS_mutex;
   119 static SDL_TLSEntry *SDL_generic_TLS;
   120 
   121 
   122 SDL_TLSData *
   123 SDL_Generic_GetTLSData()
   124 {
   125     SDL_threadID thread = SDL_ThreadID();
   126     SDL_TLSEntry *entry;
   127     SDL_TLSData *storage = NULL;
   128 
   129 #if !SDL_THREADS_DISABLED
   130     if (!SDL_generic_TLS_mutex) {
   131         static SDL_SpinLock tls_lock;
   132         SDL_AtomicLock(&tls_lock);
   133         if (!SDL_generic_TLS_mutex) {
   134             SDL_mutex *mutex = SDL_CreateMutex();
   135             SDL_MemoryBarrierRelease();
   136             SDL_generic_TLS_mutex = mutex;
   137             if (!SDL_generic_TLS_mutex) {
   138                 SDL_AtomicUnlock(&tls_lock);
   139                 return NULL;
   140             }
   141         }
   142         SDL_AtomicUnlock(&tls_lock);
   143     }
   144 #endif /* SDL_THREADS_DISABLED */
   145 
   146     SDL_MemoryBarrierAcquire();
   147     SDL_LockMutex(SDL_generic_TLS_mutex);
   148     for (entry = SDL_generic_TLS; entry; entry = entry->next) {
   149         if (entry->thread == thread) {
   150             storage = entry->storage;
   151             break;
   152         }
   153     }
   154 #if !SDL_THREADS_DISABLED
   155     SDL_UnlockMutex(SDL_generic_TLS_mutex);
   156 #endif
   157 
   158     return storage;
   159 }
   160 
   161 int
   162 SDL_Generic_SetTLSData(SDL_TLSData *storage)
   163 {
   164     SDL_threadID thread = SDL_ThreadID();
   165     SDL_TLSEntry *prev, *entry;
   166 
   167     /* SDL_Generic_GetTLSData() is always called first, so we can assume SDL_generic_TLS_mutex */
   168     SDL_LockMutex(SDL_generic_TLS_mutex);
   169     prev = NULL;
   170     for (entry = SDL_generic_TLS; entry; entry = entry->next) {
   171         if (entry->thread == thread) {
   172             if (storage) {
   173                 entry->storage = storage;
   174             } else {
   175                 if (prev) {
   176                     prev->next = entry->next;
   177                 } else {
   178                     SDL_generic_TLS = entry->next;
   179                 }
   180                 SDL_free(entry);
   181             }
   182             break;
   183         }
   184         prev = entry;
   185     }
   186     if (!entry) {
   187         entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry));
   188         if (entry) {
   189             entry->thread = thread;
   190             entry->storage = storage;
   191             entry->next = SDL_generic_TLS;
   192             SDL_generic_TLS = entry;
   193         }
   194     }
   195     SDL_UnlockMutex(SDL_generic_TLS_mutex);
   196 
   197     if (!entry) {
   198         return SDL_OutOfMemory();
   199     }
   200     return 0;
   201 }
   202 
   203 /* Routine to get the thread-specific error variable */
   204 SDL_error *
   205 SDL_GetErrBuf(void)
   206 {
   207     static SDL_SpinLock tls_lock;
   208     static SDL_bool tls_being_created;
   209     static SDL_TLSID tls_errbuf;
   210     static SDL_error SDL_global_errbuf;
   211     const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1;
   212     SDL_error *errbuf;
   213 
   214     /* tls_being_created is there simply to prevent recursion if SDL_TLSCreate() fails.
   215        It also means it's possible for another thread to also use SDL_global_errbuf,
   216        but that's very unlikely and hopefully won't cause issues.
   217      */
   218     if (!tls_errbuf && !tls_being_created) {
   219         SDL_AtomicLock(&tls_lock);
   220         if (!tls_errbuf) {
   221             SDL_TLSID slot;
   222             tls_being_created = SDL_TRUE;
   223             slot = SDL_TLSCreate();
   224             tls_being_created = SDL_FALSE;
   225             SDL_MemoryBarrierRelease();
   226             tls_errbuf = slot;
   227         }
   228         SDL_AtomicUnlock(&tls_lock);
   229     }
   230     if (!tls_errbuf) {
   231         return &SDL_global_errbuf;
   232     }
   233 
   234     SDL_MemoryBarrierAcquire();
   235     errbuf = (SDL_error *)SDL_TLSGet(tls_errbuf);
   236     if (errbuf == ALLOCATION_IN_PROGRESS) {
   237         return &SDL_global_errbuf;
   238     }
   239     if (!errbuf) {
   240         /* Mark that we're in the middle of allocating our buffer */
   241         SDL_TLSSet(tls_errbuf, ALLOCATION_IN_PROGRESS, NULL);
   242         errbuf = (SDL_error *)SDL_malloc(sizeof(*errbuf));
   243         if (!errbuf) {
   244             SDL_TLSSet(tls_errbuf, NULL, NULL);
   245             return &SDL_global_errbuf;
   246         }
   247         SDL_zerop(errbuf);
   248         SDL_TLSSet(tls_errbuf, errbuf, SDL_free);
   249     }
   250     return errbuf;
   251 }
   252 
   253 
   254 /* Arguments and callback to setup and run the user thread function */
   255 typedef struct
   256 {
   257     int (SDLCALL * func) (void *);
   258     void *data;
   259     SDL_Thread *info;
   260     SDL_sem *wait;
   261 } thread_args;
   262 
   263 void
   264 SDL_RunThread(void *data)
   265 {
   266     thread_args *args = (thread_args *) data;
   267     int (SDLCALL * userfunc) (void *) = args->func;
   268     void *userdata = args->data;
   269     SDL_Thread *thread = args->info;
   270     int *statusloc = &thread->status;
   271 
   272     /* Perform any system-dependent setup - this function may not fail */
   273     SDL_SYS_SetupThread(thread->name);
   274 
   275     /* Get the thread id */
   276     thread->threadid = SDL_ThreadID();
   277 
   278     /* Wake up the parent thread */
   279     SDL_SemPost(args->wait);
   280 
   281     /* Run the function */
   282     *statusloc = userfunc(userdata);
   283 
   284     /* Clean up thread-local storage */
   285     SDL_TLSCleanup();
   286 
   287     /* Mark us as ready to be joined (or detached) */
   288     if (!SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) {
   289         /* Clean up if something already detached us. */
   290         if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) {
   291             if (thread->name) {
   292                 SDL_free(thread->name);
   293             }
   294             SDL_free(thread);
   295         }
   296     }
   297 }
   298 
   299 #ifdef SDL_CreateThread
   300 #undef SDL_CreateThread
   301 #endif
   302 #if SDL_DYNAMIC_API
   303 #define SDL_CreateThread SDL_CreateThread_REAL
   304 #endif
   305 
   306 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
   307 DECLSPEC SDL_Thread *SDLCALL
   308 SDL_CreateThread(int (SDLCALL * fn) (void *),
   309                  const char *name, void *data,
   310                  pfnSDL_CurrentBeginThread pfnBeginThread,
   311                  pfnSDL_CurrentEndThread pfnEndThread)
   312 #else
   313 DECLSPEC SDL_Thread *SDLCALL
   314 SDL_CreateThread(int (SDLCALL * fn) (void *),
   315                  const char *name, void *data)
   316 #endif
   317 {
   318     SDL_Thread *thread;
   319     thread_args *args;
   320     int ret;
   321 
   322     /* Allocate memory for the thread info structure */
   323     thread = (SDL_Thread *) SDL_malloc(sizeof(*thread));
   324     if (thread == NULL) {
   325         SDL_OutOfMemory();
   326         return (NULL);
   327     }
   328     SDL_zerop(thread);
   329     thread->status = -1;
   330     SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE);
   331 
   332     /* Set up the arguments for the thread */
   333     if (name != NULL) {
   334         thread->name = SDL_strdup(name);
   335         if (thread->name == NULL) {
   336             SDL_OutOfMemory();
   337             SDL_free(thread);
   338             return (NULL);
   339         }
   340     }
   341 
   342     /* Set up the arguments for the thread */
   343     args = (thread_args *) SDL_malloc(sizeof(*args));
   344     if (args == NULL) {
   345         SDL_OutOfMemory();
   346         if (thread->name) {
   347             SDL_free(thread->name);
   348         }
   349         SDL_free(thread);
   350         return (NULL);
   351     }
   352     args->func = fn;
   353     args->data = data;
   354     args->info = thread;
   355     args->wait = SDL_CreateSemaphore(0);
   356     if (args->wait == NULL) {
   357         if (thread->name) {
   358             SDL_free(thread->name);
   359         }
   360         SDL_free(thread);
   361         SDL_free(args);
   362         return (NULL);
   363     }
   364 
   365     /* Create the thread and go! */
   366 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
   367     ret = SDL_SYS_CreateThread(thread, args, pfnBeginThread, pfnEndThread);
   368 #else
   369     ret = SDL_SYS_CreateThread(thread, args);
   370 #endif
   371     if (ret >= 0) {
   372         /* Wait for the thread function to use arguments */
   373         SDL_SemWait(args->wait);
   374     } else {
   375         /* Oops, failed.  Gotta free everything */
   376         if (thread->name) {
   377             SDL_free(thread->name);
   378         }
   379         SDL_free(thread);
   380         thread = NULL;
   381     }
   382     SDL_DestroySemaphore(args->wait);
   383     SDL_free(args);
   384 
   385     /* Everything is running now */
   386     return (thread);
   387 }
   388 
   389 SDL_threadID
   390 SDL_GetThreadID(SDL_Thread * thread)
   391 {
   392     SDL_threadID id;
   393 
   394     if (thread) {
   395         id = thread->threadid;
   396     } else {
   397         id = SDL_ThreadID();
   398     }
   399     return id;
   400 }
   401 
   402 const char *
   403 SDL_GetThreadName(SDL_Thread * thread)
   404 {
   405     if (thread) {
   406         return thread->name;
   407     } else {
   408         return NULL;
   409     }
   410 }
   411 
   412 int
   413 SDL_SetThreadPriority(SDL_ThreadPriority priority)
   414 {
   415     return SDL_SYS_SetThreadPriority(priority);
   416 }
   417 
   418 void
   419 SDL_WaitThread(SDL_Thread * thread, int *status)
   420 {
   421     if (thread) {
   422         SDL_SYS_WaitThread(thread);
   423         if (status) {
   424             *status = thread->status;
   425         }
   426         if (thread->name) {
   427             SDL_free(thread->name);
   428         }
   429         SDL_free(thread);
   430     }
   431 }
   432 
   433 void
   434 SDL_DetachThread(SDL_Thread * thread)
   435 {
   436     if (!thread) {
   437         return;
   438     }
   439 
   440     /* Grab dibs if the state is alive+joinable. */
   441     if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_DETACHED)) {
   442         SDL_SYS_DetachThread(thread);
   443     } else {
   444         /* all other states are pretty final, see where we landed. */
   445         const int state = SDL_AtomicGet(&thread->state);
   446         if ((state == SDL_THREAD_STATE_DETACHED) || (state == SDL_THREAD_STATE_CLEANED)) {
   447             return;  /* already detached (you shouldn't call this twice!) */
   448         } else if (state == SDL_THREAD_STATE_ZOMBIE) {
   449             SDL_WaitThread(thread, NULL);  /* already done, clean it up. */
   450         } else {
   451             SDL_assert(0 && "Unexpected thread state");
   452         }
   453     }
   454 }
   455 
   456 /* vi: set ts=4 sw=4 expandtab: */