Blob Blame History Raw
From c38064c9614b70e92a38e98df3ebd6b232ea2f55 Mon Sep 17 00:00:00 2001
From: Ray Strode <rstrode@redhat.com>
Date: Tue, 10 Oct 2023 14:39:13 -0400
Subject: [PATCH 3/4] thread: Allow turnning off rt scheduling for running
 thread

At the moment if a thread is made real-time there's no going back,
it stays real-time for the duration of its life.

That's suboptimal because real-time threads are expected by RTKit to
have an rlimit on their CPU time and certain GPU drivers in the kernel
can exceed that CPU time during certain operations like DPMS off.

This commit adds two new ref counted functions:

    meta_thread_{un,}inhibit_realtime_in_impl

that allow turning a thread real-time or normally scheduled. At the same
time, this commit stores the RTKit proxy as private data on the thread
so that it can be reused by the above apis.

A subsequent commit will use the new APIs.
---
 src/backends/native/meta-thread.c | 174 ++++++++++++++++++++++++++----
 src/backends/native/meta-thread.h |   6 ++
 2 files changed, 157 insertions(+), 23 deletions(-)

diff --git a/src/backends/native/meta-thread.c b/src/backends/native/meta-thread.c
index 93a84a8a5..f291c0b4a 100644
--- a/src/backends/native/meta-thread.c
+++ b/src/backends/native/meta-thread.c
@@ -56,62 +56,65 @@ typedef struct _MetaThreadCallbackSource
 
   GMutex mutex;
   GCond cond;
 
   MetaThread *thread;
   GMainContext *main_context;
   GList *callbacks;
   gboolean needs_flush;
 } MetaThreadCallbackSource;
 
 typedef struct _MetaThreadPrivate
 {
   MetaBackend *backend;
   char *name;
 
   GMainContext *main_context;
 
   MetaThreadImpl *impl;
   gboolean wants_realtime;
   gboolean waiting_for_impl_task;
   GSource *wrapper_source;
 
   GMutex callbacks_mutex;
   GHashTable *callback_sources;
 
   MetaThreadType thread_type;
 
   GThread *main_thread;
 
   struct {
+    MetaDBusRealtimeKit1 *rtkit_proxy;
     GThread *thread;
+    pid_t thread_id;
     GMutex init_mutex;
+    int realtime_inhibit_count;
   } kernel;
 } MetaThreadPrivate;
 
 typedef struct _MetaThreadClassPrivate
 {
   GType impl_type;
 } MetaThreadClassPrivate;
 
 static void initable_iface_init (GInitableIface *initable_iface);
 
 G_DEFINE_TYPE_WITH_CODE (MetaThread, meta_thread, G_TYPE_OBJECT,
                          G_ADD_PRIVATE (MetaThread)
                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
                                                 initable_iface_init)
                          g_type_add_class_private (g_define_type_id,
                                                    sizeof (MetaThreadClassPrivate)))
 
 static void
 meta_thread_callback_data_free (MetaThreadCallbackData *callback_data)
 {
   if (callback_data->user_data_destroy)
     callback_data->user_data_destroy (callback_data->user_data);
   g_free (callback_data);
 }
 
 static void
 meta_thread_get_property (GObject    *object,
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
@@ -175,181 +178,238 @@ get_rtkit_property (MetaDBusRealtimeKit1  *rtkit_proxy,
 {
   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_realtime_scheduling (MetaThread  *thread,
-                             GError     **error)
+ensure_realtime_kit_proxy (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;
+
+  if (priv->kernel.rtkit_proxy)
+    return TRUE;
 
   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);
+  priv->kernel.rtkit_proxy = g_steal_pointer (&rtkit_proxy);
+  return TRUE;
+}
+
+static gboolean
+request_realtime_scheduling (MetaThread  *thread,
+                             GError     **error)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  g_autoptr (GError) local_error = NULL;
+  int64_t rttime;
+  struct rlimit rl;
+  uint32_t priority;
+
+  if (!ensure_realtime_kit_proxy (thread, error))
+    return FALSE;
+
+  priority = meta_dbus_realtime_kit1_get_max_realtime_priority (priv->kernel.rtkit_proxy);
   if (priority == 0)
     {
       g_autoptr (GVariant) priority_variant = NULL;
 
-      priority_variant = get_rtkit_property (rtkit_proxy,
+      priority_variant = get_rtkit_property (priv->kernel.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);
+  rttime = meta_dbus_realtime_kit1_get_rttime_usec_max (priv->kernel.rtkit_proxy);
   if (rttime == 0)
     {
       g_autoptr (GVariant) rttime_variant = NULL;
 
-      rttime_variant = get_rtkit_property (rtkit_proxy,
+      rttime_variant = get_rtkit_property (priv->kernel.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 (),
+  if (!meta_dbus_realtime_kit1_call_make_thread_realtime_sync (priv->kernel.rtkit_proxy,
+                                                               priv->kernel.thread_id,
                                                                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 gboolean
+request_normal_scheduling (MetaThread  *thread,
+                           GError     **error)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  g_autoptr (GError) local_error = NULL;
+
+  if (!ensure_realtime_kit_proxy (thread, error))
+    return FALSE;
+
+  meta_topic (META_DEBUG_BACKEND, "Setting '%s' thread to normal priority", priv->name);
+  if (!meta_dbus_realtime_kit1_call_make_thread_high_priority_sync (priv->kernel.rtkit_proxy,
+                                                                    priv->kernel.thread_id,
+                                                                    0 /* "normal" nice value */,
+                                                                    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 gboolean
+meta_thread_uses_realtime_scheduling (MetaThread *thread)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  gboolean uses_realtime_scheduling = FALSE;
+
+  switch (priv->thread_type)
+    {
+    case META_THREAD_TYPE_USER:
+      break;
+    case META_THREAD_TYPE_KERNEL:
+      if (priv->kernel.realtime_inhibit_count == 0)
+        uses_realtime_scheduling = TRUE;
+      break;
+    }
+
+  return uses_realtime_scheduling;
+}
+
 static gpointer
 thread_impl_func (gpointer user_data)
 {
   MetaThread *thread = META_THREAD (user_data);
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
   MetaThreadImpl *impl = priv->impl;
   MetaThreadImplRunFlags run_flags = META_THREAD_IMPL_RUN_FLAG_NONE;
   GMainContext *thread_context = meta_thread_impl_get_main_context (impl);
 #ifdef HAVE_PROFILER
   MetaContext *context = meta_backend_get_context (priv->backend);
   MetaProfiler *profiler = meta_context_get_profiler (context);
 #endif
 
   g_mutex_lock (&priv->kernel.init_mutex);
   g_mutex_unlock (&priv->kernel.init_mutex);
 
   g_main_context_push_thread_default (thread_context);
 
 #ifdef HAVE_PROFILER
   meta_profiler_register_thread (profiler, thread_context, priv->name);
 #endif
 
+  priv->kernel.thread_id = gettid ();
+  priv->kernel.realtime_inhibit_count = 1;
+
   if (priv->wants_realtime)
-    {
-      g_autoptr (GError) error = NULL;
+    meta_thread_uninhibit_realtime_in_impl (thread);
 
-      if (!request_realtime_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);
-          run_flags |= META_THREAD_IMPL_RUN_FLAG_REALTIME;
-        }
+  if (meta_thread_uses_realtime_scheduling (thread))
+    {
+      g_message ("Made thread '%s' realtime scheduled", priv->name);
+      run_flags |= META_THREAD_IMPL_RUN_FLAG_REALTIME;
     }
 
   meta_thread_impl_run (impl, run_flags);
 
 #ifdef HAVE_PROFILER
   meta_profiler_unregister_thread (profiler, thread_context);
 #endif
 
   g_main_context_pop_thread_default (thread_context);
 
   return GINT_TO_POINTER (TRUE);
 }
 
 typedef struct _WrapperSource
 {
   GSource base;
 
   GMainContext *thread_main_context;
 
   GPollFD fds[256];
   gpointer fd_tags[256];
   int n_fds;
   int priority;
 } WrapperSource;
 
 static gboolean
 wrapper_source_prepare (GSource *source,
                         int     *timeout)
 {
   WrapperSource *wrapper_source = (WrapperSource *) source;
@@ -522,60 +582,66 @@ meta_thread_initable_init (GInitable     *initable,
 
   start_thread (thread);
 
   return TRUE;
 }
 
 static void
 initable_iface_init (GInitableIface *initable_iface)
 {
   initable_iface->init = meta_thread_initable_init;
 }
 
 static void
 finalize_thread_user (MetaThread *thread)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   meta_thread_impl_terminate (priv->impl);
   while (meta_thread_impl_dispatch (priv->impl) > 0);
   unwrap_main_context (thread, meta_thread_impl_get_main_context (priv->impl));
 }
 
 static void
 finalize_thread_kernel (MetaThread *thread)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   meta_thread_impl_terminate (priv->impl);
   g_thread_join (priv->kernel.thread);
   priv->kernel.thread = NULL;
+  priv->kernel.thread_id = 0;
+
+  priv->kernel.realtime_inhibit_count = -1;
+
+  g_clear_object (&priv->kernel.rtkit_proxy);
+
   g_mutex_clear (&priv->kernel.init_mutex);
 }
 
 static void
 tear_down_thread (MetaThread *thread)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   switch (priv->thread_type)
     {
     case META_THREAD_TYPE_USER:
       finalize_thread_user (thread);
       break;
     case META_THREAD_TYPE_KERNEL:
       finalize_thread_kernel (thread);
       break;
     }
 
   meta_thread_flush_callbacks (thread);
 }
 
 static void
 meta_thread_finalize (GObject *object)
 {
   MetaThread *thread = META_THREAD (object);
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   tear_down_thread (thread);
 
   meta_thread_unregister_callback_context (thread, priv->main_context);
@@ -618,60 +684,62 @@ meta_thread_class_init (MetaThreadClass *klass)
 
   obj_props[PROP_THREAD_TYPE] =
     g_param_spec_enum ("thread-type",
                        "thread-type",
                        "Type of thread",
                        META_TYPE_THREAD_TYPE,
                        META_THREAD_TYPE_KERNEL,
                        G_PARAM_READWRITE |
                        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);
 }
 
 static void
 meta_thread_init (MetaThread *thread)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   g_mutex_init (&priv->callbacks_mutex);
   priv->main_thread = g_thread_self ();
+
+  priv->kernel.realtime_inhibit_count = -1;
 }
 
 void
 meta_thread_class_register_impl_type (MetaThreadClass *thread_class,
                                       GType            impl_type)
 {
   MetaThreadClassPrivate *class_priv =
     G_TYPE_CLASS_GET_PRIVATE (thread_class, META_TYPE_THREAD,
                               MetaThreadClassPrivate);
 
   g_assert (class_priv->impl_type == G_TYPE_INVALID);
   class_priv->impl_type = impl_type;
 }
 
 void
 meta_thread_reset_thread_type (MetaThread     *thread,
                                MetaThreadType  thread_type)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
   g_autoptr (GMainContext) thread_context = NULL;
 
   if (priv->thread_type == thread_type)
     return;
 
   tear_down_thread (thread);
   g_assert (!priv->wrapper_source);
 
   priv->thread_type = thread_type;
 
   start_thread (thread);
@@ -1134,30 +1202,90 @@ meta_thread_get_thread_type (MetaThread *thread)
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   return priv->thread_type;
 }
 
 GThread *
 meta_thread_get_thread (MetaThread *thread)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   g_assert (priv->thread_type == META_THREAD_TYPE_KERNEL);
 
   return priv->kernel.thread;
 }
 
 gboolean
 meta_thread_is_in_impl_task (MetaThread *thread)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   return meta_thread_impl_is_in_impl (priv->impl);
 }
 
 gboolean
 meta_thread_is_waiting_for_impl_task (MetaThread *thread)
 {
   MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
 
   return priv->waiting_for_impl_task;
 }
+
+void
+meta_thread_inhibit_realtime_in_impl (MetaThread *thread)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  g_autoptr (GError) error = NULL;
+
+  switch (priv->thread_type)
+    {
+    case META_THREAD_TYPE_KERNEL:
+      priv->kernel.realtime_inhibit_count++;
+
+      if (priv->kernel.realtime_inhibit_count == 1)
+        {
+          if (!request_normal_scheduling (thread, &error))
+            {
+              g_warning ("Failed to make thread '%s' normally scheduled: %s",
+                         priv->name, error->message);
+              priv->kernel.realtime_inhibit_count--;
+            }
+          else
+            {
+              meta_topic (META_DEBUG_BACKEND, "Made thread '%s' normally scheduled", priv->name);
+            }
+        }
+      break;
+    case META_THREAD_TYPE_USER:
+      break;
+    }
+}
+
+void
+meta_thread_uninhibit_realtime_in_impl (MetaThread *thread)
+{
+  MetaThreadPrivate *priv = meta_thread_get_instance_private (thread);
+  g_autoptr (GError) error = NULL;
+
+  switch (priv->thread_type)
+    {
+    case META_THREAD_TYPE_KERNEL:
+      priv->kernel.realtime_inhibit_count--;
+
+      if (priv->kernel.realtime_inhibit_count == 0)
+        {
+          if (!request_realtime_scheduling (thread, &error))
+            {
+              g_warning ("Failed to make thread '%s' realtime scheduled: %s",
+                         priv->name, error->message);
+              priv->kernel.realtime_inhibit_count++;
+            }
+          else
+            {
+              meta_topic (META_DEBUG_BACKEND, "Made thread '%s' realtime scheduled", priv->name);
+            }
+        }
+      break;
+    case META_THREAD_TYPE_USER:
+      break;
+    }
+}
diff --git a/src/backends/native/meta-thread.h b/src/backends/native/meta-thread.h
index 9fc3588b9..96b79b586 100644
--- a/src/backends/native/meta-thread.h
+++ b/src/backends/native/meta-thread.h
@@ -71,36 +71,42 @@ void meta_thread_queue_callback (MetaThread         *thread,
                                  GDestroyNotify      user_data_destroy);
 
 META_EXPORT_TEST
 void meta_thread_flush_callbacks (MetaThread *thread);
 
 META_EXPORT_TEST
 gpointer meta_thread_run_impl_task_sync (MetaThread          *thread,
                                          MetaThreadTaskFunc   func,
                                          gpointer             user_data,
                                          GError             **error);
 
 META_EXPORT_TEST
 void meta_thread_post_impl_task (MetaThread                 *thread,
                                  MetaThreadTaskFunc          func,
                                  gpointer                    user_data,
                                  GDestroyNotify              user_data_destroy,
                                  MetaThreadTaskFeedbackFunc  feedback_func,
                                  gpointer                    feedback_user_data);
 
 META_EXPORT_TEST
 MetaBackend * meta_thread_get_backend (MetaThread *thread);
 
 META_EXPORT_TEST
 const char * meta_thread_get_name (MetaThread *thread);
 
 META_EXPORT_TEST
 gboolean meta_thread_is_in_impl_task (MetaThread *thread);
 
 gboolean meta_thread_is_waiting_for_impl_task (MetaThread *thread);
 
+META_EXPORT_TEST
+void meta_thread_inhibit_realtime_in_impl (MetaThread *thread);
+
+META_EXPORT_TEST
+void meta_thread_uninhibit_realtime_in_impl (MetaThread *thread);
+
 #define meta_assert_in_thread_impl(thread) \
         g_assert (meta_thread_is_in_impl_task (thread))
 #define meta_assert_not_in_thread_impl(thread) \
         g_assert (!meta_thread_is_in_impl_task (thread))
 #define meta_assert_is_waiting_for_thread_impl_task(thread) \
         g_assert (meta_thread_is_waiting_for_impl_task (thread))
-- 
2.41.0