[gnome-software: 7/14] gs-plugin-loader: Make setup() asynchronous




commit 8f156c471ebdd7df815c22e89b672d45f28429d6
Author: Philip Withnall <pwithnall endlessos org>
Date:   Tue Mar 1 14:33:22 2022 +0000

    gs-plugin-loader: Make setup() asynchronous
    
    And add a synchronous wrapper to `gs-plugin-loader-sync.c` which the
    tests will continue to use.
    
    Making `setup()` asynchronous means that it can be run without running
    inside a new nested `GMainContext`. This means that any long-lived
    objects created during a plugin’s setup function, which reference the
    current thread-default `GMainContext`, will not end up referencing the
    temporary one.
    
    Once `gs-application.c` is ported to use the new asynchronous API, this
    will fix file monitoring.
    
    Signed-off-by: Philip Withnall <pwithnall endlessos org>
    
    Helps: #1661

 lib/gs-plugin-loader-sync.c |  39 +++++++++
 lib/gs-plugin-loader-sync.h |   5 ++
 lib/gs-plugin-loader.c      | 194 ++++++++++++++++++++++++++++----------------
 lib/gs-plugin-loader.h      |  10 ++-
 lib/gs-test.c               |   4 +-
 5 files changed, 177 insertions(+), 75 deletions(-)
---
diff --git a/lib/gs-plugin-loader-sync.c b/lib/gs-plugin-loader-sync.c
index f7b0d0f50..345fa4cc7 100644
--- a/lib/gs-plugin-loader-sync.c
+++ b/lib/gs-plugin-loader-sync.c
@@ -250,3 +250,42 @@ gs_plugin_loader_get_system_app (GsPluginLoader *plugin_loader,
 
        return app;
 }
