src/thread/SDL_thread.c
author Sam Lantinga <slouken@libsdl.org>
Wed, 10 Jul 2013 18:31:17 -0700
changeset 7393 358696c354a8
parent 7391 a29895dc5e9a
child 7482 249d8ecbbb7d
permissions -rw-r--r--
Added release/acquire memory barriers to the atomic API
* Added a destructor to clean up TLS memory at thread shutdown
* Refactored the TLS code to have platform independent code and a small platform dependent core with a fallback to generic code if platform dependent functions fail.
* Fixed recursion issues with SDL_GetErrBuf()
     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_config.h"
    22 
    23 /* System independent thread management routines for SDL */
    24 
    25 #include "SDL_thread.h"
    26 #include "SDL_thread_c.h"
    27 #include "SDL_systhread.h"
    28 #include "../SDL_error_c.h"
    29 
    30 
    31 SDL_TLSID
    32 SDL_TLSCreate()
    33 {
    34     static SDL_atomic_t SDL_tls_id;
    35     return SDL_AtomicIncRef(&SDL_tls_id)+1;
    36 }
    37 
    38 void *
    39 SDL_TLSGet(SDL_TLSID id)
    40 {
    41     SDL_TLSData *storage;
    42 
    43     storage = SDL_SYS_GetTLSData();
    44     if (!storage || id == 0 || id > storage->limit) {
    45         return NULL;
    46     }
    47     return storage->array[id-1].data;
    48 }
    49 
    50 int
    51 SDL_TLSSet(SDL_TLSID id, const void *value, void (*destructor)(void *))
    52 {
    53     SDL_TLSData *storage;
    54 
    55     if (id == 0) {
    56         return SDL_InvalidParamError("id");
    57     }
    58 
    59     storage = SDL_SYS_GetTLSData();
    60     if (!storage || id > storage->limit) {
    61         int i, oldlimit, newlimit;
    62 
    63         oldlimit = storage ? storage->limit : 0;
    64         newlimit = (id + TLS_ALLOC_CHUNKSIZE);
    65         storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage)+(newlimit-1)*sizeof(storage->array[0]));
    66         if (!storage) {
    67             return SDL_OutOfMemory();
    68         }
    69         storage->limit = newlimit;
    70         for (i = oldlimit; i < newlimit; ++i) {
    71             storage->array[i].data = NULL;
    72             storage->array[i].destructor = NULL;
    73         }
    74         if (SDL_SYS_SetTLSData(storage) != 0) {
    75             return -1;
    76         }
    77     }
    78 
    79     storage->array[id-1].data = SDL_const_cast(void*, value);
    80     storage->array[id-1].destructor = destructor;
    81     return 0;
    82 }
    83 
    84 static void
    85 SDL_TLSCleanup()
    86 {
    87     SDL_TLSData *storage;
    88 
    89     storage = SDL_SYS_GetTLSData();
    90     if (storage) {
    91         int i;
    92         for (i = 0; i < storage->limit; ++i) {
    93             if (storage->array[i].destructor) {
    94                 storage->array[i].destructor(storage->array[i].data);
    95             }
    96         }
    97         SDL_SYS_SetTLSData(NULL);
    98         SDL_free(storage);
    99     }
   100 }
   101 
   102 
   103 /* This is a generic implementation of thread-local storage which doesn't
   104    require additional OS support.
   105 
   106    It is not especially efficient and doesn't clean up thread-local storage
   107    as threads exit.  If there is a real OS that doesn't support thread-local
   108    storage this implementation should be improved to be production quality.
   109 */
   110 
   111 typedef struct SDL_TLSEntry {
   112     SDL_threadID thread;
   113     SDL_TLSData *storage;
   114     struct SDL_TLSEntry *next;
   115 } SDL_TLSEntry;
   116 
   117 static SDL_mutex *SDL_generic_TLS_mutex;
   118 static SDL_TLSEntry *SDL_generic_TLS;
   119 
   120 
   121 SDL_TLSData *
   122 SDL_Generic_GetTLSData()
   123 {
   124     SDL_threadID thread = SDL_ThreadID();
   125     SDL_TLSEntry *entry;
   126     SDL_TLSData *storage = NULL;
   127 
   128     if (!SDL_generic_TLS_mutex) {
   129         static SDL_SpinLock tls_lock;
   130         SDL_AtomicLock(&tls_lock);
   131         if (!SDL_generic_TLS_mutex) {
   132             SDL_mutex *mutex = SDL_CreateMutex();
   133             SDL_MemoryBarrierRelease();
   134             SDL_generic_TLS_mutex = mutex;
   135             if (!SDL_generic_TLS_mutex) {
   136                 SDL_AtomicUnlock(&tls_lock);
   137                 return NULL;
   138             }
   139         }
   140         SDL_AtomicUnlock(&tls_lock);
   141     }
   142 
   143     SDL_MemoryBarrierAcquire();
   144     SDL_LockMutex(SDL_generic_TLS_mutex);
   145     for (entry = SDL_generic_TLS; entry; entry = entry->next) {
   146         if (entry->thread == thread) {
   147             storage = entry->storage;
   148             break;
   149         }
   150     }
   151     SDL_UnlockMutex(SDL_generic_TLS_mutex);
   152 
   153     return storage;
   154 }
   155 
   156 int
   157 SDL_Generic_SetTLSData(SDL_TLSData *storage)
   158 {
   159     SDL_threadID thread = SDL_ThreadID();
   160     SDL_TLSEntry *prev, *entry;
   161 
   162     /* SDL_Generic_GetTLSData() is always called first, so we can assume SDL_generic_TLS_mutex */
   163     SDL_LockMutex(SDL_generic_TLS_mutex);
   164     prev = NULL;
   165     for (entry = SDL_generic_TLS; entry; entry = entry->next) {
   166         if (entry->thread == thread) {
   167             if (storage) {
   168                 entry->storage = storage;
   169             } else {
   170                 if (prev) {
   171                     prev->next = entry->next;
   172                 } else {
   173                     SDL_generic_TLS = entry->next;
   174                 }
   175                 SDL_free(entry);
   176             }
   177             break;
   178         }
   179         prev = entry;
   180     }
   181     if (!entry) {
   182         entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry));
   183         if (entry) {
   184             entry->thread = thread;
   185             entry->storage = storage;
   186             entry->next = SDL_generic_TLS;
   187             SDL_generic_TLS = entry;
   188         }
   189     }
   190     SDL_UnlockMutex(SDL_generic_TLS_mutex);
   191 
   192     if (!entry) {
   193         return SDL_OutOfMemory();
   194     }
   195     return 0;
   196 }
   197 
   198 /* Routine to get the thread-specific error variable */
   199 SDL_error *
   200 SDL_GetErrBuf(void)
   201 {
   202     static SDL_SpinLock tls_lock;
   203     static SDL_bool tls_being_created;
   204     static SDL_TLSID tls_errbuf;
   205     static SDL_error SDL_global_errbuf;
   206     const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1;
   207     SDL_error *errbuf;
   208 
   209     /* tls_being_created is there simply to prevent recursion if SDL_TLSCreate() fails.
   210        It also means it's possible for another thread to also use SDL_global_errbuf,
   211        but that's very unlikely and hopefully won't cause issues.
   212      */
   213     if (!tls_errbuf && !tls_being_created) {
   214         SDL_AtomicLock(&tls_lock);
   215         if (!tls_errbuf) {
   216             SDL_TLSID slot;
   217             tls_being_created = SDL_TRUE;
   218             slot = SDL_TLSCreate();
   219             tls_being_created = SDL_FALSE;
   220             SDL_MemoryBarrierRelease();
   221             tls_errbuf = slot;
   222         }
   223         SDL_AtomicUnlock(&tls_lock);
   224     }
   225     if (!tls_errbuf) {
   226         return &SDL_global_errbuf;
   227     }
   228 
   229     SDL_MemoryBarrierAcquire();
   230     errbuf = (SDL_error *)SDL_TLSGet(tls_errbuf);
   231     if (errbuf == ALLOCATION_IN_PROGRESS) {
   232         return &SDL_global_errbuf;
   233     }
   234     if (!errbuf) {
   235         /* Mark that we're in the middle of allocating our buffer */
   236         SDL_TLSSet(tls_errbuf, ALLOCATION_IN_PROGRESS, NULL);
   237         errbuf = (SDL_error *)SDL_malloc(sizeof(*errbuf));
   238         if (!errbuf) {
   239             SDL_TLSSet(tls_errbuf, NULL, NULL);
   240             return &SDL_global_errbuf;
   241         }
   242         SDL_zerop(errbuf);
   243         SDL_TLSSet(tls_errbuf, errbuf, SDL_free);
   244     }
   245     return errbuf;
   246 }
   247 
   248 
   249 /* Arguments and callback to setup and run the user thread function */
   250 typedef struct
   251 {
   252     int (SDLCALL * func) (void *);
   253     void *data;
   254     SDL_Thread *info;
   255     SDL_sem *wait;
   256 } thread_args;
   257 
   258 void
   259 SDL_RunThread(void *data)
   260 {
   261     thread_args *args = (thread_args *) data;
   262     int (SDLCALL * userfunc) (void *) = args->func;
   263     void *userdata = args->data;
   264     int *statusloc = &args->info->status;
   265 
   266     /* Perform any system-dependent setup - this function may not fail */
   267     SDL_SYS_SetupThread(args->info->name);
   268 
   269     /* Get the thread id */
   270     args->info->threadid = SDL_ThreadID();
   271 
   272     /* Wake up the parent thread */
   273     SDL_SemPost(args->wait);
   274 
   275     /* Run the function */
   276     *statusloc = userfunc(userdata);
   277 
   278     /* Clean up thread-local storage */
   279     SDL_TLSCleanup();
   280 }
   281 
   282 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
   283 #undef SDL_CreateThread
   284 DECLSPEC SDL_Thread *SDLCALL
   285 SDL_CreateThread(int (SDLCALL * fn) (void *),
   286                  const char *name, void *data,
   287                  pfnSDL_CurrentBeginThread pfnBeginThread,
   288                  pfnSDL_CurrentEndThread pfnEndThread)
   289 #else
   290 DECLSPEC SDL_Thread *SDLCALL
   291 SDL_CreateThread(int (SDLCALL * fn) (void *),
   292                  const char *name, void *data)
   293 #endif
   294 {
   295     SDL_Thread *thread;
   296     thread_args *args;
   297     int ret;
   298 
   299     /* Allocate memory for the thread info structure */
   300     thread = (SDL_Thread *) SDL_malloc(sizeof(*thread));
   301     if (thread == NULL) {
   302         SDL_OutOfMemory();
   303         return (NULL);
   304     }
   305     SDL_memset(thread, 0, (sizeof *thread));
   306     thread->status = -1;
   307 
   308     /* Set up the arguments for the thread */
   309     if (name != NULL) {
   310         thread->name = SDL_strdup(name);
   311         if (thread->name == NULL) {
   312             SDL_OutOfMemory();
   313             SDL_free(thread);
   314             return (NULL);
   315         }
   316     }
   317 
   318     /* Set up the arguments for the thread */
   319     args = (thread_args *) SDL_malloc(sizeof(*args));
   320     if (args == NULL) {
   321         SDL_OutOfMemory();
   322         SDL_free(thread->name);
   323         SDL_free(thread);
   324         return (NULL);
   325     }
   326     args->func = fn;
   327     args->data = data;
   328     args->info = thread;
   329     args->wait = SDL_CreateSemaphore(0);
   330     if (args->wait == NULL) {
   331         SDL_free(thread->name);
   332         SDL_free(thread);
   333         SDL_free(args);
   334         return (NULL);
   335     }
   336 
   337     /* Create the thread and go! */
   338 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
   339     ret = SDL_SYS_CreateThread(thread, args, pfnBeginThread, pfnEndThread);
   340 #else
   341     ret = SDL_SYS_CreateThread(thread, args);
   342 #endif
   343     if (ret >= 0) {
   344         /* Wait for the thread function to use arguments */
   345         SDL_SemWait(args->wait);
   346     } else {
   347         /* Oops, failed.  Gotta free everything */
   348         SDL_free(thread->name);
   349         SDL_free(thread);
   350         thread = NULL;
   351     }
   352     SDL_DestroySemaphore(args->wait);
   353     SDL_free(args);
   354 
   355     /* Everything is running now */
   356     return (thread);
   357 }
   358 
   359 SDL_threadID
   360 SDL_GetThreadID(SDL_Thread * thread)
   361 {
   362     SDL_threadID id;
   363 
   364     if (thread) {
   365         id = thread->threadid;
   366     } else {
   367         id = SDL_ThreadID();
   368     }
   369     return id;
   370 }
   371 
   372 const char *
   373 SDL_GetThreadName(SDL_Thread * thread)
   374 {
   375     return thread->name;
   376 }
   377 
   378 int
   379 SDL_SetThreadPriority(SDL_ThreadPriority priority)
   380 {
   381     return SDL_SYS_SetThreadPriority(priority);
   382 }
   383 
   384 void
   385 SDL_WaitThread(SDL_Thread * thread, int *status)
   386 {
   387     if (thread) {
   388         SDL_SYS_WaitThread(thread);
   389         if (status) {
   390             *status = thread->status;
   391         }
   392         SDL_free(thread->name);
   393         SDL_free(thread);
   394     }
   395 }
   396 
   397 /* vi: set ts=4 sw=4 expandtab: */