thread: Support making threads real time scheduled
Real time scheduling is needed for better control of when we commit updates to the kernel, so add a property to MetaThread that, if the thread implementation uses a kernel thread and not a user thread, RTKit is asked to make the thread real time scheduled using the maximum priority allowed. Currently RTKit doesn't support the GetAll() D-Bus properties method, so some fall back code is added, as GDBusProxy depends on GetAll() working to make the cached properties up to date. Once https://github.com/heftig/rtkit/pull/30 lands and becomes widely available in distributions, the work around can be dropped. Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2777>
This commit is contained in:
parent
276ebbf5ee
commit
bd2fa92c29
@ -22,11 +22,13 @@
|
||||
#include "backends/native/meta-thread-private.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#include "backends/meta-backend-private.h"
|
||||
#include "backends/meta-backend-types.h"
|
||||
#include "backends/native/meta-thread-impl.h"
|
||||
|
||||
#include "meta-dbus-rtkit1.h"
|
||||
#include "meta-private-enum-types.h"
|
||||
|
||||
enum
|
||||
@ -36,6 +38,7 @@ enum
|
||||
PROP_BACKEND,
|
||||
PROP_NAME,
|
||||
PROP_THREAD_TYPE,
|
||||
PROP_WANTS_REALTIME,
|
||||
|
||||
N_PROPS
|
||||
};
|
||||
@ -70,6 +73,7 @@ typedef struct _MetaThreadPrivate
|
||||
GMainContext *main_context;
|
||||
|
||||
MetaThreadImpl *impl;
|
||||
gboolean wants_realtime;
|
||||
gboolean waiting_for_impl_task;
|
||||
GSource *wrapper_source;
|
||||
|
||||
@ -128,6 +132,9 @@ meta_thread_get_property (GObject *object,
|
||||
case PROP_THREAD_TYPE:
|
||||
g_value_set_enum (value, priv->thread_type);
|
||||
break;
|
||||
case PROP_WANTS_REALTIME:
|
||||
g_value_set_boolean (value, priv->wants_realtime);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -154,12 +161,134 @@ meta_thread_set_property (GObject *object,
|
||||
case PROP_THREAD_TYPE:
|
||||
priv->thread_type = g_value_get_enum (value);
|
||||
break;
|
||||
case PROP_WANTS_REALTIME:
|
||||
priv->wants_realtime = g_value_get_boolean (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
get_rtkit_property (MetaDBusRealtimeKit1 *rtkit_proxy,
|
||||
const char *property_name,
|
||||
GError **error)
|
||||
{
|
||||
GDBusConnection *connection;
|
||||
g_autoptr (GVariant) prop_value = NULL;
|
||||
g_autoptr (GVariant) property_variant = NULL;
|
||||
|
||||
/* The following is a fall back path for a RTKit daemon that doesn't support
|
||||
* org.freedesktop.DBus.Properties.GetAll. See
|
||||
* <https://github.com/heftig/rtkit/pull/30>.
|
||||
*/
|
||||
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (rtkit_proxy));
|
||||
prop_value =
|
||||
g_dbus_connection_call_sync (connection,
|
||||
"org.freedesktop.RealtimeKit1",
|
||||
"/org/freedesktop/RealtimeKit1",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"Get",
|
||||
g_variant_new ("(ss)",
|
||||
"org.freedesktop.RealtimeKit1",
|
||||
property_name),
|
||||
G_VARIANT_TYPE ("(v)"),
|
||||
G_DBUS_CALL_FLAGS_NO_AUTO_START,
|
||||
-1, NULL, error);
|
||||
if (!prop_value)
|
||||
return NULL;
|
||||
|
||||
g_variant_get (prop_value, "(v)", &property_variant);
|
||||
return g_steal_pointer (&property_variant);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
request_real_time_scheduling (MetaThread *thread,
|
||||
GError **error)
|
||||
{
|
||||
MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
|
||||
g_autoptr (MetaDBusRealtimeKit1) rtkit_proxy = NULL;
|
||||
g_autoptr (GError) local_error = NULL;
|
||||
int64_t rttime;
|
||||
struct rlimit rl;
|
||||
uint32_t priority;
|
||||
|
||||
rtkit_proxy =
|
||||
meta_dbus_realtime_kit1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
||||
"org.freedesktop.RealtimeKit1",
|
||||
"/org/freedesktop/RealtimeKit1",
|
||||
NULL,
|
||||
&local_error);
|
||||
if (!rtkit_proxy)
|
||||
{
|
||||
g_dbus_error_strip_remote_error (local_error);
|
||||
g_propagate_prefixed_error (error, g_steal_pointer (&local_error),
|
||||
"Failed to acquire RTKit D-Bus proxy: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
priority = meta_dbus_realtime_kit1_get_max_realtime_priority (rtkit_proxy);
|
||||
if (priority == 0)
|
||||
{
|
||||
g_autoptr (GVariant) priority_variant = NULL;
|
||||
|
||||
priority_variant = get_rtkit_property (rtkit_proxy,
|
||||
"MaxRealtimePriority",
|
||||
error);
|
||||
if (!priority_variant)
|
||||
return FALSE;
|
||||
|
||||
priority = g_variant_get_int32 (priority_variant);
|
||||
}
|
||||
|
||||
if (priority == 0)
|
||||
g_warning ("Maximum real time scheduling priority is 0");
|
||||
|
||||
rttime = meta_dbus_realtime_kit1_get_rttime_usec_max (rtkit_proxy);
|
||||
if (rttime == 0)
|
||||
{
|
||||
g_autoptr (GVariant) rttime_variant = NULL;
|
||||
|
||||
rttime_variant = get_rtkit_property (rtkit_proxy,
|
||||
"RTTimeUSecMax",
|
||||
error);
|
||||
if (!rttime_variant)
|
||||
return FALSE;
|
||||
|
||||
rttime = g_variant_get_int64 (rttime_variant);
|
||||
}
|
||||
|
||||
meta_topic (META_DEBUG_BACKEND,
|
||||
"Setting soft and hard RLIMIT_RTTIME limit to %lu", rttime);
|
||||
rl.rlim_cur = rttime;
|
||||
rl.rlim_max = rttime;
|
||||
|
||||
if (setrlimit (RLIMIT_RTTIME, &rl) != 0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
||||
"Failed to set RLIMIT_RTTIME: %s", g_strerror (errno));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
meta_topic (META_DEBUG_BACKEND, "Setting '%s' thread real time priority to %d",
|
||||
priv->name, priority);
|
||||
if (!meta_dbus_realtime_kit1_call_make_thread_realtime_sync (rtkit_proxy,
|
||||
gettid (),
|
||||
priority,
|
||||
NULL,
|
||||
&local_error))
|
||||
{
|
||||
g_dbus_error_strip_remote_error (local_error);
|
||||
g_propagate_error (error, g_steal_pointer (&local_error));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
thread_impl_func (gpointer user_data)
|
||||
{
|
||||
@ -169,6 +298,21 @@ thread_impl_func (gpointer user_data)
|
||||
g_mutex_lock (&priv->kernel.init_mutex);
|
||||
g_mutex_unlock (&priv->kernel.init_mutex);
|
||||
|
||||
if (priv->wants_realtime)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!request_real_time_scheduling (thread, &error))
|
||||
{
|
||||
g_warning ("Failed to make thread '%s' realtime scheduled: %s",
|
||||
priv->name, error->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_message ("Made thread '%s' realtime scheduled", priv->name);
|
||||
}
|
||||
}
|
||||
|
||||
meta_thread_impl_run (priv->impl);
|
||||
|
||||
return GINT_TO_POINTER (TRUE);
|
||||
@ -463,6 +607,15 @@ meta_thread_class_init (MetaThreadClass *klass)
|
||||
G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_STATIC_STRINGS);
|
||||
|
||||
obj_props[PROP_WANTS_REALTIME] =
|
||||
g_param_spec_boolean ("wants-realtime",
|
||||
"wants-realtime",
|
||||
"Wants real-time thread scheduling",
|
||||
FALSE,
|
||||
G_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, obj_props);
|
||||
}
|
||||
|
||||
|
38
src/tests/dbusmock-templates/rtkit.py
Normal file
38
src/tests/dbusmock-templates/rtkit.py
Normal file
@ -0,0 +1,38 @@
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Lesser General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option) any
|
||||
# later version. See http://www.gnu.org/copyleft/lgpl.html for the full text
|
||||
# of the license.
|
||||
|
||||
__author__ = 'Jonas Ådahl'
|
||||
__copyright__ = '(c) 2022 Red Hat Inc.'
|
||||
|
||||
import dbus
|
||||
from dbusmock import MOCK_IFACE, mockobject
|
||||
|
||||
BUS_NAME = 'org.freedesktop.RealtimeKit1'
|
||||
MAIN_OBJ = '/org/freedesktop/RealtimeKit1'
|
||||
MAIN_IFACE = 'org.freedesktop.RealtimeKit1'
|
||||
SYSTEM_BUS = True
|
||||
|
||||
|
||||
def load(mock, parameters):
|
||||
mock.AddProperty(MAIN_IFACE, 'RTTimeUSecMax', dbus.Int64(200000))
|
||||
mock.AddProperty(MAIN_IFACE, 'MaxRealtimePriority', dbus.Int32(20))
|
||||
mock.AddProperty(MAIN_IFACE, 'MinNiceLevel', dbus.Int32(-15))
|
||||
mock.priorities = dict()
|
||||
|
||||
@dbus.service.method(MAIN_IFACE, in_signature='tu')
|
||||
def MakeThreadRealtime(self, thread, priority):
|
||||
self.priorities[thread] = priority
|
||||
|
||||
@dbus.service.method(MOCK_IFACE)
|
||||
def Reset(self):
|
||||
self.priorities = dict()
|
||||
|
||||
@dbus.service.method(MOCK_IFACE, in_signature='t', out_signature='u')
|
||||
def GetThreadPriority(self, thread):
|
||||
if thread in self.priorities:
|
||||
return self.priorities[thread]
|
||||
else:
|
||||
return 0
|
@ -48,6 +48,7 @@ class MutterDBusRunner(DBusTestCase):
|
||||
klass.start_from_local_template('localed')
|
||||
klass.start_from_local_template('colord')
|
||||
klass.start_from_local_template('gsd-color')
|
||||
klass.start_from_local_template('rtkit')
|
||||
|
||||
klass.system_bus_con = klass.get_dbus(system_bus=True)
|
||||
klass.session_bus_con = klass.get_dbus(system_bus=False)
|
||||
|
@ -1119,6 +1119,124 @@ meta_test_thread_change_thread_type (void)
|
||||
g_assert_null (test_thread);
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
call_rtkit_mock_method (const char *method,
|
||||
GVariant *argument)
|
||||
{
|
||||
g_autoptr (GDBusConnection) connection = NULL;
|
||||
GError *local_error = NULL;
|
||||
GVariant *ret;
|
||||
|
||||
connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM,
|
||||
NULL, NULL);
|
||||
|
||||
ret = g_dbus_connection_call_sync (connection,
|
||||
"org.freedesktop.RealtimeKit1",
|
||||
"/org/freedesktop/RealtimeKit1",
|
||||
"org.freedesktop.DBus.Mock",
|
||||
method, argument,
|
||||
NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
|
||||
NULL, &local_error);
|
||||
if (!ret)
|
||||
g_error ("Failed to get tread priority: %s", local_error->message);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
assert_realtime (MetaThreadImpl *thread_impl,
|
||||
gpointer user_data,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr (GVariant) ret = NULL;
|
||||
g_autoptr (GVariant) priority_variant = NULL;
|
||||
uint32_t priority = 0;
|
||||
|
||||
ret = call_rtkit_mock_method ("GetThreadPriority",
|
||||
g_variant_new ("(t)", gettid ()));
|
||||
|
||||
g_variant_get (ret, "(u)", &priority);
|
||||
g_assert_cmpint (priority, ==, 20);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
meta_test_thread_realtime (void)
|
||||
{
|
||||
MetaBackend *backend = meta_context_get_backend (test_context);
|
||||
MetaThread *thread;
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autoptr (GVariant) ret = NULL;
|
||||
|
||||
ret = call_rtkit_mock_method ("Reset", NULL);
|
||||
|
||||
thread = g_initable_new (META_TYPE_THREAD_TEST,
|
||||
NULL, &error,
|
||||
"backend", backend,
|
||||
"name", "test realtime",
|
||||
"thread-type", META_THREAD_TYPE_KERNEL,
|
||||
"wants-realtime", TRUE,
|
||||
NULL);
|
||||
g_object_add_weak_pointer (G_OBJECT (thread), (gpointer *) &thread);
|
||||
g_assert_nonnull (thread);
|
||||
g_assert_null (error);
|
||||
|
||||
meta_thread_post_impl_task (thread, assert_realtime, NULL, NULL,
|
||||
NULL, NULL);
|
||||
|
||||
g_object_unref (thread);
|
||||
g_assert_null (thread);
|
||||
g_assert_null (test_thread);
|
||||
}
|
||||
|
||||
static gpointer
|
||||
assert_no_realtime (MetaThreadImpl *thread_impl,
|
||||
gpointer user_data,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr (GVariant) ret = NULL;
|
||||
g_autoptr (GVariant) priority_variant = NULL;
|
||||
uint32_t priority = UINT32_MAX;
|
||||
|
||||
ret = call_rtkit_mock_method ("GetThreadPriority",
|
||||
g_variant_new ("(t)", gettid ()));
|
||||
|
||||
g_variant_get (ret, "(u)", &priority);
|
||||
g_assert_cmpint (priority, ==, 0);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
meta_test_thread_no_realtime (void)
|
||||
{
|
||||
MetaBackend *backend = meta_context_get_backend (test_context);
|
||||
MetaThread *thread;
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autoptr (GVariant) ret = NULL;
|
||||
|
||||
ret = call_rtkit_mock_method ("Reset", NULL);
|
||||
|
||||
thread = g_initable_new (META_TYPE_THREAD_TEST,
|
||||
NULL, &error,
|
||||
"backend", backend,
|
||||
"name", "test realtime",
|
||||
"thread-type", META_THREAD_TYPE_USER,
|
||||
"wants-realtime", TRUE,
|
||||
NULL);
|
||||
g_object_add_weak_pointer (G_OBJECT (thread), (gpointer *) &thread);
|
||||
g_assert_nonnull (thread);
|
||||
g_assert_null (error);
|
||||
|
||||
meta_thread_post_impl_task (thread, assert_no_realtime, NULL, NULL,
|
||||
NULL, NULL);
|
||||
|
||||
g_object_unref (thread);
|
||||
g_assert_null (thread);
|
||||
g_assert_null (test_thread);
|
||||
}
|
||||
|
||||
static void
|
||||
init_tests (void)
|
||||
{
|
||||
@ -1136,6 +1254,10 @@ init_tests (void)
|
||||
meta_test_thread_kernel_run_task_off_thread);
|
||||
g_test_add_func ("/backends/native/thread/change-thread-type",
|
||||
meta_test_thread_change_thread_type);
|
||||
g_test_add_func ("/backends/native/thread/realtime",
|
||||
meta_test_thread_realtime);
|
||||
g_test_add_func ("/backends/native/thread/no-realtime",
|
||||
meta_test_thread_no_realtime);
|
||||
}
|
||||
|
||||
int
|
||||
|
Loading…
Reference in New Issue
Block a user