Skip to content

Commit

Permalink
thread: Support making threads real time scheduled
Browse files Browse the repository at this point in the history
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
heftig/rtkit#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>
  • Loading branch information
jadahl committed Jul 17, 2023
1 parent 276ebbf commit bd2fa92
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 0 deletions.
153 changes: 153 additions & 0 deletions src/backends/native/meta-thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,6 +38,7 @@ enum
PROP_BACKEND,
PROP_NAME,
PROP_THREAD_TYPE,
PROP_WANTS_REALTIME,

N_PROPS
};
Expand Down Expand Up @@ -70,6 +73,7 @@ typedef struct _MetaThreadPrivate
GMainContext *main_context;

MetaThreadImpl *impl;
gboolean wants_realtime;
gboolean waiting_for_impl_task;
GSource *wrapper_source;

Expand Down Expand Up @@ -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;
Expand All @@ -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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down
38 changes: 38 additions & 0 deletions src/tests/dbusmock-templates/rtkit.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/tests/mutter_dbusrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def setUpClass(klass, enable_kvm, launch):
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)
Expand Down
122 changes: 122 additions & 0 deletions src/tests/native-thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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
Expand Down

0 comments on commit bd2fa92

Please sign in to comment.