1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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_c.h"
26#include "SDL_systhread.h"
27#include "../SDL_error_c.h"
28
29// The storage is local to the thread, but the IDs are global for the process
30
31static SDL_AtomicInt SDL_tls_allocated;
32static SDL_AtomicInt SDL_tls_id;
33
34void SDL_InitTLSData(void)
35{
36 SDL_SYS_InitTLSData();
37}
38
39void *SDL_GetTLS(SDL_TLSID *id)
40{
41 SDL_TLSData *storage;
42 int storage_index;
43
44 if (id == NULL) {
45 SDL_InvalidParamError("id");
46 return NULL;
47 }
48
49 storage_index = SDL_GetAtomicInt(id) - 1;
50 storage = SDL_SYS_GetTLSData();
51 if (!storage || storage_index < 0 || storage_index >= storage->limit) {
52 return NULL;
53 }
54 return storage->array[storage_index].data;
55}
56
57bool SDL_SetTLS(SDL_TLSID *id, const void *value, SDL_TLSDestructorCallback destructor)
58{
59 SDL_TLSData *storage;
60 int storage_index;
61
62 if (id == NULL) {
63 return SDL_InvalidParamError("id");
64 }
65
66 /* Make sure TLS is initialized.
67 * There's a race condition here if you are calling this from non-SDL threads
68 * and haven't called SDL_Init() on your main thread, but such is life.
69 */
70 SDL_InitTLSData();
71
72 // Get the storage index associated with the ID in a thread-safe way
73 storage_index = SDL_GetAtomicInt(id) - 1;
74 if (storage_index < 0) {
75 int new_id = (SDL_AtomicIncRef(&SDL_tls_id) + 1);
76
77 SDL_CompareAndSwapAtomicInt(id, 0, new_id);
78
79 /* If there was a race condition we'll have wasted an ID, but every thread
80 * will have the same storage index for this id.
81 */
82 storage_index = SDL_GetAtomicInt(id) - 1;
83 }
84
85 // Get the storage for the current thread
86 storage = SDL_SYS_GetTLSData();
87 if (!storage || storage_index >= storage->limit) {
88 unsigned int i, oldlimit, newlimit;
89 SDL_TLSData *new_storage;
90
91 oldlimit = storage ? storage->limit : 0;
92 newlimit = (storage_index + TLS_ALLOC_CHUNKSIZE);
93 new_storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage) + (newlimit - 1) * sizeof(storage->array[0]));
94 if (!new_storage) {
95 return false;
96 }
97 storage = new_storage;
98 storage->limit = newlimit;
99 for (i = oldlimit; i < newlimit; ++i) {
100 storage->array[i].data = NULL;
101 storage->array[i].destructor = NULL;
102 }
103 if (!SDL_SYS_SetTLSData(storage)) {
104 SDL_free(storage);
105 return false;
106 }
107 SDL_AtomicIncRef(&SDL_tls_allocated);
108 }
109
110 storage->array[storage_index].data = SDL_const_cast(void *, value);
111 storage->array[storage_index].destructor = destructor;
112 return true;
113}
114
115void SDL_CleanupTLS(void)
116{
117 SDL_TLSData *storage;
118
119 // Cleanup the storage for the current thread
120 storage = SDL_SYS_GetTLSData();
121 if (storage) {
122 int i;
123 for (i = 0; i < storage->limit; ++i) {
124 if (storage->array[i].destructor) {
125 storage->array[i].destructor(storage->array[i].data);
126 }
127 }
128 SDL_SYS_SetTLSData(NULL);
129 SDL_free(storage);
130 (void)SDL_AtomicDecRef(&SDL_tls_allocated);
131 }
132}
133
134void SDL_QuitTLSData(void)
135{
136 SDL_CleanupTLS();
137
138 if (SDL_GetAtomicInt(&SDL_tls_allocated) == 0) {
139 SDL_SYS_QuitTLSData();
140 } else {
141 // Some thread hasn't called SDL_CleanupTLS()
142 }
143}
144
145/* This is a generic implementation of thread-local storage which doesn't
146 require additional OS support.
147
148 It is not especially efficient and doesn't clean up thread-local storage
149 as threads exit. If there is a real OS that doesn't support thread-local
150 storage this implementation should be improved to be production quality.
151*/
152
153typedef struct SDL_TLSEntry
154{
155 SDL_ThreadID thread;
156 SDL_TLSData *storage;
157 struct SDL_TLSEntry *next;
158} SDL_TLSEntry;
159
160static SDL_Mutex *SDL_generic_TLS_mutex;
161static SDL_TLSEntry *SDL_generic_TLS;
162
163void SDL_Generic_InitTLSData(void)
164{
165 if (!SDL_generic_TLS_mutex) {
166 SDL_generic_TLS_mutex = SDL_CreateMutex();
167 }
168}
169
170SDL_TLSData *SDL_Generic_GetTLSData(void)
171{
172 SDL_ThreadID thread = SDL_GetCurrentThreadID();
173 SDL_TLSEntry *entry;
174 SDL_TLSData *storage = NULL;
175
176 SDL_LockMutex(SDL_generic_TLS_mutex);
177 for (entry = SDL_generic_TLS; entry; entry = entry->next) {
178 if (entry->thread == thread) {
179 storage = entry->storage;
180 break;
181 }
182 }
183 SDL_UnlockMutex(SDL_generic_TLS_mutex);
184
185 return storage;
186}
187
188bool SDL_Generic_SetTLSData(SDL_TLSData *data)
189{
190 SDL_ThreadID thread = SDL_GetCurrentThreadID();
191 SDL_TLSEntry *prev, *entry;
192 bool result = true;
193
194 SDL_LockMutex(SDL_generic_TLS_mutex);
195 prev = NULL;
196 for (entry = SDL_generic_TLS; entry; entry = entry->next) {
197 if (entry->thread == thread) {
198 if (data) {
199 entry->storage = data;
200 } else {
201 if (prev) {
202 prev->next = entry->next;
203 } else {
204 SDL_generic_TLS = entry->next;
205 }
206 SDL_free(entry);
207 }
208 break;
209 }
210 prev = entry;
211 }
212 if (!entry && data) {
213 entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry));
214 if (entry) {
215 entry->thread = thread;
216 entry->storage = data;
217 entry->next = SDL_generic_TLS;
218 SDL_generic_TLS = entry;
219 } else {
220 result = false;
221 }
222 }
223 SDL_UnlockMutex(SDL_generic_TLS_mutex);
224
225 return result;
226}
227
228void SDL_Generic_QuitTLSData(void)
229{
230 SDL_TLSEntry *entry;
231
232 // This should have been cleaned up by the time we get here
233 SDL_assert(!SDL_generic_TLS);
234 if (SDL_generic_TLS) {
235 SDL_LockMutex(SDL_generic_TLS_mutex);
236 for (entry = SDL_generic_TLS; entry; ) {
237 SDL_TLSEntry *next = entry->next;
238 SDL_free(entry->storage);
239 SDL_free(entry);
240 entry = next;
241 }
242 SDL_generic_TLS = NULL;
243 SDL_UnlockMutex(SDL_generic_TLS_mutex);
244 }
245
246 if (SDL_generic_TLS_mutex) {
247 SDL_DestroyMutex(SDL_generic_TLS_mutex);
248 SDL_generic_TLS_mutex = NULL;
249 }
250}
251
252// Non-thread-safe global error variable
253static SDL_error *SDL_GetStaticErrBuf(void)
254{
255 static SDL_error SDL_global_error;
256 static char SDL_global_error_str[128];
257 SDL_global_error.str = SDL_global_error_str;
258 SDL_global_error.len = sizeof(SDL_global_error_str);
259 return &SDL_global_error;
260}
261
262#ifndef SDL_THREADS_DISABLED
263static void SDLCALL SDL_FreeErrBuf(void *data)
264{
265 SDL_error *errbuf = (SDL_error *)data;
266
267 if (errbuf->str) {
268 errbuf->free_func(errbuf->str);
269 }
270 errbuf->free_func(errbuf);
271}
272#endif
273
274// Routine to get the thread-specific error variable
275SDL_error *SDL_GetErrBuf(bool create)
276{
277#ifdef SDL_THREADS_DISABLED
278 return SDL_GetStaticErrBuf();
279#else
280 static SDL_TLSID tls_errbuf;
281 SDL_error *errbuf;
282
283 errbuf = (SDL_error *)SDL_GetTLS(&tls_errbuf);
284 if (!errbuf) {
285 if (!create) {
286 return NULL;
287 }
288
289 /* Get the original memory functions for this allocation because the lifetime
290 * of the error buffer may span calls to SDL_SetMemoryFunctions() by the app
291 */
292 SDL_realloc_func realloc_func;
293 SDL_free_func free_func;
294 SDL_GetOriginalMemoryFunctions(NULL, NULL, &realloc_func, &free_func);
295
296 errbuf = (SDL_error *)realloc_func(NULL, sizeof(*errbuf));
297 if (!errbuf) {
298 return SDL_GetStaticErrBuf();
299 }
300 SDL_zerop(errbuf);
301 errbuf->realloc_func = realloc_func;
302 errbuf->free_func = free_func;
303 SDL_SetTLS(&tls_errbuf, errbuf, SDL_FreeErrBuf);
304 }
305 return errbuf;
306#endif // SDL_THREADS_DISABLED
307}
308
309static bool ThreadValid(SDL_Thread *thread)
310{
311 return SDL_ObjectValid(thread, SDL_OBJECT_TYPE_THREAD);
312}
313
314void SDL_RunThread(SDL_Thread *thread)
315{
316 void *userdata = thread->userdata;
317 int(SDLCALL *userfunc)(void *) = thread->userfunc;
318
319 int *statusloc = &thread->status;
320
321 // Perform any system-dependent setup - this function may not fail
322 SDL_SYS_SetupThread(thread->name);
323
324 // Get the thread id
325 thread->threadid = SDL_GetCurrentThreadID();
326
327 // Run the function
328 *statusloc = userfunc(userdata);
329
330 // Clean up thread-local storage
331 SDL_CleanupTLS();
332
333 // Mark us as ready to be joined (or detached)
334 if (!SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_COMPLETE)) {
335 // Clean up if something already detached us.
336 if (SDL_GetThreadState(thread) == SDL_THREAD_DETACHED) {
337 SDL_free(thread->name); // Can't free later, we've already cleaned up TLS
338 SDL_free(thread);
339 }
340 }
341}
342
343SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props,
344 SDL_FunctionPointer pfnBeginThread,
345 SDL_FunctionPointer pfnEndThread)
346{
347 // rather than check this in every backend, just make sure it's correct upfront. Only allow non-NULL if Windows, or Microsoft GDK.
348 #if !defined(SDL_PLATFORM_WINDOWS)
349 if (pfnBeginThread || pfnEndThread) {
350 SDL_SetError("_beginthreadex/_endthreadex not supported on this platform");
351 return NULL;
352 }
353 #endif
354
355 SDL_ThreadFunction fn = (SDL_ThreadFunction) SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, NULL);
356 const char *name = SDL_GetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, NULL);
357 const size_t stacksize = (size_t) SDL_GetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, 0);
358 void *userdata = SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, NULL);
359
360 if (!fn) {
361 SDL_SetError("Thread entry function is NULL");
362 return NULL;
363 }
364
365 SDL_InitMainThread();
366
367 SDL_Thread *thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread));
368 if (!thread) {
369 return NULL;
370 }
371 thread->status = -1;
372 SDL_SetAtomicInt(&thread->state, SDL_THREAD_ALIVE);
373
374 // Set up the arguments for the thread
375 if (name) {
376 thread->name = SDL_strdup(name);
377 if (!thread->name) {
378 SDL_free(thread);
379 return NULL;
380 }
381 }
382
383 thread->userfunc = fn;
384 thread->userdata = userdata;
385 thread->stacksize = stacksize;
386
387 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, true);
388
389 // Create the thread and go!
390 if (!SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread)) {
391 // Oops, failed. Gotta free everything
392 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false);
393 SDL_free(thread->name);
394 SDL_free(thread);
395 thread = NULL;
396 }
397
398 // Everything is running now
399 return thread;
400}
401
402SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn,
403 const char *name, void *userdata,
404 SDL_FunctionPointer pfnBeginThread,
405 SDL_FunctionPointer pfnEndThread)
406{
407 const SDL_PropertiesID props = SDL_CreateProperties();
408 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
409 SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
410 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata);
411 SDL_Thread *thread = SDL_CreateThreadWithPropertiesRuntime(props, pfnBeginThread, pfnEndThread);
412 SDL_DestroyProperties(props);
413 return thread;
414}
415
416// internal helper function, not in the public API.
417SDL_Thread *SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, size_t stacksize, void *userdata)
418{
419 const SDL_PropertiesID props = SDL_CreateProperties();
420 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
421 SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
422 SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata);
423 SDL_SetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, (Sint64) stacksize);
424 SDL_Thread *thread = SDL_CreateThreadWithProperties(props);
425 SDL_DestroyProperties(props);
426 return thread;
427}
428
429SDL_ThreadID SDL_GetThreadID(SDL_Thread *thread)
430{
431 SDL_ThreadID id = 0;
432
433 if (thread) {
434 if (ThreadValid(thread)) {
435 id = thread->threadid;
436 }
437 } else {
438 id = SDL_GetCurrentThreadID();
439 }
440 return id;
441}
442
443const char *SDL_GetThreadName(SDL_Thread *thread)
444{
445 if (ThreadValid(thread)) {
446 return SDL_GetPersistentString(thread->name);
447 } else {
448 return NULL;
449 }
450}
451
452bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority)
453{
454 return SDL_SYS_SetThreadPriority(priority);
455}
456
457void SDL_WaitThread(SDL_Thread *thread, int *status)
458{
459 if (!ThreadValid(thread)) {
460 if (status) {
461 *status = -1;
462 }
463 return;
464 }
465
466 SDL_SYS_WaitThread(thread);
467 if (status) {
468 *status = thread->status;
469 }
470 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false);
471 SDL_free(thread->name);
472 SDL_free(thread);
473}
474
475SDL_ThreadState SDL_GetThreadState(SDL_Thread *thread)
476{
477 if (!ThreadValid(thread)) {
478 return SDL_THREAD_UNKNOWN;
479 }
480
481 return (SDL_ThreadState)SDL_GetAtomicInt(&thread->state);
482}
483
484void SDL_DetachThread(SDL_Thread *thread)
485{
486 if (!ThreadValid(thread)) {
487 return;
488 }
489
490 // The thread may vanish at any time, it's no longer valid
491 SDL_SetObjectValid(thread, SDL_OBJECT_TYPE_THREAD, false);
492
493 // Grab dibs if the state is alive+joinable.
494 if (SDL_CompareAndSwapAtomicInt(&thread->state, SDL_THREAD_ALIVE, SDL_THREAD_DETACHED)) {
495 SDL_SYS_DetachThread(thread);
496 } else {
497 // all other states are pretty final, see where we landed.
498 SDL_ThreadState thread_state = SDL_GetThreadState(thread);
499 if (thread_state == SDL_THREAD_DETACHED) {
500 return; // already detached (you shouldn't call this twice!)
501 } else if (thread_state == SDL_THREAD_COMPLETE) {
502 SDL_WaitThread(thread, NULL); // already done, clean it up.
503 }
504 }
505}
506
507void SDL_WaitSemaphore(SDL_Semaphore *sem)
508{
509 SDL_WaitSemaphoreTimeoutNS(sem, -1);
510}
511
512bool SDL_TryWaitSemaphore(SDL_Semaphore *sem)
513{
514 return SDL_WaitSemaphoreTimeoutNS(sem, 0);
515}
516
517bool SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS)
518{
519 Sint64 timeoutNS;
520
521 if (timeoutMS >= 0) {
522 timeoutNS = SDL_MS_TO_NS(timeoutMS);
523 } else {
524 timeoutNS = -1;
525 }
526 return SDL_WaitSemaphoreTimeoutNS(sem, timeoutNS);
527}
528
529void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex)
530{
531 SDL_WaitConditionTimeoutNS(cond, mutex, -1);
532}
533
534bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS)
535{
536 Sint64 timeoutNS;
537
538 if (timeoutMS >= 0) {
539 timeoutNS = SDL_MS_TO_NS(timeoutMS);
540 } else {
541 timeoutNS = -1;
542 }
543 return SDL_WaitConditionTimeoutNS(cond, mutex, timeoutNS);
544}
545
546bool SDL_ShouldInit(SDL_InitState *state)
547{
548 while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_INITIALIZED) {
549 if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED, SDL_INIT_STATUS_INITIALIZING)) {
550 state->thread = SDL_GetCurrentThreadID();
551 return true;
552 }
553
554 // Wait for the other thread to complete transition
555 SDL_Delay(1);
556 }
557 return false;
558}
559
560bool SDL_ShouldQuit(SDL_InitState *state)
561{
562 while (SDL_GetAtomicInt(&state->status) != SDL_INIT_STATUS_UNINITIALIZED) {
563 if (SDL_CompareAndSwapAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED, SDL_INIT_STATUS_UNINITIALIZING)) {
564 state->thread = SDL_GetCurrentThreadID();
565 return true;
566 }
567
568 // Wait for the other thread to complete transition
569 SDL_Delay(1);
570 }
571 return false;
572}
573
574void SDL_SetInitialized(SDL_InitState *state, bool initialized)
575{
576 SDL_assert(state->thread == SDL_GetCurrentThreadID());
577
578 if (initialized) {
579 SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_INITIALIZED);
580 } else {
581 SDL_SetAtomicInt(&state->status, SDL_INIT_STATUS_UNINITIALIZED);
582 }
583}
584
585