SDL_thread.c (13225B)
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_thread.h" 26 #include "SDL_thread_c.h" 27 #include "SDL_systhread.h" 28 #include "SDL_hints.h" 29 #include "../SDL_error_c.h" 30 31 32 SDL_TLSID 33 SDL_TLSCreate() 34 { 35 static SDL_atomic_t SDL_tls_id; 36 return SDL_AtomicIncRef(&SDL_tls_id)+1; 37 } 38 39 void * 40 SDL_TLSGet(SDL_TLSID id) 41 { 42 SDL_TLSData *storage; 43 44 storage = SDL_SYS_GetTLSData(); 45 if (!storage || id == 0 || id > storage->limit) { 46 return NULL; 47 } 48 return storage->array[id-1].data; 49 } 50 51 int 52 SDL_TLSSet(SDL_TLSID id, const void *value, void (SDLCALL *destructor)(void *)) 53 { 54 SDL_TLSData *storage; 55 56 if (id == 0) { 57 return SDL_InvalidParamError("id"); 58 } 59 60 storage = SDL_SYS_GetTLSData(); 61 if (!storage || (id > storage->limit)) { 62 unsigned int i, oldlimit, newlimit; 63 64 oldlimit = storage ? storage->limit : 0; 65 newlimit = (id + TLS_ALLOC_CHUNKSIZE); 66 storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage)+(newlimit-1)*sizeof(storage->array[0])); 67 if (!storage) { 68 return SDL_OutOfMemory(); 69 } 70 storage->limit = newlimit; 71 for (i = oldlimit; i < newlimit; ++i) { 72 storage->array[i].data = NULL; 73 storage->array[i].destructor = NULL; 74 } 75 if (SDL_SYS_SetTLSData(storage) != 0) { 76 return -1; 77 } 78 } 79 80 storage->array[id-1].data = SDL_const_cast(void*, value); 81 storage->array[id-1].destructor = destructor; 82 return 0; 83 } 84 85 static void 86 SDL_TLSCleanup() 87 { 88 SDL_TLSData *storage; 89 90 storage = SDL_SYS_GetTLSData(); 91 if (storage) { 92 unsigned int i; 93 for (i = 0; i < storage->limit; ++i) { 94 if (storage->array[i].destructor) { 95 storage->array[i].destructor(storage->array[i].data); 96 } 97 } 98 SDL_SYS_SetTLSData(NULL); 99 SDL_free(storage); 100 } 101 } 102 103 104 /* This is a generic implementation of thread-local storage which doesn't 105 require additional OS support. 106 107 It is not especially efficient and doesn't clean up thread-local storage 108 as threads exit. If there is a real OS that doesn't support thread-local 109 storage this implementation should be improved to be production quality. 110 */ 111 112 typedef struct SDL_TLSEntry { 113 SDL_threadID thread; 114 SDL_TLSData *storage; 115 struct SDL_TLSEntry *next; 116 } SDL_TLSEntry; 117 118 static SDL_mutex *SDL_generic_TLS_mutex; 119 static SDL_TLSEntry *SDL_generic_TLS; 120 121 122 SDL_TLSData * 123 SDL_Generic_GetTLSData(void) 124 { 125 SDL_threadID thread = SDL_ThreadID(); 126 SDL_TLSEntry *entry; 127 SDL_TLSData *storage = NULL; 128 129 #if !SDL_THREADS_DISABLED 130 if (!SDL_generic_TLS_mutex) { 131 static SDL_SpinLock tls_lock; 132 SDL_AtomicLock(&tls_lock); 133 if (!SDL_generic_TLS_mutex) { 134 SDL_mutex *mutex = SDL_CreateMutex(); 135 SDL_MemoryBarrierRelease(); 136 SDL_generic_TLS_mutex = mutex; 137 if (!SDL_generic_TLS_mutex) { 138 SDL_AtomicUnlock(&tls_lock); 139 return NULL; 140 } 141 } 142 SDL_AtomicUnlock(&tls_lock); 143 } 144 #endif /* SDL_THREADS_DISABLED */ 145 146 SDL_MemoryBarrierAcquire(); 147 SDL_LockMutex(SDL_generic_TLS_mutex); 148 for (entry = SDL_generic_TLS; entry; entry = entry->next) { 149 if (entry->thread == thread) { 150 storage = entry->storage; 151 break; 152 } 153 } 154 #if !SDL_THREADS_DISABLED 155 SDL_UnlockMutex(SDL_generic_TLS_mutex); 156 #endif 157 158 return storage; 159 } 160 161 int 162 SDL_Generic_SetTLSData(SDL_TLSData *storage) 163 { 164 SDL_threadID thread = SDL_ThreadID(); 165 SDL_TLSEntry *prev, *entry; 166 167 /* SDL_Generic_GetTLSData() is always called first, so we can assume SDL_generic_TLS_mutex */ 168 SDL_LockMutex(SDL_generic_TLS_mutex); 169 prev = NULL; 170 for (entry = SDL_generic_TLS; entry; entry = entry->next) { 171 if (entry->thread == thread) { 172 if (storage) { 173 entry->storage = storage; 174 } else { 175 if (prev) { 176 prev->next = entry->next; 177 } else { 178 SDL_generic_TLS = entry->next; 179 } 180 SDL_free(entry); 181 } 182 break; 183 } 184 prev = entry; 185 } 186 if (!entry) { 187 entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry)); 188 if (entry) { 189 entry->thread = thread; 190 entry->storage = storage; 191 entry->next = SDL_generic_TLS; 192 SDL_generic_TLS = entry; 193 } 194 } 195 SDL_UnlockMutex(SDL_generic_TLS_mutex); 196 197 if (!entry) { 198 return SDL_OutOfMemory(); 199 } 200 return 0; 201 } 202 203 /* Routine to get the thread-specific error variable */ 204 SDL_error * 205 SDL_GetErrBuf(void) 206 { 207 #if SDL_THREADS_DISABLED 208 /* Non-thread-safe global error variable */ 209 static SDL_error SDL_global_error; 210 return &SDL_global_error; 211 #else 212 static SDL_SpinLock tls_lock; 213 static SDL_bool tls_being_created; 214 static SDL_TLSID tls_errbuf; 215 static SDL_error SDL_global_errbuf; 216 const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1; 217 SDL_error *errbuf; 218 219 /* tls_being_created is there simply to prevent recursion if SDL_TLSCreate() fails. 220 It also means it's possible for another thread to also use SDL_global_errbuf, 221 but that's very unlikely and hopefully won't cause issues. 222 */ 223 if (!tls_errbuf && !tls_being_created) { 224 SDL_AtomicLock(&tls_lock); 225 if (!tls_errbuf) { 226 SDL_TLSID slot; 227 tls_being_created = SDL_TRUE; 228 slot = SDL_TLSCreate(); 229 tls_being_created = SDL_FALSE; 230 SDL_MemoryBarrierRelease(); 231 tls_errbuf = slot; 232 } 233 SDL_AtomicUnlock(&tls_lock); 234 } 235 if (!tls_errbuf) { 236 return &SDL_global_errbuf; 237 } 238 239 SDL_MemoryBarrierAcquire(); 240 errbuf = (SDL_error *)SDL_TLSGet(tls_errbuf); 241 if (errbuf == ALLOCATION_IN_PROGRESS) { 242 return &SDL_global_errbuf; 243 } 244 if (!errbuf) { 245 /* Mark that we're in the middle of allocating our buffer */ 246 SDL_TLSSet(tls_errbuf, ALLOCATION_IN_PROGRESS, NULL); 247 errbuf = (SDL_error *)SDL_malloc(sizeof(*errbuf)); 248 if (!errbuf) { 249 SDL_TLSSet(tls_errbuf, NULL, NULL); 250 return &SDL_global_errbuf; 251 } 252 SDL_zerop(errbuf); 253 SDL_TLSSet(tls_errbuf, errbuf, SDL_free); 254 } 255 return errbuf; 256 #endif /* SDL_THREADS_DISABLED */ 257 } 258 259 260 void 261 SDL_RunThread(SDL_Thread *thread) 262 { 263 void *userdata = thread->userdata; 264 int (SDLCALL * userfunc) (void *) = thread->userfunc; 265 266 int *statusloc = &thread->status; 267 268 /* Perform any system-dependent setup - this function may not fail */ 269 SDL_SYS_SetupThread(thread->name); 270 271 /* Get the thread id */ 272 thread->threadid = SDL_ThreadID(); 273 274 /* Run the function */ 275 *statusloc = userfunc(userdata); 276 277 /* Clean up thread-local storage */ 278 SDL_TLSCleanup(); 279 280 /* Mark us as ready to be joined (or detached) */ 281 if (!SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_ZOMBIE)) { 282 /* Clean up if something already detached us. */ 283 if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_DETACHED, SDL_THREAD_STATE_CLEANED)) { 284 if (thread->name) { 285 SDL_free(thread->name); 286 } 287 SDL_free(thread); 288 } 289 } 290 } 291 292 #ifdef SDL_CreateThread 293 #undef SDL_CreateThread 294 #undef SDL_CreateThreadWithStackSize 295 #endif 296 #if SDL_DYNAMIC_API 297 #define SDL_CreateThread SDL_CreateThread_REAL 298 #define SDL_CreateThreadWithStackSize SDL_CreateThreadWithStackSize_REAL 299 #endif 300 301 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD 302 SDL_Thread * 303 SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *), 304 const char *name, const size_t stacksize, void *data, 305 pfnSDL_CurrentBeginThread pfnBeginThread, 306 pfnSDL_CurrentEndThread pfnEndThread) 307 #else 308 SDL_Thread * 309 SDL_CreateThreadWithStackSize(int (SDLCALL * fn) (void *), 310 const char *name, const size_t stacksize, void *data) 311 #endif 312 { 313 SDL_Thread *thread; 314 int ret; 315 316 /* Allocate memory for the thread info structure */ 317 thread = (SDL_Thread *) SDL_calloc(1, sizeof(*thread)); 318 if (thread == NULL) { 319 SDL_OutOfMemory(); 320 return NULL; 321 } 322 thread->status = -1; 323 SDL_AtomicSet(&thread->state, SDL_THREAD_STATE_ALIVE); 324 325 /* Set up the arguments for the thread */ 326 if (name != NULL) { 327 thread->name = SDL_strdup(name); 328 if (thread->name == NULL) { 329 SDL_OutOfMemory(); 330 SDL_free(thread); 331 return NULL; 332 } 333 } 334 335 thread->userfunc = fn; 336 thread->userdata = data; 337 thread->stacksize = stacksize; 338 339 /* Create the thread and go! */ 340 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD 341 ret = SDL_SYS_CreateThread(thread, pfnBeginThread, pfnEndThread); 342 #else 343 ret = SDL_SYS_CreateThread(thread); 344 #endif 345 if (ret < 0) { 346 /* Oops, failed. Gotta free everything */ 347 SDL_free(thread->name); 348 SDL_free(thread); 349 thread = NULL; 350 } 351 352 /* Everything is running now */ 353 return thread; 354 } 355 356 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD 357 DECLSPEC SDL_Thread *SDLCALL 358 SDL_CreateThread(int (SDLCALL * fn) (void *), 359 const char *name, void *data, 360 pfnSDL_CurrentBeginThread pfnBeginThread, 361 pfnSDL_CurrentEndThread pfnEndThread) 362 #else 363 DECLSPEC SDL_Thread *SDLCALL 364 SDL_CreateThread(int (SDLCALL * fn) (void *), 365 const char *name, void *data) 366 #endif 367 { 368 /* !!! FIXME: in 2.1, just make stackhint part of the usual API. */ 369 const char *stackhint = SDL_GetHint(SDL_HINT_THREAD_STACK_SIZE); 370 size_t stacksize = 0; 371 372 /* If the SDL_HINT_THREAD_STACK_SIZE exists, use it */ 373 if (stackhint != NULL) { 374 char *endp = NULL; 375 const Sint64 hintval = SDL_strtoll(stackhint, &endp, 10); 376 if ((*stackhint != '\0') && (*endp == '\0')) { /* a valid number? */ 377 if (hintval > 0) { /* reject bogus values. */ 378 stacksize = (size_t) hintval; 379 } 380 } 381 } 382 383 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD 384 return SDL_CreateThreadWithStackSize(fn, name, stacksize, data, pfnBeginThread, pfnEndThread); 385 #else 386 return SDL_CreateThreadWithStackSize(fn, name, stacksize, data); 387 #endif 388 } 389 390 SDL_Thread * 391 SDL_CreateThreadInternal(int (SDLCALL * fn) (void *), const char *name, 392 const size_t stacksize, void *data) { 393 #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD 394 return SDL_CreateThreadWithStackSize(fn, name, stacksize, data, NULL, NULL); 395 #else 396 return SDL_CreateThreadWithStackSize(fn, name, stacksize, data); 397 #endif 398 } 399 400 SDL_threadID 401 SDL_GetThreadID(SDL_Thread * thread) 402 { 403 SDL_threadID id; 404 405 if (thread) { 406 id = thread->threadid; 407 } else { 408 id = SDL_ThreadID(); 409 } 410 return id; 411 } 412 413 const char * 414 SDL_GetThreadName(SDL_Thread * thread) 415 { 416 if (thread) { 417 return thread->name; 418 } else { 419 return NULL; 420 } 421 } 422 423 int 424 SDL_SetThreadPriority(SDL_ThreadPriority priority) 425 { 426 return SDL_SYS_SetThreadPriority(priority); 427 } 428 429 void 430 SDL_WaitThread(SDL_Thread * thread, int *status) 431 { 432 if (thread) { 433 SDL_SYS_WaitThread(thread); 434 if (status) { 435 *status = thread->status; 436 } 437 if (thread->name) { 438 SDL_free(thread->name); 439 } 440 SDL_free(thread); 441 } 442 } 443 444 void 445 SDL_DetachThread(SDL_Thread * thread) 446 { 447 if (!thread) { 448 return; 449 } 450 451 /* Grab dibs if the state is alive+joinable. */ 452 if (SDL_AtomicCAS(&thread->state, SDL_THREAD_STATE_ALIVE, SDL_THREAD_STATE_DETACHED)) { 453 SDL_SYS_DetachThread(thread); 454 } else { 455 /* all other states are pretty final, see where we landed. */ 456 const int thread_state = SDL_AtomicGet(&thread->state); 457 if ((thread_state == SDL_THREAD_STATE_DETACHED) || (thread_state == SDL_THREAD_STATE_CLEANED)) { 458 return; /* already detached (you shouldn't call this twice!) */ 459 } else if (thread_state == SDL_THREAD_STATE_ZOMBIE) { 460 SDL_WaitThread(thread, NULL); /* already done, clean it up. */ 461 } else { 462 SDL_assert(0 && "Unexpected thread state"); 463 } 464 } 465 } 466 467 /* vi: set ts=4 sw=4 expandtab: */