+
+gboolean
+gs_plugin_loader_setup (GsPluginLoader       *plugin_loader,
+                        const gchar * const  *allowlist,
+                        const gchar * const  *blocklist,
+                        GCancellable         *cancellable,
+                        GError              **error)
+{
+       GsPluginLoaderHelper helper;
+       gboolean retval;
+
+       /* create temp object */
+       helper.res = NULL;
+       helper.context = g_main_context_new ();
+       helper.loop = g_main_loop_new (helper.context, FALSE);
+
+       g_main_context_push_thread_default (helper.context);
+
+       /* run async method */
+       gs_plugin_loader_setup_async (plugin_loader,
+                                     allowlist,
+                                     blocklist,
+                                     cancellable,
+                                     _helper_finish_sync,
+                                     &helper);
+       g_main_loop_run (helper.loop);
+       retval = gs_plugin_loader_setup_finish (plugin_loader,
+                                               helper.res,
+                                               error);
+
+       g_main_context_pop_thread_default (helper.context);
+
+       g_main_loop_unref (helper.loop);
+       g_main_context_unref (helper.context);
+       if (helper.res != NULL)
+               g_object_unref (helper.res);
+
+       return retval;
+}
diff --git a/lib/gs-plugin-loader-sync.h b/lib/gs-plugin-loader-sync.h
index 493effd0e..55ddb3e05 100644
--- a/lib/gs-plugin-loader-sync.h
+++ b/lib/gs-plugin-loader-sync.h
@@ -37,5 +37,10 @@ GsApp                *gs_plugin_loader_app_create            (GsPluginLoader 
*plugin_loader,
 GsApp          *gs_plugin_loader_get_system_app        (GsPluginLoader *plugin_loader,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_loader_setup                 (GsPluginLoader *plugin_loader,
+                                                        const gchar * const *allowlist,
+                                                        const gchar * const *blocklist,
+                                                        GCancellable    *cancellable,
+                                                        GError         **error);
 
 G_END_DECLS
diff --git a/lib/gs-plugin-loader.c b/lib/gs-plugin-loader.c
index 0fbe8d16c..e361e4fa3 100644
--- a/lib/gs-plugin-loader.c
+++ b/lib/gs-plugin-loader.c
@@ -2270,36 +2270,46 @@ gs_plugin_loader_find_plugins (const gchar *path, GError **error)
 }
 
 typedef struct {
-       GsPluginLoader *plugin_loader;  /* (unowned) */
-       GMainContext *context;  /* (owned) */
        guint n_pending;
 #ifdef HAVE_SYSPROF
        gint64 setup_begin_time_nsec;
+       gint64 plugins_begin_time_nsec;
 #endif
 } SetupData;
 
+static void
+setup_data_free (SetupData *data)
+{
+       g_free (data);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (SetupData, setup_data_free)
+
 static void plugin_setup_cb (GObject      *source_object,
                              GAsyncResult *result,
                              gpointer      user_data);
+static void finish_setup_op (GTask *task);
 
 /**
- * gs_plugin_loader_setup:
+ * gs_plugin_loader_setup_async:
  * @plugin_loader: a #GsPluginLoader
  * @allowlist: list of plugin names, or %NULL
  * @blocklist: list of plugin names, or %NULL
  * @cancellable: A #GCancellable, or %NULL
- * @error: A #GError, or %NULL
+ * @callback: callback to indicate completion of the asynchronous operation
+ * @user_data: data to pass to @callback
  *
  * Sets up the plugin loader ready for use.
  *
- * Returns: %TRUE for success
+ * Since: 42
  */
-gboolean
-gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
-                       gchar **allowlist,
-                       gchar **blocklist,
-                       GCancellable *cancellable,
-                       GError **error)
+void
+gs_plugin_loader_setup_async (GsPluginLoader      *plugin_loader,
+                              const gchar * const *allowlist,
+                              const gchar * const *blocklist,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
 {
        const gchar *plugin_name;
        gboolean changes;
@@ -2309,13 +2319,19 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
        guint dep_loop_check = 0;
        guint i;
        guint j;
-       SetupData setup_data;
+       SetupData *setup_data;
+       g_autoptr(SetupData) setup_data_owned = NULL;
        g_autoptr(GsPluginLoaderHelper) helper = NULL;
        g_autoptr(GsPluginJob) plugin_job = NULL;
+       g_autoptr(GTask) task = NULL;
+       g_autoptr(GError) local_error = NULL;
 #ifdef HAVE_SYSPROF
        gint64 begin_time_nsec G_GNUC_UNUSED = SYSPROF_CAPTURE_CURRENT_TIME;
 #endif
 
+       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+       g_task_set_source_tag (task, gs_plugin_loader_setup_async);
+
        /* use the default, but this requires a 'make install' */
        if (plugin_loader->locations->len == 0) {
                g_autofree gchar *filename = NULL;
@@ -2331,9 +2347,12 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
                monitor = g_file_monitor_directory (plugin_dir,
                                                    G_FILE_MONITOR_NONE,
                                                    cancellable,
-                                                   error);
-               if (monitor == NULL)
-                       return FALSE;
+                                                   &local_error);
+               if (monitor == NULL) {
+                       g_task_return_error (task, g_steal_pointer (&local_error));
+                       return;
+               }
+
                g_signal_connect (monitor, "changed",
                                  G_CALLBACK (gs_plugin_loader_plugin_dir_changed_cb), plugin_loader);
                g_ptr_array_add (plugin_loader->file_monitors, monitor);
@@ -2346,9 +2365,12 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
 
                /* search in the plugin directory for plugins */
                g_debug ("searching for plugins in %s", location);
-               fns = gs_plugin_loader_find_plugins (location, error);
-               if (fns == NULL)
-                       return FALSE;
+               fns = gs_plugin_loader_find_plugins (location, &local_error);
+               if (fns == NULL) {
+                       g_task_return_error (task, g_steal_pointer (&local_error));
+                       return;
+               }
+
                for (j = 0; j < fns->len; j++) {
                        const gchar *fn = g_ptr_array_index (fns, j);
                        gs_plugin_loader_open_plugin (plugin_loader, fn);
@@ -2436,11 +2458,11 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
 
                /* check we're not stuck */
                if (dep_loop_check++ > 100) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_PLUGIN_DEPSOLVE_FAILED,
-                                    "got stuck in dep loop");
-                       return FALSE;
+                       g_task_return_new_error (task,
+                                                GS_PLUGIN_ERROR,
+                                                GS_PLUGIN_ERROR_PLUGIN_DEPSOLVE_FAILED,
+                                                "got stuck in dep loop");
+                       return;
                }
        } while (changes);
 
@@ -2497,104 +2519,134 @@ gs_plugin_loader_setup (GsPluginLoader *plugin_loader,
 
                /* check we're not stuck */
                if (dep_loop_check++ > 100) {
-                       g_set_error (error,
-                                    GS_PLUGIN_ERROR,
-                                    GS_PLUGIN_ERROR_PLUGIN_DEPSOLVE_FAILED,
-                                    "got stuck in priority loop");
-                       return FALSE;
+                       g_task_return_new_error (task,
+                                                GS_PLUGIN_ERROR,
+                                                GS_PLUGIN_ERROR_PLUGIN_DEPSOLVE_FAILED,
+                                                "got stuck in priority loop");
+                       return;
                }
        } while (changes);
 
        /* run setup */
-       setup_data.plugin_loader = plugin_loader;
-       setup_data.n_pending = 1;  /* incremented until all operations have been started */
-       setup_data.context = g_main_context_new ();
+       setup_data = setup_data_owned = g_new0 (SetupData, 1);
+       setup_data->n_pending = 1;  /* incremented until all operations have been started */
 #ifdef HAVE_SYSPROF
-       setup_data.setup_begin_time_nsec = SYSPROF_CAPTURE_CURRENT_TIME;
+       setup_data->setup_begin_time_nsec = begin_time_nsec;
+       setup_data->plugins_begin_time_nsec = SYSPROF_CAPTURE_CURRENT_TIME;
 #endif
 
-       g_main_context_push_thread_default (setup_data.context);
+       g_task_set_task_data (task, g_steal_pointer (&setup_data_owned), (GDestroyNotify) setup_data_free);
 
-       for (guint i = 0; i < plugin_loader->plugins->len; i++) {
-               GsPlugin *plugin = GS_PLUGIN (plugin_loader->plugins->pdata[i]);
+       for (i = 0; i < plugin_loader->plugins->len; i++) {
+               plugin = GS_PLUGIN (plugin_loader->plugins->pdata[i]);
 
                if (!gs_plugin_get_enabled (plugin))
                        continue;
 
                if (GS_PLUGIN_GET_CLASS (plugin)->setup_async != NULL) {
+                       setup_data->n_pending++;
                        GS_PLUGIN_GET_CLASS (plugin)->setup_async (plugin, cancellable,
-                                                                  plugin_setup_cb, &setup_data);
-                       setup_data.n_pending++;
+                                                                  plugin_setup_cb, g_object_ref (task));
                }
        }
 
-       /* Wait for setup to complete in all plugins.
-        * Nested iteration of the main context is not generally good practice,
-        * but we expect gs_plugin_loader_setup() to only ever be executed early
-        * in the process’ lifetime, so it’s probably OK. This could be
-        * refactored in future. */
-       setup_data.n_pending--;
+       finish_setup_op (task);
+}
 
