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