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