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 | |
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 | |