1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 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 | 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 | SDL_MemoryBarrierAcquire(); |
145 | SDL_LockMutex(SDL_generic_TLS_mutex); |
146 | #endif /* SDL_THREADS_DISABLED */ |
147 | |
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: */ |
468 | |