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

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