2
Simple DirectMedia Layer
3
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
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.
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:
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.
21
#include "SDL_internal.h"
23
// System independent thread management routines for SDL
25
#include "SDL_thread_c.h"
26
#include "SDL_systhread.h"
27
#include "../SDL_error_c.h"
29
// The storage is local to the thread, but the IDs are global for the process
31
static SDL_AtomicInt SDL_tls_allocated;
32
static SDL_AtomicInt SDL_tls_id;
34
void SDL_InitTLSData(void)
36
SDL_SYS_InitTLSData();
39
void *SDL_GetTLS(SDL_TLSID *id)
45
SDL_InvalidParamError("id");
49
storage_index = SDL_AtomicGet(id) - 1;
50
storage = SDL_SYS_GetTLSData();
51
if (!storage || storage_index < 0 || storage_index >= storage->limit) {
54
return storage->array[storage_index].data;
57
SDL_bool SDL_SetTLS(SDL_TLSID *id, const void *value, SDL_TLSDestructorCallback destructor)
63
return SDL_InvalidParamError("id");
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.
72
// Get the storage index associated with the ID in a thread-safe way
73
storage_index = SDL_AtomicGet(id) - 1;
74
if (storage_index < 0) {
75
int new_id = (SDL_AtomicIncRef(&SDL_tls_id) + 1);
77
SDL_AtomicCompareAndSwap(id, 0, new_id);
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.
82
storage_index = SDL_AtomicGet(id) - 1;
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;
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]));
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;
103
if (!SDL_SYS_SetTLSData(storage)) {
107
SDL_AtomicIncRef(&SDL_tls_allocated);
110
storage->array[storage_index].data = SDL_const_cast(void *, value);
111
storage->array[storage_index].destructor = destructor;
115
void SDL_CleanupTLS(void)
117
SDL_TLSData *storage;
119
// Cleanup the storage for the current thread
120
storage = SDL_SYS_GetTLSData();
123
for (i = 0; i < storage->limit; ++i) {
124
if (storage->array[i].destructor) {
125
storage->array[i].destructor(storage->array[i].data);
128
SDL_SYS_SetTLSData(NULL);
130
(void)SDL_AtomicDecRef(&SDL_tls_allocated);
134
void SDL_QuitTLSData(void)
138
if (SDL_AtomicGet(&SDL_tls_allocated) == 0) {
139
SDL_SYS_QuitTLSData();
141
// Some thread hasn't called SDL_CleanupTLS()
145
/* This is a generic implementation of thread-local storage which doesn't
146
require additional OS support.
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.
153
typedef struct SDL_TLSEntry
156
SDL_TLSData *storage;
157
struct SDL_TLSEntry *next;
160
static SDL_Mutex *SDL_generic_TLS_mutex;
161
static SDL_TLSEntry *SDL_generic_TLS;
163
void SDL_Generic_InitTLSData(void)
165
if (!SDL_generic_TLS_mutex) {
166
SDL_generic_TLS_mutex = SDL_CreateMutex();
170
SDL_TLSData *SDL_Generic_GetTLSData(void)
172
SDL_ThreadID thread = SDL_GetCurrentThreadID();
174
SDL_TLSData *storage = NULL;
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;
183
SDL_UnlockMutex(SDL_generic_TLS_mutex);
188
bool SDL_Generic_SetTLSData(SDL_TLSData *data)
190
SDL_ThreadID thread = SDL_GetCurrentThreadID();
191
SDL_TLSEntry *prev, *entry;
194
SDL_LockMutex(SDL_generic_TLS_mutex);
196
for (entry = SDL_generic_TLS; entry; entry = entry->next) {
197
if (entry->thread == thread) {
199
entry->storage = data;
202
prev->next = entry->next;
204
SDL_generic_TLS = entry->next;
212
if (!entry && data) {
213
entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry));
215
entry->thread = thread;
216
entry->storage = data;
217
entry->next = SDL_generic_TLS;
218
SDL_generic_TLS = entry;
223
SDL_UnlockMutex(SDL_generic_TLS_mutex);
228
void SDL_Generic_QuitTLSData(void)
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);
242
SDL_generic_TLS = NULL;
243
SDL_UnlockMutex(SDL_generic_TLS_mutex);
246
if (SDL_generic_TLS_mutex) {
247
SDL_DestroyMutex(SDL_generic_TLS_mutex);
248
SDL_generic_TLS_mutex = NULL;
252
// Non-thread-safe global error variable
253
static SDL_error *SDL_GetStaticErrBuf(void)
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;
262
#ifndef SDL_THREADS_DISABLED
263
static void SDLCALL SDL_FreeErrBuf(void *data)
265
SDL_error *errbuf = (SDL_error *)data;
268
errbuf->free_func(errbuf->str);
270
errbuf->free_func(errbuf);
274
// Routine to get the thread-specific error variable
275
SDL_error *SDL_GetErrBuf(bool create)
277
#ifdef SDL_THREADS_DISABLED
278
return SDL_GetStaticErrBuf();
280
static SDL_TLSID tls_errbuf;
283
errbuf = (SDL_error *)SDL_GetTLS(&tls_errbuf);
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
292
SDL_realloc_func realloc_func;
293
SDL_free_func free_func;
294
SDL_GetOriginalMemoryFunctions(NULL, NULL, &realloc_func, &free_func);
296
errbuf = (SDL_error *)realloc_func(NULL, sizeof(*errbuf));
298
return SDL_GetStaticErrBuf();
301
errbuf->realloc_func = realloc_func;
302
errbuf->free_func = free_func;
303
SDL_SetTLS(&tls_errbuf, errbuf, SDL_FreeErrBuf);
306
#endif // SDL_THREADS_DISABLED
309
void SDL_RunThread(SDL_Thread *thread)
311
void *userdata = thread->userdata;
312
int(SDLCALL * userfunc)(void *) = thread->userfunc;
314
int *statusloc = &thread->status;
316
// Perform any system-dependent setup - this function may not fail
317
SDL_SYS_SetupThread(thread->name);
320
thread->threadid = SDL_GetCurrentThreadID();
323
*statusloc = userfunc(userdata);
325
// Clean up thread-local storage
328
// Mark us as ready to be joined (or detached)
329
if (!SDL_AtomicCompareAndSwap(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) {
330
// Clean up if something already detached us.
331
if (SDL_AtomicCompareAndSwap(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) {
332
SDL_free(thread->name); // Can't free later, we've already cleaned up TLS
338
SDL_Thread *SDL_CreateThreadWithPropertiesRuntime(SDL_PropertiesID props,
339
SDL_FunctionPointer pfnBeginThread,
340
SDL_FunctionPointer pfnEndThread)
342
// rather than check this in every backend, just make sure it's correct upfront. Only allow non-NULL if non-WinRT Windows, or Microsoft GDK.
343
#if (!defined(SDL_PLATFORM_WIN32) && !defined(SDL_PLATFORM_GDK)) || defined(SDL_PLATFORM_WINRT)
344
if (pfnBeginThread || pfnEndThread) {
345
SDL_SetError("_beginthreadex/_endthreadex not supported on this platform");
350
SDL_ThreadFunction fn = (SDL_ThreadFunction) SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, NULL);
351
const char *name = SDL_GetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, NULL);
352
const size_t stacksize = (size_t) SDL_GetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, 0);
353
void *userdata = SDL_GetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, NULL);
356
SDL_SetError("Thread entry function is NULL");
360
SDL_InitMainThread();
362
SDL_Thread *thread = (SDL_Thread *)SDL_calloc(1, sizeof(*thread));
367
SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE);
369
// Set up the arguments for the thread
371
thread->name = SDL_strdup(name);
378
thread->userfunc = fn;
379
thread->userdata = userdata;
380
thread->stacksize = stacksize;
382
// Create the thread and go!
383
if (!SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread)) {
384
// Oops, failed. Gotta free everything
385
SDL_free(thread->name);
390
// Everything is running now
394
SDL_Thread *SDL_CreateThreadRuntime(SDL_ThreadFunction fn,
395
const char *name, void *userdata,
396
SDL_FunctionPointer pfnBeginThread,
397
SDL_FunctionPointer pfnEndThread)
399
const SDL_PropertiesID props = SDL_CreateProperties();
400
SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
401
SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
402
SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata);
403
SDL_Thread *thread = SDL_CreateThreadWithPropertiesRuntime(props, pfnBeginThread, pfnEndThread);
404
SDL_DestroyProperties(props);
408
// internal helper function, not in the public API.
409
SDL_Thread *SDL_CreateThreadWithStackSize(SDL_ThreadFunction fn, const char *name, size_t stacksize, void *userdata)
411
const SDL_PropertiesID props = SDL_CreateProperties();
412
SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_ENTRY_FUNCTION_POINTER, (void *) fn);
413
SDL_SetStringProperty(props, SDL_PROP_THREAD_CREATE_NAME_STRING, name);
414
SDL_SetPointerProperty(props, SDL_PROP_THREAD_CREATE_USERDATA_POINTER, userdata);
415
SDL_SetNumberProperty(props, SDL_PROP_THREAD_CREATE_STACKSIZE_NUMBER, (Sint64) stacksize);
416
SDL_Thread *thread = SDL_CreateThreadWithProperties(props);
417
SDL_DestroyProperties(props);
421
SDL_ThreadID SDL_GetThreadID(SDL_Thread *thread)
426
id = thread->threadid;
428
id = SDL_GetCurrentThreadID();
433
const char *SDL_GetThreadName(SDL_Thread *thread)
436
return SDL_GetPersistentString(thread->name);
442
SDL_bool SDL_SetThreadPriority(SDL_ThreadPriority priority)
444
return SDL_SYS_SetThreadPriority(priority);
447
void SDL_WaitThread(SDL_Thread *thread, int *status)
450
SDL_SYS_WaitThread(thread);
452
*status = thread->status;
454
SDL_free(thread->name);
459
void SDL_DetachThread(SDL_Thread *thread)
465
// Grab dibs if the state is alive+joinable.
466
if (SDL_AtomicCompareAndSwap(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_DETACHED)) {
467
SDL_SYS_DetachThread(thread);
469
// all other states are pretty final, see where we landed.
470
const int thread_state = SDL_AtomicGet(&thread->state);
471
if ((thread_state == SDL_THREAD_STATE_DETACHED) || (thread_state == SDL_THREAD_STATE_CLEANED)) {
472
return; // already detached (you shouldn't call this twice!)
473
} else if (thread_state == SDL_THREAD_STATE_ZOMBIE) {
474
SDL_WaitThread(thread, NULL); // already done, clean it up.
476
SDL_assert(0 && "Unexpected thread state");
481
void SDL_WaitSemaphore(SDL_Semaphore *sem)
483
SDL_WaitSemaphoreTimeoutNS(sem, -1);
486
SDL_bool SDL_TryWaitSemaphore(SDL_Semaphore *sem)
488
return SDL_WaitSemaphoreTimeoutNS(sem, 0);
491
SDL_bool SDL_WaitSemaphoreTimeout(SDL_Semaphore *sem, Sint32 timeoutMS)
495
if (timeoutMS >= 0) {
496
timeoutNS = SDL_MS_TO_NS(timeoutMS);
500
return SDL_WaitSemaphoreTimeoutNS(sem, timeoutNS);
503
void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex)
505
SDL_WaitConditionTimeoutNS(cond, mutex, -1);
508
SDL_bool SDL_WaitConditionTimeout(SDL_Condition *cond, SDL_Mutex *mutex, Sint32 timeoutMS)
512
if (timeoutMS >= 0) {
513
timeoutNS = SDL_MS_TO_NS(timeoutMS);
517
return SDL_WaitConditionTimeoutNS(cond, mutex, timeoutNS);