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#include <pthread.h>
24
25#ifdef HAVE_PTHREAD_NP_H
26#include <pthread_np.h>
27#endif
28
29#ifdef HAVE_SIGNAL_H
30#include <signal.h>
31#endif
32#include <errno.h>
33
34#ifdef SDL_PLATFORM_LINUX
35#include <sys/time.h>
36#include <sys/resource.h>
37#include <sys/syscall.h>
38#include <unistd.h>
39
40#include "../../core/linux/SDL_dbus.h"
41#endif // SDL_PLATFORM_LINUX
42
43#if (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID) || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)) && defined(HAVE_DLOPEN)
44#include <dlfcn.h>
45#ifndef RTLD_DEFAULT
46#define RTLD_DEFAULT NULL
47#endif
48#endif
49
50#include "../SDL_thread_c.h"
51#include "../SDL_systhread.h"
52#ifdef SDL_PLATFORM_ANDROID
53#include "../../core/android/SDL_android.h"
54#endif
55
56#ifdef SDL_PLATFORM_HAIKU
57#include <kernel/OS.h>
58#endif
59
60#ifdef HAVE_SIGNAL_H
61// List of signals to mask in the subthreads
62static const int sig_list[] = {
63 SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGCHLD, SIGWINCH,
64 SIGVTALRM, SIGPROF, 0
65};
66#endif
67
68static void *RunThread(void *data)
69{
70#ifdef SDL_PLATFORM_ANDROID
71 Android_JNI_SetupThread();
72#endif
73 SDL_RunThread((SDL_Thread *)data);
74 return NULL;
75}
76
77#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)) && defined(HAVE_DLOPEN)
78static bool checked_setname = false;
79static int (*ppthread_setname_np)(const char *) = NULL;
80#elif (defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN)
81static bool checked_setname = false;
82static int (*ppthread_setname_np)(pthread_t, const char *) = NULL;
83#endif
84bool SDL_SYS_CreateThread(SDL_Thread *thread,
85 SDL_FunctionPointer pfnBeginThread,
86 SDL_FunctionPointer pfnEndThread)
87{
88 pthread_attr_t type;
89
90// do this here before any threads exist, so there's no race condition.
91#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN)
92 if (!checked_setname) {
93 void *fn = dlsym(RTLD_DEFAULT, "pthread_setname_np");
94#if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)
95 ppthread_setname_np = (int (*)(const char *))fn;
96#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)
97 ppthread_setname_np = (int (*)(pthread_t, const char *))fn;
98#endif
99 checked_setname = true;
100 }
101 #endif
102
103 // Set the thread attributes
104 if (pthread_attr_init(&type) != 0) {
105 return SDL_SetError("Couldn't initialize pthread attributes");
106 }
107 pthread_attr_setdetachstate(&type, PTHREAD_CREATE_JOINABLE);
108
109 // Set caller-requested stack size. Otherwise: use the system default.
110 if (thread->stacksize) {
111 pthread_attr_setstacksize(&type, thread->stacksize);
112 }
113
114 // Create the thread and go!
115 if (pthread_create(&thread->handle, &type, RunThread, thread) != 0) {
116 return SDL_SetError("Not enough resources to create thread");
117 }
118
119 return true;
120}
121
122void SDL_SYS_SetupThread(const char *name)
123{
124#ifdef HAVE_SIGNAL_H
125 int i;
126 sigset_t mask;
127#endif
128
129 if (name) {
130#if (defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)) && defined(HAVE_DLOPEN)
131 SDL_assert(checked_setname);
132 if (ppthread_setname_np) {
133#if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS)
134 ppthread_setname_np(name);
135#elif defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_ANDROID)
136 if (ppthread_setname_np(pthread_self(), name) == ERANGE) {
137 char namebuf[16]; // Limited to 16 char
138 SDL_strlcpy(namebuf, name, sizeof(namebuf));
139 ppthread_setname_np(pthread_self(), namebuf);
140 }
141#endif
142 }
143#elif defined(HAVE_PTHREAD_SETNAME_NP)
144#ifdef SDL_PLATFORM_NETBSD
145 pthread_setname_np(pthread_self(), "%s", name);
146#else
147 if (pthread_setname_np(pthread_self(), name) == ERANGE) {
148 char namebuf[16]; // Limited to 16 char
149 SDL_strlcpy(namebuf, name, sizeof(namebuf));
150 pthread_setname_np(pthread_self(), namebuf);
151 }
152#endif
153#elif defined(HAVE_PTHREAD_SET_NAME_NP)
154 pthread_set_name_np(pthread_self(), name);
155#elif defined(SDL_PLATFORM_HAIKU)
156 // The docs say the thread name can't be longer than B_OS_NAME_LENGTH.
157 char namebuf[B_OS_NAME_LENGTH];
158 SDL_strlcpy(namebuf, name, sizeof(namebuf));
159 rename_thread(find_thread(NULL), namebuf);
160#endif
161 }
162
163#ifdef HAVE_SIGNAL_H
164 // Mask asynchronous signals for this thread
165 sigemptyset(&mask);
166 for (i = 0; sig_list[i]; ++i) {
167 sigaddset(&mask, sig_list[i]);
168 }
169 pthread_sigmask(SIG_BLOCK, &mask, 0);
170#endif
171
172#ifdef PTHREAD_CANCEL_ASYNCHRONOUS
173 // Allow ourselves to be asynchronously cancelled
174 {
175 int oldstate;
176 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldstate);
177 }
178#endif
179}
180
181SDL_ThreadID SDL_GetCurrentThreadID(void)
182{
183 return (SDL_ThreadID)pthread_self();
184}
185
186bool SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
187{
188#ifdef SDL_PLATFORM_RISCOS
189 // FIXME: Setting thread priority does not seem to be supported
190 return true;
191#else
192 struct sched_param sched;
193 int policy;
194 int pri_policy;
195 pthread_t thread = pthread_self();
196 const char *policyhint = SDL_GetHint(SDL_HINT_THREAD_PRIORITY_POLICY);
197 const bool timecritical_realtime_hint = SDL_GetHintBoolean(SDL_HINT_THREAD_FORCE_REALTIME_TIME_CRITICAL, false);
198
199 if (pthread_getschedparam(thread, &policy, &sched) != 0) {
200 return SDL_SetError("pthread_getschedparam() failed");
201 }
202
203 /* Higher priority levels may require changing the pthread scheduler policy
204 * for the thread. SDL will make such changes by default but there is
205 * also a hint allowing that behavior to be overridden. */
206 switch (priority) {
207 case SDL_THREAD_PRIORITY_LOW:
208 case SDL_THREAD_PRIORITY_NORMAL:
209 pri_policy = SCHED_OTHER;
210 break;
211 case SDL_THREAD_PRIORITY_HIGH:
212 case SDL_THREAD_PRIORITY_TIME_CRITICAL:
213#if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
214 // Apple requires SCHED_RR for high priority threads
215 pri_policy = SCHED_RR;
216 break;
217#else
218 pri_policy = SCHED_OTHER;
219 break;
220#endif
221 default:
222 pri_policy = policy;
223 break;
224 }
225
226 if (timecritical_realtime_hint && priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
227 pri_policy = SCHED_RR;
228 }
229
230 if (policyhint) {
231 if (SDL_strcmp(policyhint, "current") == 0) {
232 // Leave current thread scheduler policy unchanged
233 } else if (SDL_strcmp(policyhint, "other") == 0) {
234 policy = SCHED_OTHER;
235 } else if (SDL_strcmp(policyhint, "rr") == 0) {
236 policy = SCHED_RR;
237 } else if (SDL_strcmp(policyhint, "fifo") == 0) {
238 policy = SCHED_FIFO;
239 } else {
240 policy = pri_policy;
241 }
242 } else {
243 policy = pri_policy;
244 }
245
246#ifdef SDL_PLATFORM_LINUX
247 {
248 pid_t linuxTid = syscall(SYS_gettid);
249 return SDL_SetLinuxThreadPriorityAndPolicy(linuxTid, priority, policy);
250 }
251#else
252 if (priority == SDL_THREAD_PRIORITY_LOW) {
253 sched.sched_priority = sched_get_priority_min(policy);
254 } else if (priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
255 sched.sched_priority = sched_get_priority_max(policy);
256 } else {
257 int min_priority = sched_get_priority_min(policy);
258 int max_priority = sched_get_priority_max(policy);
259
260#if defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
261 if (min_priority == 15 && max_priority == 47) {
262 // Apple has a specific set of thread priorities
263 if (priority == SDL_THREAD_PRIORITY_HIGH) {
264 sched.sched_priority = 45;
265 } else {
266 sched.sched_priority = 37;
267 }
268 } else
269#endif // SDL_PLATFORM_MACOS || SDL_PLATFORM_IOS || SDL_PLATFORM_TVOS
270 {
271 sched.sched_priority = (min_priority + (max_priority - min_priority) / 2);
272 if (priority == SDL_THREAD_PRIORITY_HIGH) {
273 sched.sched_priority += ((max_priority - min_priority) / 4);
274 }
275 }
276 }
277 if (pthread_setschedparam(thread, policy, &sched) != 0) {
278 return SDL_SetError("pthread_setschedparam() failed");
279 }
280 return true;
281#endif // linux
282#endif // #if SDL_PLATFORM_RISCOS
283}
284
285void SDL_SYS_WaitThread(SDL_Thread *thread)
286{
287 pthread_join(thread->handle, 0);
288}
289
290void SDL_SYS_DetachThread(SDL_Thread *thread)
291{
292 pthread_detach(thread->handle);
293}
294