diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 44e6fd457..f0b7e52d8 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -763,6 +763,19 @@ extern "C" { */ #define SDL_HINT_THREAD_STACK_SIZE "SDL_THREAD_STACK_SIZE" +/** +* \brief A string specifying additional information to use with SDL_SetThreadPriority. +* +* By default SDL_SetThreadPriority will make appropriate system changes in order to +* apply a thread priority. For example on systems using pthreads the scheduler policy +* is changed automatically to a policy that works well with a given priority. +* Code which has specific requirements can override SDL's default behavior with this hint. +* +* pthread hint values are "current", "other", "fifo" and "rr". +* Currently no other platform hint values are defined but may be in the future. +*/ +#define SDL_HINT_THREAD_PRIORITY_POLICY "SDL_THREAD_PRIORITY_POLICY" + /** * \brief If set to 1, then do not allow high-DPI windows. ("Retina" on Mac and iOS) */ diff --git a/include/SDL_thread.h b/include/SDL_thread.h index cb5301174..4016358a0 100644 --- a/include/SDL_thread.h +++ b/include/SDL_thread.h @@ -54,6 +54,11 @@ typedef unsigned int SDL_TLSID; /** * The SDL thread priority. * + * SDL will make system changes as necessary in order to apply the thread priority. + * Code which attempts to control thread state related to priority should be aware + * that calling SDL_SetThreadPriority may alter such state. + * SDL_HINT_THREAD_PRIORITY_POLICY can be used to control aspects of this behavior. + * * \note On many systems you require special privileges to set high or time critical priority. */ typedef enum { diff --git a/src/core/linux/SDL_threadprio.c b/src/core/linux/SDL_threadprio.c index 2cbc4cb48..7141888e7 100644 --- a/src/core/linux/SDL_threadprio.c +++ b/src/core/linux/SDL_threadprio.c @@ -34,6 +34,8 @@ #include "SDL_dbus.h" #if SDL_USE_LIBDBUS +#include + /* d-bus queries to org.freedesktop.RealtimeKit1. */ #define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1" #define RTKIT_DBUS_PATH "/org/freedesktop/RealtimeKit1" @@ -41,6 +43,7 @@ static pthread_once_t rtkit_initialize_once = PTHREAD_ONCE_INIT; static Sint32 rtkit_min_nice_level = -20; +static Sint32 rtkit_max_realtime_priority = 99; static void rtkit_initialize() @@ -52,10 +55,76 @@ rtkit_initialize() DBUS_TYPE_INT32, &rtkit_min_nice_level)) { rtkit_min_nice_level = -20; } + + /* Try getting maximum realtime priority: this can be less than the POSIX default (99). */ + if (!dbus || !SDL_DBus_QueryPropertyOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MaxRealtimePriority", + DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) { + rtkit_max_realtime_priority = 99; + } } static SDL_bool -rtkit_setpriority(pid_t thread, int nice_level) +rtkit_initialize_thread() +{ + // Following is an excerpt from rtkit README that outlines the requirements + // a thread must meet before making rtkit requests: + // + // * Only clients with RLIMIT_RTTIME set will get RT scheduling + // + // * RT scheduling will only be handed out to processes with + // SCHED_RESET_ON_FORK set to guarantee that the scheduling + // settings cannot 'leak' to child processes, thus making sure + // that 'RT fork bombs' cannot be used to bypass RLIMIT_RTTIME + // and take the system down. + // + // * Limits are enforced on all user controllable resources, only + // a maximum number of users, processes, threads can request RT + // scheduling at the same time. + // + // * Only a limited number of threads may be made RT in a + // specific time frame. + // + // * Client authorization is verified with PolicyKit + + int err; + struct rlimit rlimit; + int nLimit = RLIMIT_RTTIME; + pid_t nPid = 0; //self + int nSchedPolicy = sched_getscheduler(nPid) | SCHED_RESET_ON_FORK; + struct sched_param schedParam = {}; + + // Requirement #1: Set RLIMIT_RTTIME + err = getrlimit(nLimit, &rlimit); + if (err) + { + return SDL_FALSE; + } + + rlimit.rlim_cur = rlimit.rlim_max; + err = setrlimit(nLimit, &rlimit); + if (err) + { + return SDL_FALSE; + } + + // Requirement #2: Add SCHED_RESET_ON_FORK to the scheduler policy + err = sched_getparam(nPid, &schedParam); + if (err) + { + return SDL_FALSE; + } + + err = sched_setscheduler(nPid, nSchedPolicy, &schedParam); + if (err) + { + return SDL_FALSE; + } + + return SDL_TRUE; +} + +static SDL_bool +rtkit_setpriority_nice(pid_t thread, int nice_level) { Uint64 ui64 = (Uint64)thread; Sint32 si32 = (Sint32)nice_level; @@ -66,6 +135,14 @@ rtkit_setpriority(pid_t thread, int nice_level) if (si32 < rtkit_min_nice_level) si32 = rtkit_min_nice_level; + // We always perform the thread state changes necessary for rtkit. + // This wastes some system calls if the state is already set but + // typically code sets a thread priority and leaves it so it's + // not expected that this wasted effort will be an issue. + // We also do not quit if this fails, we let the rtkit request + // go through to determine whether it really needs to fail or not. + rtkit_initialize_thread(); + if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadHighPriority", DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_INT32, &si32, DBUS_TYPE_INVALID, @@ -74,10 +151,38 @@ rtkit_setpriority(pid_t thread, int nice_level) } return SDL_TRUE; } + +static SDL_bool +rtkit_setpriority_realtime(pid_t thread, int rt_priority) +{ + Uint64 ui64 = (Uint64)thread; + Sint32 si32 = (Sint32)rt_priority; + SDL_DBusContext *dbus = SDL_DBus_GetContext(); + + pthread_once(&rtkit_initialize_once, rtkit_initialize); + + if (si32 > rtkit_max_realtime_priority) + si32 = rtkit_max_realtime_priority; + + // We always perform the thread state changes necessary for rtkit. + // This wastes some system calls if the state is already set but + // typically code sets a thread priority and leaves it so it's + // not expected that this wasted effort will be an issue. + // We also do not quit if this fails, we let the rtkit request + // go through to determine whether it really needs to fail or not. + rtkit_initialize_thread(); + + if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn, + RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadRealtime", + DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_INT32, &si32, DBUS_TYPE_INVALID, + DBUS_TYPE_INVALID)) { + return SDL_FALSE; + } + return SDL_TRUE; +} #endif /* dbus */ #endif /* threads */ - /* this is a public symbol, so it has to exist even if threads are disabled. */ int SDL_LinuxSetThreadPriority(Sint64 threadID, int priority) @@ -102,7 +207,7 @@ SDL_LinuxSetThreadPriority(Sint64 threadID, int priority) README and sample code at: http://git.0pointer.net/rtkit.git */ - if (rtkit_setpriority((pid_t)threadID, priority)) { + if (rtkit_setpriority_nice((pid_t)threadID, priority)) { return 0; } #endif @@ -111,6 +216,69 @@ SDL_LinuxSetThreadPriority(Sint64 threadID, int priority) #endif } +/* this is a public symbol, so it has to exist even if threads are disabled. */ +int +SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy) +{ +#if SDL_THREADS_DISABLED + return SDL_Unsupported(); +#else + if (schedPolicy != SCHED_RR && schedPolicy != SCHED_FIFO && setpriority(PRIO_PROCESS, (id_t)threadID, priority) == 0) { + return 0; + } + +#if SDL_USE_LIBDBUS + /* Note that this fails you most likely: + * Have your process's scheduler incorrectly configured. + See the requirements at: + http://git.0pointer.net/rtkit.git/tree/README#n16 + * Encountered dbus/polkit security restrictions. Note + that the RealtimeKit1 dbus endpoint is inaccessible + over ssh connections for most common distro configs. + You might want to check your local config for details: + /usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy + + README and sample code at: http://git.0pointer.net/rtkit.git + */ + if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) { + int rtPriority; + + if (sdlPriority == SDL_THREAD_PRIORITY_LOW) { + rtPriority = 1; + } else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) { + rtPriority = rtkit_max_realtime_priority * 3 / 4; + } else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) { + rtPriority = rtkit_max_realtime_priority; + } else { + rtPriority = rtkit_max_realtime_priority / 2; + } + + if (rtkit_setpriority_realtime((pid_t)threadID, rtPriority)) { + return 0; + } + } else { + int niceLevel; + + if (sdlPriority == SDL_THREAD_PRIORITY_LOW) { + niceLevel = 19; + } else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) { + niceLevel = -10; + } else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) { + niceLevel = -20; + } else { + niceLevel = 0; + } + + if (rtkit_setpriority_nice((pid_t)threadID, niceLevel)) { + return 0; + } + } +#endif + + return SDL_SetError("setpriority() failed"); +#endif +} + #endif /* __LINUX__ */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/pthread/SDL_systhread.c b/src/thread/pthread/SDL_systhread.c index fb96a401a..2dc6024cd 100644 --- a/src/thread/pthread/SDL_systhread.c +++ b/src/thread/pthread/SDL_systhread.c @@ -184,34 +184,71 @@ SDL_ThreadID(void) return ((SDL_threadID) pthread_self()); } +#if __LINUX__ +/** + \brief Sets the SDL priority (not nice level) for a thread, using setpriority() if appropriate, and RealtimeKit if available. + Differs from SDL_LinuxSetThreadPriority in also taking the desired scheduler policy, + such as SCHED_OTHER or SCHED_RR. + + \return 0 on success, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_LinuxSetThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy); +#endif + int SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority) { #if __NACL__ || __RISCOS__ /* FIXME: Setting thread priority does not seem to be supported in NACL */ return 0; -#elif __LINUX__ - int value; - pid_t thread = syscall(SYS_gettid); - - if (priority == SDL_THREAD_PRIORITY_LOW) { - value = 19; - } else if (priority == SDL_THREAD_PRIORITY_HIGH) { - value = -10; - } else if (priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) { - value = -20; - } else { - value = 0; - } - return SDL_LinuxSetThreadPriority(thread, value); #else struct sched_param sched; int policy; + int pri_policy; pthread_t thread = pthread_self(); + const char *policyhint = SDL_GetHint(SDL_HINT_THREAD_PRIORITY_POLICY); if (pthread_getschedparam(thread, &policy, &sched) != 0) { return SDL_SetError("pthread_getschedparam() failed"); } + + /* Higher priority levels may require changing the pthread scheduler policy + * for the thread. SDL will make such changes by default but there is + * also a hint allowing that behavior to be overridden. */ + switch (priority) { + case SDL_THREAD_PRIORITY_LOW: + case SDL_THREAD_PRIORITY_NORMAL: + pri_policy = SCHED_OTHER; + break; + case SDL_THREAD_PRIORITY_HIGH: + case SDL_THREAD_PRIORITY_TIME_CRITICAL: + pri_policy = SCHED_RR; + break; + default: + pri_policy = policy; + break; + } + + if (policyhint) { + if (SDL_strcmp(policyhint, "current") == 0) { + /* Leave current thread scheduler policy unchanged */ + } else if (SDL_strcmp(policyhint, "other") == 0) { + policy = SCHED_OTHER; + } else if (SDL_strcmp(policyhint, "rr") == 0) { + policy = SCHED_RR; + } else if (SDL_strcmp(policyhint, "fifo") == 0) { + policy = SCHED_FIFO; + } else { + policy = pri_policy; + } + } else { + policy = pri_policy; + } + +#if __LINUX__ + pid_t thread = syscall(SYS_gettid); + return SDL_LinuxSetThreadPriorityAndPolicy(thread, priority, policy); +#else if (priority == SDL_THREAD_PRIORITY_LOW) { sched.sched_priority = sched_get_priority_min(policy); } else if (priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) { @@ -242,6 +279,7 @@ SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority) } return 0; #endif /* linux */ +#endif /* #if __NACL__ || __RISCOS__ */ } void