-       while (setup_data.n_pending > 0)
-               g_main_context_iteration (setup_data.context, TRUE);
+static void
+plugin_setup_cb (GObject      *source_object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+       GsPlugin *plugin = GS_PLUGIN (source_object);
+       g_autoptr(GTask) task = g_steal_pointer (&user_data);
+       GsPluginLoader *plugin_loader = g_task_get_source_object (task);
+       SetupData *data = g_task_get_task_data (task);
+       g_autoptr(GError) local_error = NULL;
 
-       g_main_context_pop_thread_default (setup_data.context);
-       g_clear_pointer (&setup_data.context, g_main_context_unref);
+       g_assert (GS_PLUGIN_GET_CLASS (plugin)->setup_finish != NULL);
 
-       /* now we can load the install-queue */
-       if (!load_install_queue (plugin_loader, error))
-               return FALSE;
+       if (!GS_PLUGIN_GET_CLASS (plugin)->setup_finish (plugin, result, &local_error)) {
+               g_debug ("disabling %s as setup failed: %s",
+                        gs_plugin_get_name (plugin),
+                        local_error->message);
+               gs_plugin_set_enabled (plugin, FALSE);
+       }
 
 #ifdef HAVE_SYSPROF
        if (plugin_loader->sysprof_writer != NULL) {
                sysprof_capture_writer_add_mark (plugin_loader->sysprof_writer,
-                                                begin_time_nsec,
+                                                data->plugins_begin_time_nsec,
                                                 sched_getcpu (),
                                                 getpid (),
-                                                SYSPROF_CAPTURE_CURRENT_TIME - begin_time_nsec,
+                                                SYSPROF_CAPTURE_CURRENT_TIME - data->plugins_begin_time_nsec,
                                                 "gnome-software",
-                                                "setup",
+                                                "setup-plugin",
                                                 NULL);
        }
 #endif  /* HAVE_SYSPROF */
 
-       return TRUE;
+       /* Indicate this plugin has finished setting up. */
+       finish_setup_op (task);
 }
 
 static void
