sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_thread.c (13225B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 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_thread.h"
     26 #include "SDL_thread_c.h"
     27 #include "SDL_systhread.h"
     28 #include "SDL_hints.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 (SDLCALL *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(void)
    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 #if SDL_THREADS_DISABLED
    208     /* Non-thread-safe global error variable */
    209     static SDL_error SDL_global_error;
    210     return &SDL_global_error;
    211 #else
    212     static SDL_SpinLock tls_lock;
    213     static SDL_bool tls_being_created;
    214     static SDL_TLSID tls_errbuf;
    215     static SDL_error SDL_global_errbuf;
    216     const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1;
    217     SDL_error *errbuf;
    218 
    219     /* tls_being_created is there simply to prevent recursion if SDL_TLSCreate() fails.
    220        It also means it's possible for another thread to also use SDL_global_errbuf,
    221        but that's very unlikely and hopefully won't cause issues.
    222      */
    223     if (!tls_errbuf && !tls_being_created) {
    224         SDL_AtomicLock(&tls_lock);
    225         if (!tls_errbuf) {
    226             SDL_TLSID slot;
    227             tls_being_created = SDL_TRUE;
    228             slot = SDL_TLSCreate();
    229             tls_being_created = SDL_FALSE;
    230             SDL_MemoryBarrierRelease();
    231             tls_errbuf = slot;
    232         }
    233         SDL_AtomicUnlock(&tls_lock);
    234     }
    235     if (!tls_errbuf) {
    236         return &SDL_global_errbuf;
    237     }
    238 
    239     SDL_MemoryBarrierAcquire();
    240     errbuf = (SDL_error *)SDL_TLSGet(tls_errbuf);
    241     if (errbuf == ALLOCATION_IN_PROGRESS) {
    242         return &SDL_global_errbuf;
    243     }
    244     if (!errbuf) {
    245         /* Mark that we're in the middle of allocating our buffer */
    246         SDL_TLSSet(tls_errbuf, ALLOCATION_IN_PROGRESS, NULL);
    247         errbuf = (SDL_error *)SDL_malloc(sizeof(*errbuf));
    248         if (!errbuf) {
    249             SDL_TLSSet(tls_errbuf, NULL, NULL);
    250             return &SDL_global_errbuf;
    251         }
    252         SDL_zerop(errbuf);
    253         SDL_TLSSet(tls_errbuf, errbuf, SDL_free);
    254     }
    255     return errbuf;
    256 #endif /* SDL_THREADS_DISABLED */
    257 }
    258 
    259 
    260 void
    261 SDL_RunThread(SDL_Thread *thread)
    262 {
    263     void *userdata = thread->userdata;
    264     int (SDLCALL * userfunc) (void *) = thread->userfunc;
    265 
    266     int *statusloc = &thread->status;
    267 
    268     /* Perform any system-dependent setup - this function may not fail */
    269     SDL_SYS_SetupThread(thread->name);
    270 
    271     /* Get the thread id */
    272     thread->threadid = SDL_ThreadID();
    273 
    274     /* Run the function */
    275     *statusloc = userfunc(userdata);
    276 
    277     /* Clean up thread-local storage */
    278     SDL_TLSCleanup();
    279 
    280     /* Mark us as ready to be joined (or detached) */
    281     if (!SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) {
    282         /* Clean up if something already detached us. */
    283         if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) {
    284             if (thread->name) {
    285                 SDL_free(thread->name);
    286             }
    287             SDL_free(thread);
    288         }
    289     }
    290 }
    291 
    292 #ifdef SDL_CreateThread
    293 #undef SDL_CreateThread
    294 #undef SDL_CreateThreadWithStackSize
    295 #endif
    296 #if SDL_DYNAMIC_API
    297 #define SDL_CreateThread SDL_CreateThread_REAL
    298 #define SDL_CreateThreadWithStackSize SDL_CreateThreadWithStackSize_REAL
    299 #endif
    300 
    301 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
    302 SDL_Thread *
    303 SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *),
    304                  const char *name, const size_t stacksize, void *data,
    305                  pfnSDL_CurrentBeginThread pfnBeginThread,
    306                  pfnSDL_CurrentEndThread pfnEndThread)
    307 #else
    308 SDL_Thread *
    309 SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *),
    310                 const char *name, const size_t stacksize, void *data)
    311 #endif
    312 {
    313     SDL_Thread *thread;
    314     int ret;
    315 
    316     /* Allocate memory for the thread info structure */
    317     thread = (SDL_Thread *) SDL_calloc(1, sizeof(*thread));
    318     if (thread == NULL) {
    319         SDL_OutOfMemory();
    320         return NULL;
    321     }
    322     thread->status = -1;
    323     SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE);
    324 
    325     /* Set up the arguments for the thread */
    326     if (name != NULL) {
    327         thread->name = SDL_strdup(name);
    328         if (thread->name == NULL) {
    329             SDL_OutOfMemory();
    330             SDL_free(thread);
    331             return NULL;
    332         }
    333     }
    334 
    335     thread->userfunc = fn;
    336     thread->userdata = data;
    337     thread->stacksize = stacksize;
    338 
    339     /* Create the thread and go! */
    340 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
    341     ret = SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread);
    342 #else
    343     ret = SDL_SYS_CreateThread(thread);
    344 #endif
    345     if (ret < 0) {
    346         /* Oops, failed.  Gotta free everything */
    347         SDL_free(thread->name);
    348         SDL_free(thread);
    349         thread = NULL;
    350     }
    351 
    352     /* Everything is running now */
    353     return thread;
    354 }
    355 
    356 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
    357 DECLSPEC SDL_Thread *SDLCALL
    358 SDL_CreateThread(int (SDLCALL * fn) (void *),
    359                  const char *name, void *data,
    360                  pfnSDL_CurrentBeginThread pfnBeginThread,
    361                  pfnSDL_CurrentEndThread pfnEndThread)
    362 #else
    363 DECLSPEC SDL_Thread *SDLCALL
    364 SDL_CreateThread(int (SDLCALL * fn) (void *),
    365                  const char *name, void *data)
    366 #endif
    367 {
    368     /* !!! FIXME: in 2.1, just make stackhint part of the usual API. */
    369     const char *stackhint = SDL_GetHint(SDL_HINT_THREAD_STACK_SIZE);
    370     size_t stacksize = 0;
    371 
    372     /* If the SDL_HINT_THREAD_STACK_SIZE exists, use it */
    373     if (stackhint != NULL) {
    374         char *endp = NULL;
    375         const Sint64 hintval = SDL_strtoll(stackhint, &endp, 10);
    376         if ((*stackhint != '\0') && (*endp == '\0')) {  /* a valid number? */
    377             if (hintval > 0) {  /* reject bogus values. */
    378                 stacksize = (size_t) hintval;
    379             }
    380         }
    381     }
    382 
    383 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
    384     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data, pfnBeginThread, pfnEndThread);
    385 #else
    386     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data);
    387 #endif
    388 }
    389 
    390 SDL_Thread *
    391 SDL_CreateThreadInternal(int (SDLCALL * fn) (void *), const char *name,
    392                          const size_t stacksize, void *data) {
    393 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
    394     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data, NULL, NULL);
    395 #else
    396     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data);
    397 #endif
    398 }
    399 
    400 SDL_threadID
    401 SDL_GetThreadID(SDL_Thread * thread)
    402 {
    403     SDL_threadID id;
    404 
    405     if (thread) {
    406         id = thread->threadid;
    407     } else {
    408         id = SDL_ThreadID();
    409     }
    410     return id;
    411 }
    412 
    413 const char *
    414 SDL_GetThreadName(SDL_Thread * thread)
    415 {
    416     if (thread) {
    417         return thread->name;
    418     } else {
    419         return NULL;
    420     }
    421 }
    422 
    423 int
    424 SDL_SetThreadPriority(SDL_ThreadPriority priority)
    425 {
    426     return SDL_SYS_SetThreadPriority(priority);
    427 }
    428 
    429 void
    430 SDL_WaitThread(SDL_Thread * thread, int *status)
    431 {
    432     if (thread) {
    433         SDL_SYS_WaitThread(thread);
    434         if (status) {
    435             *status = thread->status;
    436         }
    437         if (thread->name) {
    438             SDL_free(thread->name);
    439         }
    440         SDL_free(thread);
    441     }
    442 }
    443 
    444 void
    445 SDL_DetachThread(SDL_Thread * thread)
    446 {
    447     if (!thread) {
    448         return;
    449     }
    450 
    451     /* Grab dibs if the state is alive+joinable. */
    452     if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_DETACHED)) {
    453         SDL_SYS_DetachThread(thread);
    454     } else {
    455         /* all other states are pretty final, see where we landed. */
    456         const int thread_state = SDL_AtomicGet(&thread->state);
    457         if ((thread_state == SDL_THREAD_STATE_DETACHED) || (thread_state == SDL_THREAD_STATE_CLEANED)) {
    458             return;  /* already detached (you shouldn't call this twice!) */
    459         } else if (thread_state == SDL_THREAD_STATE_ZOMBIE) {
    460             SDL_WaitThread(thread, NULL);  /* already done, clean it up. */
    461         } else {
    462             SDL_assert(0 && "Unexpected thread state");
    463         }
    464     }
    465 }
    466 
    467 /* vi: set ts=4 sw=4 expandtab: */