Skip to content

Commit

Permalink
Make some changes to SDL_SetThreadPriority to try and have SDL transp…
Browse files Browse the repository at this point in the history
…arently 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.
  • Loading branch information
slouken committed May 26, 2020
1 parent f16e6bf commit abd5841
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 17 deletions.
13 changes: 13 additions & 0 deletions include/SDL_hints.h
Expand Up @@ -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)
*/
Expand Down
5 changes: 5 additions & 0 deletions include/SDL_thread.h
Expand Up @@ -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 {
Expand Down
174 changes: 171 additions & 3 deletions src/core/linux/SDL_threadprio.c
Expand Up @@ -34,13 +34,16 @@
#include "SDL_dbus.h"

#if SDL_USE_LIBDBUS
#include <sched.h>

/* d-bus queries to org.freedesktop.RealtimeKit1. */
#define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1"
#define RTKIT_DBUS_PATH "/org/freedesktop/RealtimeKit1"
#define RTKIT_DBUS_INTERFACE "org.freedesktop.RealtimeKit1"

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()
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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: */
66 changes: 52 additions & 14 deletions src/thread/pthread/SDL_systhread.c
Expand Up @@ -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) {
Expand Down Expand Up @@ -242,6 +279,7 @@ SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
}
return 0;
#endif /* linux */
#endif /* #if __NACL__ || __RISCOS__ */
}

void
Expand Down

0 comments on commit abd5841

Please sign in to comment.