Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/thread/SDL_thread.c
9903 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
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
31
static SDL_AtomicInt SDL_tls_allocated;
32
static SDL_AtomicInt SDL_tls_id;
33
34
void SDL_InitTLSData(void)
35
{
36
SDL_SYS_InitTLSData();
37
}
38
39
void *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
57
bool 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
115
void 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
134
void 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
153
typedef struct SDL_TLSEntry
154
{
155
SDL_ThreadID thread;
156
SDL_TLSData *storage;
157
struct SDL_TLSEntry *next;
158
} SDL_TLSEntry;
159
160
static SDL_Mutex *SDL_generic_TLS_mutex;
161
static SDL_TLSEntry *SDL_generic_TLS;
162
163
void SDL_Generic_InitTLSData(void)
164
{
165
if (!SDL_generic_TLS_mutex) {
166
SDL_generic_TLS_mutex = SDL_CreateMutex();
167
}
168
}
169
170
SDL_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
188
bool 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
228
void 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
253
static 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
263
static 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
275
SDL_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
309
static bool ThreadValid(SDL_Thread *thread)
310
{
311
return SDL_ObjectValid(thread, SDL_OBJECT_TYPE_THREAD);
312
}
313
314
void 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
343
SDL_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
402
SDL_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.
417
SDL_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
429
SDL_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
443
const 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
452
bool SDL_SetCurrentThreadPriority(SDL_ThreadPriority priority)
453
{
454
return SDL_SYS_SetThreadPriority(priority);
455
}
456
457
void 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
475
SDL_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
484
void 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
507
void SDL_WaitSemaphore(SDL_Semaphore *sem)
508
{
509
SDL_WaitSemaphoreTimeoutNS(sem, -1);
510
}
511
512
bool SDL_TryWaitSemaphore(SDL_Semaphore *sem)
513
{
514
return SDL_WaitSemaphoreTimeoutNS(sem, 0);
515
}
516
517
bool 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
529
void SDL_WaitCondition(SDL_Condition *cond, SDL_Mutex *mutex)
530
{
531
SDL_WaitConditionTimeoutNS(cond, mutex, -1);
532
}
533
534
bool 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
546
bool 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
560
bool 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
574
void 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
586