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:
Jonas Ådahl 2022-10-24 15:25:31 +02:00
parent 276ebbf5ee
commit bd2fa92c29
4 changed files with 314 additions and 0 deletions

View File

@ -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);
}

View 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

View File

@ -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)

View File

@ -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