• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 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_hints.h"
30 #include "../SDL_error_c.h"
31 
32 
33 SDL_TLSID
SDL_TLSCreate()34 SDL_TLSCreate()
35 {
36     static SDL_atomic_t SDL_tls_id;
37     return SDL_AtomicIncRef(&SDL_tls_id)+1;
38 }
39 
40 void *
SDL_TLSGet(SDL_TLSID id)41 SDL_TLSGet(SDL_TLSID id)
42 {
43     SDL_TLSData *storage;
44 
45     storage = SDL_SYS_GetTLSData();
46     if (!storage || id == 0 || id > storage->limit) {
47         return NULL;
48     }
49     return storage->array[id-1].data;
50 }
51 
52 int
SDL_TLSSet(SDL_TLSID id,const void * value,void (* destructor)(void *))53 SDL_TLSSet(SDL_TLSID id, const void *value, void (*destructor)(void *))
54 {
55     SDL_TLSData *storage;
56 
57     if (id == 0) {
58         return SDL_InvalidParamError("id");
59     }
60 
61     storage = SDL_SYS_GetTLSData();
62     if (!storage || (id > storage->limit)) {
63         unsigned int i, oldlimit, newlimit;
64 
65         oldlimit = storage ? storage->limit : 0;
66         newlimit = (id + TLS_ALLOC_CHUNKSIZE);
67         storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage)+(newlimit-1)*sizeof(storage->array[0]));
68         if (!storage) {
69             return SDL_OutOfMemory();
70         }
71         storage->limit = newlimit;
72         for (i = oldlimit; i < newlimit; ++i) {
73             storage->array[i].data = NULL;
74             storage->array[i].destructor = NULL;
75         }
76         if (SDL_SYS_SetTLSData(storage) != 0) {
77             return -1;
78         }
79     }
80 
81     storage->array[id-1].data = SDL_const_cast(void*, value);
82     storage->array[id-1].destructor = destructor;
83     return 0;
84 }
85 
86 static void
SDL_TLSCleanup()87 SDL_TLSCleanup()
88 {
89     SDL_TLSData *storage;
90 
91     storage = SDL_SYS_GetTLSData();
92     if (storage) {
93         unsigned int i;
94         for (i = 0; i < storage->limit; ++i) {
95             if (storage->array[i].destructor) {
96                 storage->array[i].destructor(storage->array[i].data);
97             }
98         }
99         SDL_SYS_SetTLSData(NULL);
100         SDL_free(storage);
101     }
102 }
103 
104 
105 /* This is a generic implementation of thread-local storage which doesn't
106    require additional OS support.
107 
108    It is not especially efficient and doesn't clean up thread-local storage
109    as threads exit.  If there is a real OS that doesn't support thread-local
110    storage this implementation should be improved to be production quality.
111 */
112 
113 typedef struct SDL_TLSEntry {
114     SDL_threadID thread;
115     SDL_TLSData *storage;
116     struct SDL_TLSEntry *next;
117 } SDL_TLSEntry;
118 
119 static SDL_mutex *SDL_generic_TLS_mutex;
120 static SDL_TLSEntry *SDL_generic_TLS;
121 
122 
123 SDL_TLSData *
SDL_Generic_GetTLSData()124 SDL_Generic_GetTLSData()
125 {
126     SDL_threadID thread = SDL_ThreadID();
127     SDL_TLSEntry *entry;
128     SDL_TLSData *storage = NULL;
129 
130 #if !SDL_THREADS_DISABLED
131     if (!SDL_generic_TLS_mutex) {
132         static SDL_SpinLock tls_lock;
133         SDL_AtomicLock(&tls_lock);
134         if (!SDL_generic_TLS_mutex) {
135             SDL_mutex *mutex = SDL_CreateMutex();
136             SDL_MemoryBarrierRelease();
137             SDL_generic_TLS_mutex = mutex;
138             if (!SDL_generic_TLS_mutex) {
139                 SDL_AtomicUnlock(&tls_lock);
140                 return NULL;
141             }
142         }
143         SDL_AtomicUnlock(&tls_lock);
144     }
145 #endif /* SDL_THREADS_DISABLED */
146 
147     SDL_MemoryBarrierAcquire();
148     SDL_LockMutex(SDL_generic_TLS_mutex);
149     for (entry = SDL_generic_TLS; entry; entry = entry->next) {
150         if (entry->thread == thread) {
151             storage = entry->storage;
152             break;
153         }
154     }
155 #if !SDL_THREADS_DISABLED
156     SDL_UnlockMutex(SDL_generic_TLS_mutex);
157 #endif
158 
159     return storage;
160 }
161 
162 int
SDL_Generic_SetTLSData(SDL_TLSData * storage)163 SDL_Generic_SetTLSData(SDL_TLSData *storage)
164 {
165     SDL_threadID thread = SDL_ThreadID();
166     SDL_TLSEntry *prev, *entry;
167 
168     /* SDL_Generic_GetTLSData() is always called first, so we can assume SDL_generic_TLS_mutex */
169     SDL_LockMutex(SDL_generic_TLS_mutex);
170     prev = NULL;
171     for (entry = SDL_generic_TLS; entry; entry = entry->next) {
172         if (entry->thread == thread) {
173             if (storage) {
174                 entry->storage = storage;
175             } else {
176                 if (prev) {
177                     prev->next = entry->next;
178                 } else {
179                     SDL_generic_TLS = entry->next;
180                 }
181                 SDL_free(entry);
182             }
183             break;
184         }
185         prev = entry;
186     }
187     if (!entry) {
188         entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry));
189         if (entry) {
190             entry->thread = thread;
191             entry->storage = storage;
192             entry->next = SDL_generic_TLS;
193             SDL_generic_TLS = entry;
194         }
195     }
196     SDL_UnlockMutex(SDL_generic_TLS_mutex);
197 
198     if (!entry) {
199         return SDL_OutOfMemory();
200     }
201     return 0;
202 }
203 
204 /* Routine to get the thread-specific error variable */
205 SDL_error *
SDL_GetErrBuf(void)206 SDL_GetErrBuf(void)
207 {
208     static SDL_SpinLock tls_lock;
209     static SDL_bool tls_being_created;
210     static SDL_TLSID tls_errbuf;
211     static SDL_error SDL_global_errbuf;
212     const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1;
213     SDL_error *errbuf;
214 
215     /* tls_being_created is there simply to prevent recursion if SDL_TLSCreate() fails.
216        It also means it's possible for another thread to also use SDL_global_errbuf,
217        but that's very unlikely and hopefully won't cause issues.
218      */
219     if (!tls_errbuf && !tls_being_created) {
220         SDL_AtomicLock(&tls_lock);
221         if (!tls_errbuf) {
222             SDL_TLSID slot;
223             tls_being_created = SDL_TRUE;
224             slot = SDL_TLSCreate();
225             tls_being_created = SDL_FALSE;
226             SDL_MemoryBarrierRelease();
227             tls_errbuf = slot;
228         }
229         SDL_AtomicUnlock(&tls_lock);
230     }
231     if (!tls_errbuf) {
232         return &SDL_global_errbuf;
233     }
234 
235     SDL_MemoryBarrierAcquire();
236     errbuf = (SDL_error *)SDL_TLSGet(tls_errbuf);
237     if (errbuf == ALLOCATION_IN_PROGRESS) {
238         return &SDL_global_errbuf;
239     }
240     if (!errbuf) {
241         /* Mark that we're in the middle of allocating our buffer */
242         SDL_TLSSet(tls_errbuf, ALLOCATION_IN_PROGRESS, NULL);
243         errbuf = (SDL_error *)SDL_malloc(sizeof(*errbuf));
244         if (!errbuf) {
245             SDL_TLSSet(tls_errbuf, NULL, NULL);
246             return &SDL_global_errbuf;
247         }
248         SDL_zerop(errbuf);
249         SDL_TLSSet(tls_errbuf, errbuf, SDL_free);
250     }
251     return errbuf;
252 }
253 
254 
255 /* Arguments and callback to setup and run the user thread function */
256 typedef struct
257 {
258     int (SDLCALL * func) (void *);
259     void *data;
260     SDL_Thread *info;
261     SDL_sem *wait;
262 } thread_args;
263 
264 void
SDL_RunThread(void * data)265 SDL_RunThread(void *data)
266 {
267     thread_args *args = (thread_args *) data;
268     int (SDLCALL * userfunc) (void *) = args->func;
269     void *userdata = args->data;
270     SDL_Thread *thread = args->info;
271     int *statusloc = &thread->status;
272 
273     /* Perform any system-dependent setup - this function may not fail */
274     SDL_SYS_SetupThread(thread->name);
275 
276     /* Get the thread id */
277     thread->threadid = SDL_ThreadID();
278 
279     /* Wake up the parent thread */
280     SDL_SemPost(args->wait);
281 
282     /* Run the function */
283     *statusloc = userfunc(userdata);
284 
285     /* Clean up thread-local storage */
286     SDL_TLSCleanup();
287 
288     /* Mark us as ready to be joined (or detached) */
289     if (!SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) {
290         /* Clean up if something already detached us. */
291         if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) {
292             if (thread->name) {
293                 SDL_free(thread->name);
294             }
295             SDL_free(thread);
296         }
297     }
298 }
299 
300 #ifdef SDL_CreateThread
301 #undef SDL_CreateThread
302 #endif
303 #if SDL_DYNAMIC_API
304 #define SDL_CreateThread SDL_CreateThread_REAL
305 #endif
306 
307 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
308 static SDL_Thread *
SDL_CreateThreadWithStackSize(int (SDLCALL * fn)(void *),const char * name,const size_t stacksize,void * data,pfnSDL_CurrentBeginThread pfnBeginThread,pfnSDL_CurrentEndThread pfnEndThread)309 SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *),
310                  const char *name, const size_t stacksize, void *data,
311                  pfnSDL_CurrentBeginThread pfnBeginThread,
312                  pfnSDL_CurrentEndThread pfnEndThread)
313 #else
314 static SDL_Thread *
315 SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *),
316                 const char *name, const size_t stacksize, void *data)
317 #endif
318 {
319     SDL_Thread *thread;
320     thread_args *args;
321     int ret;
322 
323     /* Allocate memory for the thread info structure */
324     thread = (SDL_Thread *) SDL_malloc(sizeof(*thread));
325     if (thread == NULL) {
326         SDL_OutOfMemory();
327         return (NULL);
328     }
329     SDL_zerop(thread);
330     thread->status = -1;
331     SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE);
332 
333     /* Set up the arguments for the thread */
334     if (name != NULL) {
335         thread->name = SDL_strdup(name);
336         if (thread->name == NULL) {
337             SDL_OutOfMemory();
338             SDL_free(thread);
339             return (NULL);
340         }
341     }
342 
343     /* Set up the arguments for the thread */
344     args = (thread_args *) SDL_malloc(sizeof(*args));
345     if (args == NULL) {
346         SDL_OutOfMemory();
347         if (thread->name) {
348             SDL_free(thread->name);
349         }
350         SDL_free(thread);
351         return (NULL);
352     }
353     args->func = fn;
354     args->data = data;
355     args->info = thread;
356     args->wait = SDL_CreateSemaphore(0);
357     if (args->wait == NULL) {
358         if (thread->name) {
359             SDL_free(thread->name);
360         }
361         SDL_free(thread);
362         SDL_free(args);
363         return (NULL);
364     }
365 
366     thread->stacksize = stacksize;
367 
368     /* Create the thread and go! */
369 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
370     ret = SDL_SYS_CreateThread(thread, args, pfnBeginThread, pfnEndThread);
371 #else
372     ret = SDL_SYS_CreateThread(thread, args);
373 #endif
374     if (ret >= 0) {
375         /* Wait for the thread function to use arguments */
376         SDL_SemWait(args->wait);
377     } else {
378         /* Oops, failed.  Gotta free everything */
379         if (thread->name) {
380             SDL_free(thread->name);
381         }
382         SDL_free(thread);
383         thread = NULL;
384     }
385     SDL_DestroySemaphore(args->wait);
386     SDL_free(args);
387 
388     /* Everything is running now */
389     return (thread);
390 }
391 
392 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
393 DECLSPEC SDL_Thread *SDLCALL
SDL_CreateThread(int (SDLCALL * fn)(void *),const char * name,void * data,pfnSDL_CurrentBeginThread pfnBeginThread,pfnSDL_CurrentEndThread pfnEndThread)394 SDL_CreateThread(int (SDLCALL * fn) (void *),
395                  const char *name, void *data,
396                  pfnSDL_CurrentBeginThread pfnBeginThread,
397                  pfnSDL_CurrentEndThread pfnEndThread)
398 #else
399 DECLSPEC SDL_Thread *SDLCALL
400 SDL_CreateThread(int (SDLCALL * fn) (void *),
401                  const char *name, void *data)
402 #endif
403 {
404     /* !!! FIXME: in 2.1, just make stackhint part of the usual API. */
405     const char *stackhint = SDL_GetHint(SDL_HINT_THREAD_STACK_SIZE);
406     size_t stacksize = 0;
407 
408     /* If the SDL_HINT_THREAD_STACK_SIZE exists, use it */
409     if (stackhint != NULL) {
410         char *endp = NULL;
411         const Sint64 hintval = SDL_strtoll(stackhint, &endp, 10);
412         if ((*stackhint != '\0') && (*endp == '\0')) {  /* a valid number? */
413             if (hintval > 0) {  /* reject bogus values. */
414                 stacksize = (size_t) hintval;
415             }
416         }
417     }
418 
419 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
420     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data, pfnBeginThread, pfnEndThread);
421 #else
422     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data);
423 #endif
424 }
425 
426 SDL_Thread *
SDL_CreateThreadInternal(int (SDLCALL * fn)(void *),const char * name,const size_t stacksize,void * data)427 SDL_CreateThreadInternal(int (SDLCALL * fn) (void *), const char *name,
428                          const size_t stacksize, void *data) {
429 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD
430     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data, NULL, NULL);
431 #else
432     return SDL_CreateThreadWithStackSize(fn, name, stacksize, data);
433 #endif
434 }
435 
436 SDL_threadID
SDL_GetThreadID(SDL_Thread * thread)437 SDL_GetThreadID(SDL_Thread * thread)
438 {
439     SDL_threadID id;
440 
441     if (thread) {
442         id = thread->threadid;
443     } else {
444         id = SDL_ThreadID();
445     }
446     return id;
447 }
448 
449 const char *
SDL_GetThreadName(SDL_Thread * thread)450 SDL_GetThreadName(SDL_Thread * thread)
451 {
452     if (thread) {
453         return thread->name;
454     } else {
455         return NULL;
456     }
457 }
458 
459 int
SDL_SetThreadPriority(SDL_ThreadPriority priority)460 SDL_SetThreadPriority(SDL_ThreadPriority priority)
461 {
462     return SDL_SYS_SetThreadPriority(priority);
463 }
464 
465 void
SDL_WaitThread(SDL_Thread * thread,int * status)466 SDL_WaitThread(SDL_Thread * thread, int *status)
467 {
468     if (thread) {
469         SDL_SYS_WaitThread(thread);
470         if (status) {
471             *status = thread->status;
472         }
473         if (thread->name) {
474             SDL_free(thread->name);
475         }
476         SDL_free(thread);
477     }
478 }
479 
480 void
SDL_DetachThread(SDL_Thread * thread)481 SDL_DetachThread(SDL_Thread * thread)
482 {
483     if (!thread) {
484         return;
485     }
486 
487     /* Grab dibs if the state is alive+joinable. */
488     if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_DETACHED)) {
489         SDL_SYS_DetachThread(thread);
490     } else {
491         /* all other states are pretty final, see where we landed. */
492         const int thread_state = SDL_AtomicGet(&thread->state);
493         if ((thread_state == SDL_THREAD_STATE_DETACHED) || (thread_state == SDL_THREAD_STATE_CLEANED)) {
494             return;  /* already detached (you shouldn't call this twice!) */
495         } else if (thread_state == SDL_THREAD_STATE_ZOMBIE) {
496             SDL_WaitThread(thread, NULL);  /* already done, clean it up. */
497         } else {
498             SDL_assert(0 && "Unexpected thread state");
499         }
500     }
501 }
502 
503 /* vi: set ts=4 sw=4 expandtab: */
504