-plugin_setup_cb (GObject      *source_object,
-                 GAsyncResult *result,
-                 gpointer      user_data)
+finish_setup_op (GTask *task)
 {
-       GsPlugin *plugin = GS_PLUGIN (source_object);
-       SetupData *data = user_data;
+       SetupData *data = g_task_get_task_data (task);
+       GsPluginLoader *plugin_loader = g_task_get_source_object (task);
        g_autoptr(GError) local_error = NULL;
 
-       g_assert (GS_PLUGIN_GET_CLASS (plugin)->setup_finish != NULL);
+       g_assert (data->n_pending > 0);
+       data->n_pending--;
 
-       if (!GS_PLUGIN_GET_CLASS (plugin)->setup_finish (plugin, result, &local_error)) {
-               g_debug ("disabling %s as setup failed: %s",
-                        gs_plugin_get_name (plugin),
-                        local_error->message);
-               gs_plugin_set_enabled (plugin, FALSE);
+       if (data->n_pending > 0)
+               return;
+
+       /* now we can load the install-queue */
+       if (!load_install_queue (plugin_loader, &local_error)) {
+               g_task_return_error (task, g_steal_pointer (&local_error));
+               return;
        }
 
 #ifdef HAVE_SYSPROF
-       if (data->plugin_loader->sysprof_writer != NULL) {
-               sysprof_capture_writer_add_mark (data->plugin_loader->sysprof_writer,
+       if (plugin_loader->sysprof_writer != NULL) {
+               sysprof_capture_writer_add_mark (plugin_loader->sysprof_writer,
                                                 data->setup_begin_time_nsec,
                                                 sched_getcpu (),
                                                 getpid (),
                                                 SYSPROF_CAPTURE_CURRENT_TIME - data->setup_begin_time_nsec,
                                                 "gnome-software",
-                                                "setup-plugin",
+                                                "setup",
                                                 NULL);
        }
 #endif  /* HAVE_SYSPROF */
 
-       /* Indicate this plugin has finished setting up. */
-       data->n_pending--;
-       g_main_context_wakeup (data->context);
+       g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * gs_plugin_loader_setup_finish:
+ * @plugin_loader: a #GsPluginLoader
+ * @result: result of the asynchronous operation
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finish an asynchronous setup operation started with
+ * gs_plugin_loader_setup_async().
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ * Since: 42
+ */
+gboolean
+gs_plugin_loader_setup_finish (GsPluginLoader  *plugin_loader,
+                               GAsyncResult    *result,
+                               GError         **error)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
+       g_return_val_if_fail (g_task_is_valid (result, plugin_loader), FALSE);
+       g_return_val_if_fail (g_async_result_is_tagged (result, gs_plugin_loader_setup_async), FALSE);
+
+       return g_task_propagate_boolean (G_TASK (result), error);
 }
 
 void
diff --git a/lib/gs-plugin-loader.h b/lib/gs-plugin-loader.h
index 6a956d7d5..e7edeeecf 100644
--- a/lib/gs-plugin-loader.h
+++ b/lib/gs-plugin-loader.h
@@ -45,10 +45,14 @@ void                 gs_plugin_loader_job_get_categories_async (GsPluginLoader 
*plugin_loader,
 GPtrArray      *gs_plugin_loader_job_get_categories_finish (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
-gboolean        gs_plugin_loader_setup                 (GsPluginLoader *plugin_loader,
-                                                        gchar          **allowlist,
-                                                        gchar          **blocklist,
+void            gs_plugin_loader_setup_async           (GsPluginLoader *plugin_loader,
+                                                        const gchar * const *allowlist,
+                                                        const gchar * const *blocklist,
                                                         GCancellable   *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer        user_data);
+gboolean        gs_plugin_loader_setup_finish          (GsPluginLoader *plugin_loader,
+                                                        GAsyncResult   *result,
                                                         GError         **error);
 
 void            gs_plugin_loader_shutdown              (GsPluginLoader *plugin_loader,
diff --git a/lib/gs-test.c b/lib/gs-test.c
index 1dbd7b8f4..2a2e1077b 100644
--- a/lib/gs-test.c
+++ b/lib/gs-test.c
@@ -8,6 +8,7 @@
 
 #include <stdlib.h>
 
+#include "gs-plugin-loader-sync.h"
 #include "gs-test.h"
 
 /**
@@ -126,7 +127,8 @@ gs_test_reinitialise_plugin_loader (GsPluginLoader      *plugin_loader,
        /* remove any events */
        gs_plugin_loader_remove_events (plugin_loader);
 
-       /* Start all the plugins setting up again in parallel. */
+       /* Start all the plugins setting up again in parallel. Use the blocking
+        * sync version of the function, just for the tests. */
        gs_plugin_loader_setup (plugin_loader, allowlist, blocklist, NULL, &local_error);
        g_assert_no_error (local_error);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]