From bd2fa92c29e7e6a55676af508f1c32904e6cdbc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Mon, 24 Oct 2022 15:25:31 +0200 Subject: [PATCH] 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: --- src/backends/native/meta-thread.c | 153 ++++++++++++++++++++++++++ src/tests/dbusmock-templates/rtkit.py | 38 +++++++ src/tests/mutter_dbusrunner.py | 1 + src/tests/native-thread.c | 122 ++++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 src/tests/dbusmock-templates/rtkit.py diff --git a/src/backends/native/meta-thread.c b/src/backends/native/meta-thread.c index f74008264..4fbd13a37 100644 --- a/src/backends/native/meta-thread.c +++ b/src/backends/native/meta-thread.c @@ -22,11 +22,13 @@ #include "backends/native/meta-thread-private.h" #include +#include #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 + * . + */ + 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); } diff --git a/src/tests/dbusmock-templates/rtkit.py b/src/tests/dbusmock-templates/rtkit.py new file mode 100644 index 000000000..168582de8 --- /dev/null +++ b/src/tests/dbusmock-templates/rtkit.py @@ -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 diff --git a/src/tests/mutter_dbusrunner.py b/src/tests/mutter_dbusrunner.py index ca75094cd..2f776a788 100644 --- a/src/tests/mutter_dbusrunner.py +++ b/src/tests/mutter_dbusrunner.py @@ -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) diff --git a/src/tests/native-thread.c b/src/tests/native-thread.c index 6a3fc327c..2596b1418 100644 --- a/src/tests/native-thread.c +++ b/src/tests/native-thread.c @@ -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