Make some changes to SDL_SetThreadPriority to try and have SDL transparently handle more of the work.

1. Comment that SDL_SetThreadPriority will make any necessary system changes when applying priority.
2. Add a hint to override SDL's default behavior for scheduler policy.
3. Modify the pthreads SDL_SetThreadPriority so that instead of just using the current thread scheduler policy it will change it to a policy that should work best for the requested priority.
4. Add hint checks in SDL_SetThreadPriority so that #3 can be overridden if desired.
5. Modify the Linux SDL_SetThreadPriority so that in the case that policy, either by SDL defaults or from the hint, is a realtime policy it uses the realtime rtkit API.
6. Prior to calling rtkit on Linux make the necessary thread state changes that rtkit requires.  Currently this is done every time as it isn't expected that SDL_SetThreadPriority will be called repeatedly for a thread.
This commit is contained in:
Sam Lantinga 2020-05-26 13:19:19 -07:00
parent f16e6bfa8e
commit abd5841850
4 changed files with 241 additions and 17 deletions

View File

@ -763,6 +763,19 @@ extern "C" {
*/ */
#define SDL_HINT_THREAD_STACK_SIZE "SDL_THREAD_STACK_SIZE" #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) * \brief If set to 1, then do not allow high-DPI windows. ("Retina" on Mac and iOS)
*/ */

View File

@ -54,6 +54,11 @@ typedef unsigned int SDL_TLSID;
/** /**
* The SDL thread priority. * 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. * \note On many systems you require special privileges to set high or time critical priority.
*/ */
typedef enum { typedef enum {

View File

@ -34,6 +34,8 @@
#include "SDL_dbus.h" #include "SDL_dbus.h"
#if SDL_USE_LIBDBUS #if SDL_USE_LIBDBUS
#include <sched.h>
/* d-bus queries to org.freedesktop.RealtimeKit1. */ /* d-bus queries to org.freedesktop.RealtimeKit1. */
#define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1" #define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1"
#define RTKIT_DBUS_PATH "/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 pthread_once_t rtkit_initialize_once = PTHREAD_ONCE_INIT;
static Sint32 rtkit_min_nice_level = -20; static Sint32 rtkit_min_nice_level = -20;
static Sint32 rtkit_max_realtime_priority = 99;
static void static void
rtkit_initialize() rtkit_initialize()
@ -52,10 +55,76 @@ rtkit_initialize()
DBUS_TYPE_INT32, &rtkit_min_nice_level)) { DBUS_TYPE_INT32, &rtkit_min_nice_level)) {
rtkit_min_nice_level = -20; 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 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; Uint64 ui64 = (Uint64)thread;
Sint32 si32 = (Sint32)nice_level; Sint32 si32 = (Sint32)nice_level;
@ -66,6 +135,14 @@ rtkit_setpriority(pid_t thread, int nice_level)
if (si32 < rtkit_min_nice_level) if (si32 < rtkit_min_nice_level)
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, if (!dbus || !SDL_DBus_CallMethodOnConnection(dbus->system_conn,
RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadHighPriority", RTKIT_DBUS_NODE, RTKIT_DBUS_PATH, RTKIT_DBUS_INTERFACE, "MakeThreadHighPriority",
DBUS_TYPE_UINT64, &ui64, DBUS_TYPE_INT32, &si32, DBUS_TYPE_INVALID, 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; 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 /* dbus */
#endif /* threads */ #endif /* threads */
/* this is a public symbol, so it has to exist even if threads are disabled. */ /* this is a public symbol, so it has to exist even if threads are disabled. */
int int
SDL_LinuxSetThreadPriority(Sint64 threadID, int priority) 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 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; return 0;
} }
#endif #endif
@ -111,6 +216,69 @@ SDL_LinuxSetThreadPriority(Sint64 threadID, int priority)
#endif #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__ */ #endif /* __LINUX__ */
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View File

@ -184,34 +184,71 @@ SDL_ThreadID(void)
return ((SDL_threadID) pthread_self()); 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 int
SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority) SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
{ {
#if __NACL__ || __RISCOS__ #if __NACL__ || __RISCOS__
/* FIXME: Setting thread priority does not seem to be supported in NACL */ /* FIXME: Setting thread priority does not seem to be supported in NACL */
return 0; 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 #else
struct sched_param sched; struct sched_param sched;
int policy; int policy;
int pri_policy;
pthread_t thread = pthread_self(); pthread_t thread = pthread_self();
const char *policyhint = SDL_GetHint(SDL_HINT_THREAD_PRIORITY_POLICY);
if (pthread_getschedparam(thread, &policy, &sched) != 0) { if (pthread_getschedparam(thread, &policy, &sched) != 0) {
return SDL_SetError("pthread_getschedparam() failed"); 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) { if (priority == SDL_THREAD_PRIORITY_LOW) {
sched.sched_priority = sched_get_priority_min(policy); sched.sched_priority = sched_get_priority_min(policy);
} else if (priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) { } else if (priority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
@ -242,6 +279,7 @@ SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
} }
return 0; return 0;
#endif /* linux */ #endif /* linux */
#endif /* #if __NACL__ || __RISCOS__ */
} }
void void