core: Expose API to elevate a thread to realtime priority

This should make it easier for clients to elevate their audio threads to
real time priority without having to dig through much through specific
system internals.
This commit is contained in:
Arun Raghavan 2018-04-21 09:45:26 +05:30
parent 651a3d108e
commit 878ef44079
17 changed files with 205 additions and 184 deletions

View file

@ -225,6 +225,7 @@ pa_mainloop_run;
pa_mainloop_set_poll_func;
pa_mainloop_wakeup;
pa_msleep;
pa_thread_make_realtime
pa_operation_cancel;
pa_operation_get_state;
pa_operation_ref;

View file

@ -33,6 +33,7 @@
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/util.h>
#include <pulse/volume.h>
#include <pulse/xmalloc.h>
#include <pulse/internal.h>
@ -1780,7 +1781,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);

View file

@ -29,6 +29,7 @@
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/util.h>
#include <pulse/volume.h>
#include <pulse/xmalloc.h>
@ -1504,7 +1505,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);

View file

@ -30,6 +30,7 @@
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/utf8.h>
#include <pulse/util.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core-rtclock.h>
@ -1500,7 +1501,7 @@ static void thread_func(void *userdata) {
pa_log_debug("IO Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);

View file

@ -29,6 +29,7 @@
#include <jack/jack.h>
#include <pulse/util.h>
#include <pulse/xmalloc.h>
#include <pulsecore/sink.h>
@ -224,7 +225,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);
@ -267,7 +268,7 @@ static void jack_init(void *arg) {
pa_log_info("JACK thread starting up.");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority+4);
pa_thread_make_realtime(u->core->realtime_priority+4);
}
/* JACK Callback: This is called when JACK kicks us */

View file

@ -29,6 +29,7 @@
#include <jack/jack.h>
#include <pulse/util.h>
#include <pulse/xmalloc.h>
#include <pulsecore/source.h>
@ -187,7 +188,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);
@ -225,7 +226,7 @@ static void jack_init(void *arg) {
pa_log_info("JACK thread starting up.");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority+4);
pa_thread_make_realtime(u->core->realtime_priority+4);
}
static void jack_shutdown(void* arg) {

View file

@ -722,7 +722,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->module->core->realtime_scheduling)
pa_make_realtime(u->module->core->realtime_priority);
pa_thread_make_realtime(u->module->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);

View file

@ -26,6 +26,7 @@
#include <pulse/rtclock.h>
#include <pulse/timeval.h>
#include <pulse/util.h>
#include <pulse/xmalloc.h>
#include <pulsecore/macro.h>
@ -319,7 +320,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority+1);
pa_thread_make_realtime(u->core->realtime_priority+1);
pa_thread_mq_install(&u->thread_mq);

View file

@ -650,7 +650,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);

View file

@ -252,7 +252,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);

View file

@ -899,7 +899,7 @@ static void thread_func(void *userdata) {
pa_log_debug("Thread starting up");
if (u->core->realtime_scheduling)
pa_make_realtime(u->core->realtime_priority);
pa_thread_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);

View file

