Blame SOURCES/0004-global-force-fsync-to-worker-thread-when-saving-stat.patch

067a6b
From 6155789a63258865d65df4bf6abb4c83a22e58a8 Mon Sep 17 00:00:00 2001
067a6b
From: Christian Hergert <chergert@redhat.com>
067a6b
Date: Wed, 26 Feb 2020 14:46:20 -0800
067a6b
Subject: [PATCH 4/6] global: force fsync() to worker thread when saving state
067a6b
067a6b
The g_file_replace_contents_async() API can potentially call fsync() from
067a6b
the thread calling into it upon completion. This can have disasterous
067a6b
effects when run from the compositor main thread such as complete stalls.
067a6b
067a6b
This is a followup to 86a00b6872375a266449beee1ea6d5e94f1ebbcb which
067a6b
assumed (like the rest of us) that the fsync() would be performed on the
067a6b
thread that was doing the I/O operations.
067a6b
067a6b
You can verify this with an strace -e fsync and cause terminal to display
067a6b
a command completed notification (eg: from a backdrop window).
067a6b
067a6b
This also fixes a lifecycle bug for the variant, as
067a6b
g_file_replace_contents_async() does not copy the data during the operation
067a6b
as that is the responsibility of the caller. Instead, we just use a GBytes
067a6b
variant and reference the variant there.
067a6b
067a6b
https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/1050
067a6b
---
067a6b
 src/shell-global.c | 70 +++++++++++++++++++++++++++++++++++++++++-----
067a6b
 1 file changed, 63 insertions(+), 7 deletions(-)
067a6b
067a6b
diff --git a/src/shell-global.c b/src/shell-global.c
067a6b
index 8f5418677..3e4864555 100644
067a6b
--- a/src/shell-global.c
067a6b
+++ b/src/shell-global.c
067a6b
@@ -1804,6 +1804,55 @@ delete_variant_cb (GObject      *object,
067a6b
   g_hash_table_remove (global->save_ops, object);
067a6b
 }
067a6b
 
067a6b
+static void
067a6b
+replace_contents_worker (GTask        *task,
067a6b
+                         gpointer      source_object,
067a6b
+                         gpointer      task_data,
067a6b
+                         GCancellable *cancellable)
067a6b
+{
067a6b
+  GFile *file = source_object;
067a6b
+  GBytes *bytes = task_data;
067a6b
+  GError *error = NULL;
067a6b
+  const gchar *data;
067a6b
+  gsize len;
067a6b
+
067a6b
+  data = g_bytes_get_data (bytes, &len;;
067a6b
+
067a6b
+  if (!g_file_replace_contents (file, data, len, NULL, FALSE,
067a6b
+                                G_FILE_CREATE_REPLACE_DESTINATION,
067a6b
+                                NULL, cancellable, &error))
067a6b
+    g_task_return_error (task, g_steal_pointer (&error));
067a6b
+  else
067a6b
+    g_task_return_boolean (task, TRUE);
067a6b
+}
067a6b
+
067a6b
+static void
067a6b
+replace_contents_async (GFile               *path,
067a6b
+                        GBytes              *bytes,
067a6b
+                        GCancellable        *cancellable,
067a6b
+                        GAsyncReadyCallback  callback,
067a6b
+                        gpointer             user_data)
067a6b
+{
067a6b
+  g_autoptr(GTask) task = NULL;
067a6b
+
067a6b
+  g_assert (G_IS_FILE (path));
067a6b
+  g_assert (bytes != NULL);
067a6b
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
067a6b
+
067a6b
+  task = g_task_new (path, cancellable, callback, user_data);
067a6b
+  g_task_set_source_tag (task, replace_contents_async);
067a6b
+  g_task_set_task_data (task, g_bytes_ref (bytes), (GDestroyNotify)g_bytes_unref);
067a6b
+  g_task_run_in_thread (task, replace_contents_worker);
067a6b
+}
067a6b
+
067a6b
+static gboolean
067a6b
+replace_contents_finish (GFile         *file,
067a6b
+                         GAsyncResult  *result,
067a6b
+                         GError       **error)
067a6b
+{
067a6b
+  return g_task_propagate_boolean (G_TASK (result), error);
067a6b
+}
067a6b
+
067a6b
 static void
067a6b
 replace_variant_cb (GObject      *object,
067a6b
                     GAsyncResult *result,
067a6b
@@ -1812,7 +1861,7 @@ replace_variant_cb (GObject      *object,
067a6b
   ShellGlobal *global = user_data;
067a6b
   GError *error = NULL;
067a6b
 
067a6b
-  if (!g_file_replace_contents_finish (G_FILE (object), result, NULL, &error))
067a6b
+  if (!replace_contents_finish (G_FILE (object), result, &error))
067a6b
     {
067a6b
       if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
067a6b
         {
067a6b
@@ -1848,12 +1897,19 @@ save_variant (ShellGlobal *global,
067a6b
     }
067a6b
   else
067a6b
     {
067a6b
-      g_file_replace_contents_async (path,
067a6b
-                                     g_variant_get_data (variant),
067a6b
-                                     g_variant_get_size (variant),
067a6b
-                                     NULL, FALSE,
067a6b
-                                     G_FILE_CREATE_REPLACE_DESTINATION,
067a6b
-                                     cancellable, replace_variant_cb, global);
067a6b
+      g_autoptr(GBytes) bytes = NULL;
067a6b
+
067a6b
+      bytes = g_bytes_new_with_free_func (g_variant_get_data (variant),
067a6b
+                                          g_variant_get_size (variant),
067a6b
+                                          (GDestroyNotify)g_variant_unref,
067a6b
+                                          g_variant_ref (variant));
067a6b
+      /* g_file_replace_contents_async() can potentially fsync() from the
067a6b
+       * calling thread when completing the asynchronous task. Instead, we
067a6b
+       * want to force that fsync() to a thread to avoid blocking the
067a6b
+       * compository main loop. Using our own replace_contents_async()
067a6b
+       * simply executes the operation synchronously from a thread.
067a6b
+       */
067a6b
+      replace_contents_async (path, bytes, cancellable, replace_variant_cb, global);
067a6b
     }
067a6b
 
067a6b
   g_object_unref (path);
067a6b
-- 
067a6b
2.26.0
067a6b