@ -56,6 +56,7 @@
#include <pulse/timeval.h>
#include <pulsecore/socket.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core-util.h>
#include <pulsecore/macro.h>
#include <pulsecore/usergroup.h>
@ -71,6 +72,29 @@
static int _main() PA_GCC_WEAKREF(main);
#endif
#ifdef HAVE_PTHREAD
#include <pthread.h>
#endif
#ifdef HAVE_SCHED_H
#include <sched.h>
#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK)
#define SCHED_RESET_ON_FORK 0x40000000
#endif
#endif
#ifdef __APPLE__
#include <mach/mach_init.h>
#include <mach/thread_act.h>
#include <mach/thread_policy.h>
#include <sys/sysctl.h>
#endif
#ifdef HAVE_DBUS
#include <pulsecore/rtkit.h>
#endif
char *pa_get_user_name(char *s, size_t l) {
const char *p;
char *name = NULL;
@ -342,3 +366,155 @@ int pa_msleep(unsigned long t) {
#error "Platform lacks a sleep function."
#endif
}
#ifdef _POSIX_PRIORITY_SCHEDULING
static int set_scheduler(int rtprio) {
#ifdef HAVE_SCHED_H
struct sched_param sp;
#ifdef HAVE_DBUS
int r;
long long rttime;
#ifdef RLIMIT_RTTIME
struct rlimit rl;
#endif
DBusError error;
DBusConnection *bus;
dbus_error_init(&error);
#endif
pa_zero(sp);
sp.sched_priority = rtprio;
#ifdef SCHED_RESET_ON_FORK
if (pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &sp) == 0) {
pa_log_debug("SCHED_RR|SCHED_RESET_ON_FORK worked.");
return 0;
}
#endif
if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == 0) {
pa_log_debug("SCHED_RR worked.");
return 0;
}
#endif /* HAVE_SCHED_H */
#ifdef HAVE_DBUS
/* Try to talk to RealtimeKit */
if (!(bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) {
pa_log("Failed to connect to system bus: %s", error.message);
dbus_error_free(&error);
errno = -EIO;
return -1;
}
/* We need to disable exit on disconnect because otherwise
* dbus_shutdown will kill us. See
* https://bugs.freedesktop.org/show_bug.cgi?id=16924 */
dbus_connection_set_exit_on_disconnect(bus, FALSE);
rttime = rtkit_get_rttime_usec_max(bus);
if (rttime >= 0) {
#ifdef RLIMIT_RTTIME
r = getrlimit(RLIMIT_RTTIME, &rl);
if (r >= 0 && (long long) rl.rlim_max > rttime) {
pa_log_info("Clamping rlimit-rttime to %lld for RealtimeKit", rttime);
rl.rlim_cur = rl.rlim_max = rttime;
r = setrlimit(RLIMIT_RTTIME, &rl);
if (r < 0)
pa_log("setrlimit() failed: %s", pa_cstrerror(errno));
}
#endif
r = rtkit_make_realtime(bus, 0, rtprio);
dbus_connection_close(bus);
dbus_connection_unref(bus);
if (r >= 0) {
pa_log_debug("RealtimeKit worked.");
return 0;
}
errno = -r;
} else {
dbus_connection_close(bus);
dbus_connection_unref(bus);
errno = -rttime;
}
#else
errno = 0;
#endif
return -1;
}
#endif
/* Make the current thread a realtime thread, and acquire the highest
* rtprio we can get that is less or equal the specified parameter. If
* the thread is already realtime, don't do anything. */
int pa_thread_make_realtime(int rtprio) {
#if defined(OS_IS_DARWIN)
struct thread_time_constraint_policy ttcpolicy;
uint64_t freq = 0;
size_t size = sizeof(freq);
int ret;
ret = sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0);
if (ret < 0) {
pa_log_info("Unable to read CPU frequency, acquisition of real-time scheduling failed.");
return -1;
}
pa_log_debug("sysctl for hw.cpufrequency: %llu", freq);
/* See http://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/KernelProgramming/scheduler/scheduler.html */
ttcpolicy.period = freq / 160;
ttcpolicy.computation = freq / 3300;
ttcpolicy.constraint = freq / 2200;
ttcpolicy.preemptible = 1;
ret = thread_policy_set(mach_thread_self(),
THREAD_TIME_CONSTRAINT_POLICY,
(thread_policy_t) &ttcpolicy,
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
if (ret) {
pa_log_info("Unable to set real-time thread priority (%08x).", ret);
return -1;
}
pa_log_info("Successfully acquired real-time thread priority.");
return 0;
#elif defined(_POSIX_PRIORITY_SCHEDULING)
int p;
if (set_scheduler(rtprio) >= 0) {
pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i.", rtprio);
return 0;
}
for (p = rtprio-1; p >= 1; p--)
if (set_scheduler(p) >= 0) {
pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i, which is lower than the requested %i.", p, rtprio);
return 0;
}
#elif defined(OS_IS_WIN32)
/* Windows only allows realtime scheduling to be set on a per process basis.
* Therefore, instead of making the thread realtime, just give it the highest non-realtime priority. */
if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) {
pa_log_info("Successfully enabled THREAD_PRIORITY_TIME_CRITICAL scheduling for thread.");
return 0;
}
pa_log_warn("SetThreadPriority() failed: 0x%08X", GetLastError());
errno = EPERM;
#else
errno = ENOTSUP;
#endif
pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno));
return -1;
}

View file

@ -54,6 +54,12 @@ char *pa_path_get_filename(const char *p);
/** Wait t milliseconds */
int pa_msleep(unsigned long t);
/** Make the calling thread realtime if we can. On Linux, this uses RealTimeKit
* if available and POSIX APIs otherwise (the latter applies to other UNIX
* variants as well). This is also implemented for macOS and Windows.
* \since 13.0 */
int pa_thread_make_realtime(int rtprio);
PA_C_DECL_END
#endif

View file

@ -61,14 +61,6 @@
#endif
#endif
#ifdef HAVE_SCHED_H
#include <sched.h>
#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK)
#define SCHED_RESET_ON_FORK 0x40000000
#endif
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
@ -109,15 +101,8 @@
#include <samplerate.h>
#endif
#ifdef __APPLE__
#include <mach/mach_init.h>
#include <mach/thread_act.h>
#include <mach/thread_policy.h>
#include <sys/sysctl.h>
#endif
#ifdef HAVE_DBUS
#include "rtkit.h"
#include <pulsecore/rtkit.h>
#endif
#if defined(__linux__) && !defined(__ANDROID__)
@ -697,158 +682,6 @@ char *pa_strlcpy(char *b, const char *s, size_t l) {
return b;
}
#ifdef _POSIX_PRIORITY_SCHEDULING
static int set_scheduler(int rtprio) {
#ifdef HAVE_SCHED_H
struct sched_param sp;
#ifdef HAVE_DBUS
int r;
long long rttime;
#ifdef RLIMIT_RTTIME
struct rlimit rl;
#endif
DBusError error;
DBusConnection *bus;
dbus_error_init(&error);
#endif
pa_zero(sp);
sp.sched_priority = rtprio;
#ifdef SCHED_RESET_ON_FORK
if (pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, &sp) == 0) {
pa_log_debug("SCHED_RR|SCHED_RESET_ON_FORK worked.");
return 0;
}
#endif
if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp) == 0) {
pa_log_debug("SCHED_RR worked.");
return 0;
}
#endif /* HAVE_SCHED_H */
#ifdef HAVE_DBUS
/* Try to talk to RealtimeKit */
if (!(bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error))) {
pa_log("Failed to connect to system bus: %s", error.message);
dbus_error_free(&error);
errno = -EIO;
return -1;
}
/* We need to disable exit on disconnect because otherwise
* dbus_shutdown will kill us. See
* https://bugs.freedesktop.org/show_bug.cgi?id=16924 */
dbus_connection_set_exit_on_disconnect(bus, FALSE);
rttime = rtkit_get_rttime_usec_max(bus);
if (rttime >= 0) {
#ifdef RLIMIT_RTTIME
r = getrlimit(RLIMIT_RTTIME, &rl);
if (r >= 0 && (long long) rl.rlim_max > rttime) {
pa_log_info("Clamping rlimit-rttime to %lld for RealtimeKit", rttime);
rl.rlim_cur = rl.rlim_max = rttime;
r = setrlimit(RLIMIT_RTTIME, &rl);
if (r < 0)
pa_log("setrlimit() failed: %s", pa_cstrerror(errno));
}
#endif
r = rtkit_make_realtime(bus, 0, rtprio);
dbus_connection_close(bus);
dbus_connection_unref(bus);
if (r >= 0) {
pa_log_debug("RealtimeKit worked.");
return 0;
}
errno = -r;
} else {
dbus_connection_close(bus);
dbus_connection_unref(bus);
errno = -rttime;
}
#else
errno = 0;
#endif
return -1;
}
#endif
/* Make the current thread a realtime thread, and acquire the highest
* rtprio we can get that is less or equal the specified parameter. If
* the thread is already realtime, don't do anything. */
int pa_make_realtime(int rtprio) {
#if defined(OS_IS_DARWIN)
struct thread_time_constraint_policy ttcpolicy;
uint64_t freq = 0;
size_t size = sizeof(freq);
int ret;
ret = sysctlbyname("hw.cpufrequency", &freq, &size, NULL, 0);
if (ret < 0) {
pa_log_info("Unable to read CPU frequency, acquisition of real-time scheduling failed.");
return -1;
}
pa_log_debug("sysctl for hw.cpufrequency: %llu", freq);
/* See http://developer.apple.com/library/mac/#documentation/Darwin/Conceptual/KernelProgramming/scheduler/scheduler.html */
ttcpolicy.period = freq / 160;
ttcpolicy.computation = freq / 3300;
ttcpolicy.constraint = freq / 2200;
ttcpolicy.preemptible = 1;
ret = thread_policy_set(mach_thread_self(),
THREAD_TIME_CONSTRAINT_POLICY,
(thread_policy_t) &ttcpolicy,
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
if (ret) {
pa_log_info("Unable to set real-time thread priority (%08x).", ret);
return -1;
}
pa_log_info("Successfully acquired real-time thread priority.");
return 0;
#elif defined(_POSIX_PRIORITY_SCHEDULING)
int p;
if (set_scheduler(rtprio) >= 0) {
pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i.", rtprio);
return 0;
}
for (p = rtprio-1; p >= 1; p--)
if (set_scheduler(p) >= 0) {
pa_log_info("Successfully enabled SCHED_RR scheduling for thread, with priority %i, which is lower than the requested %i.", p, rtprio);
return 0;
}
#elif defined(OS_IS_WIN32)
/* Windows only allows realtime scheduling to be set on a per process basis.
* Therefore, instead of making the thread realtime, just give it the highest non-realtime priority. */
if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) {
pa_log_info("Successfully enabled THREAD_PRIORITY_TIME_CRITICAL scheduling for thread.");
return 0;
}
pa_log_warn("SetThreadPriority() failed: 0x%08X", GetLastError());
errno = EPERM;
#else
errno = ENOTSUP;
#endif
pa_log_info("Failed to acquire real-time scheduling: %s", pa_cstrerror(errno));
return -1;
}
#ifdef HAVE_SYS_RESOURCE_H
static int set_nice(int nice_level) {
#ifdef HAVE_DBUS
@ -935,7 +768,7 @@ int pa_raise_priority(int nice_level) {
}
/* Reset the priority to normal, inverting the changes made by
* pa_raise_priority() and pa_make_realtime()*/
* pa_raise_priority() and pa_thread_make_realtime()*/
void pa_reset_priority(void) {
#ifdef HAVE_SYS_RESOURCE_H
struct sched_param sp;

View file

@ -81,7 +81,6 @@ char *pa_strlcpy(char *b, const char *s, size_t l);
char *pa_parent_dir(const char *fn);
int pa_make_realtime(int rtprio);
int pa_raise_priority(int nice_level);
void pa_reset_priority(void);

View file

@ -208,7 +208,7 @@ static void context_state_callback(pa_context *c, void *userdata) {
case PA_CONTEXT_READY: {
pa_buffer_attr buffer_attr;
pa_make_realtime(4);
pa_thread_make_realtime(4);
/* Create playback stream */
buffer_attr.maxlength = -1;

View file

@ -57,7 +57,7 @@ static void work(void *p) {
pa_log_notice("CPU%i: Created thread.", PA_PTR_TO_UINT(p));
pa_make_realtime(12);
pa_thread_make_realtime(12);
#ifdef HAVE_PTHREAD_SETAFFINITY_NP
{