[gnome-shell/wip/chergert/3-28-perf-fixes: 8/11] ShellAppCache: add cache to help keep I/O off main thread
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/chergert/3-28-perf-fixes: 8/11] ShellAppCache: add cache to help keep I/O off main thread
- Date: Thu, 27 Feb 2020 07:48:45 +0000 (UTC)
commit e5460b5f4712e614f9c32456841c240df2bb38c9
Author: Christian Hergert <chergert redhat com>
Date: Wed Feb 26 23:08:14 2020 -0800
ShellAppCache: add cache to help keep I/O off main thread
A number of things can result in doing I/O on the main thread such as
loading GAppInfo or folder translations. The ShellAppCache provides a
layer of abstraction around those things so that we can keep that work
off the main thread by delaying a short while and processing the work
synchronously off-main-thread.
The results can then be marshalled back to the main thread for use by
the rest of the system via the ShellAppCache::changed() signal.
src/meson.build | 2 +
src/shell-app-cache.c | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/shell-app-cache.h | 19 +++
3 files changed, 405 insertions(+)
---
diff --git a/src/meson.build b/src/meson.build
index 1d655f4071..53f2a0f63f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -103,6 +103,7 @@ libshell_menu_dep = declare_dependency(link_with: libshell_menu)
libshell_public_headers = [
'shell-app.h',
+ 'shell-app-cache.h',
'shell-app-system.h',
'shell-app-usage.h',
'shell-embedded-window.h',
@@ -138,6 +139,7 @@ libshell_private_headers = [
libshell_sources = [
'gnome-shell-plugin.c',
'shell-app.c',
+ 'shell-app-cache.c',
'shell-app-system.c',
'shell-app-usage.c',
'shell-embedded-window.c',
diff --git a/src/shell-app-cache.c b/src/shell-app-cache.c
new file mode 100644
index 0000000000..b154319589
--- /dev/null
+++ b/src/shell-app-cache.c
@@ -0,0 +1,384 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "shell-app-cache.h"
+
+#define DEFAULT_TIMEOUT_SECONDS 3
+
+struct _ShellAppCache
+{
+ GObject parent_instance;
+ GMutex mutex;
+ GAppInfoMonitor *monitor;
+ GHashTable *folders;
+ GCancellable *cancellable;
+ GList *app_infos;
+ guint queued_update;
+};
+
+typedef struct
+{
+ GList *app_infos;
+ GHashTable *folders;
+} CacheState;
+
+G_DEFINE_TYPE (ShellAppCache, shell_app_cache, G_TYPE_OBJECT)
+
+enum {
+ CHANGED,
+ N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+cache_state_free (CacheState *state)
+{
+ g_clear_pointer (&state->folders, g_hash_table_unref);
+ g_list_free_full (state->app_infos, g_object_unref);
+ g_slice_free (CacheState, state);
+}
+
+static CacheState *
+cache_state_new (void)
+{
+ CacheState *state;
+
+ state = g_slice_new0 (CacheState);
+ state->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ return g_steal_pointer (&state);
+}
+
+/**
+ * shell_app_cache_get_default:
+ *
+ * Gets the default #ShellAppCache.
+ *
+ * Returns: (transfer none): a #ShellAppCache
+ */
+ShellAppCache *
+shell_app_cache_get_default (void)
+{
+ static ShellAppCache *instance;
+
+ if (instance == NULL)
+ {
+ instance = g_object_new (SHELL_TYPE_APP_CACHE, NULL);
+ g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance);
+ }
+
+ return instance;
+}
+
+static void
+load_folder (GHashTable *folders,
+ const char *path)
+{
+ g_autoptr(GDir) dir = NULL;
+ const char *name;
+
+ g_assert (folders != NULL);
+ g_assert (path != NULL);
+
+ dir = g_dir_open (path, 0, NULL);
+ if (dir == NULL)
+ return;
+
+ while ((name = g_dir_read_name (dir)))
+ {
+ g_autofree gchar *filename = NULL;
+ g_autoptr(GKeyFile) keyfile = NULL;
+
+ /* First added wins */
+ if (g_hash_table_contains (folders, name))
+ continue;
+
+ filename = g_build_filename (path, name, NULL);
+ keyfile = g_key_file_new ();
+
+ if (g_key_file_load_from_file (keyfile, filename, G_KEY_FILE_NONE, NULL))
+ g_hash_table_insert (folders,
+ g_strdup (name),
+ g_key_file_get_locale_string (keyfile,
+ "Desktop Entry",
+ "Name",
+ NULL,
+ NULL));
+ }
+}
+
+static void
+load_folders (GHashTable *folders)
+{
+ const char * const *dirs;
+ g_autofree gchar *userdir = NULL;
+ guint i;
+
+ g_assert (folders != NULL);
+
+ userdir = g_build_filename (g_get_user_data_dir (), "desktop-directories", NULL);
+ load_folder (folders, userdir);
+
+ dirs = g_get_system_data_dirs ();
+ for (i = 0; dirs[i] != NULL; i++)
+ {
+ g_autofree gchar *sysdir = g_build_filename (dirs[i], "desktop-directories", NULL);
+ load_folder (folders, sysdir);
+ }
+}
+
+static void
+shell_app_cache_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ CacheState *state;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (SHELL_IS_APP_CACHE (source_object));
+
+ state = cache_state_new ();
+ state->app_infos = g_app_info_get_all ();
+ load_folders (state->folders);
+
+ g_task_return_pointer (task, state, (GDestroyNotify)cache_state_free);
+}
+
+static void
+apply_update_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ShellAppCache *cache = (ShellAppCache *)object;
+ g_autoptr(GError) error = NULL;
+ CacheState *state;
+
+ g_assert (SHELL_IS_APP_CACHE (cache));
+ g_assert (G_IS_TASK (result));
+ g_assert (user_data == NULL);
+
+ state = g_task_propagate_pointer (G_TASK (result), &error);
+
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ g_mutex_lock (&cache->mutex);
+ g_list_free_full (cache->app_infos, g_object_unref);
+ cache->app_infos = g_steal_pointer (&state->app_infos);
+ g_clear_pointer (&cache->folders, g_hash_table_unref);
+ cache->folders = g_steal_pointer (&state->folders);
+ g_mutex_unlock (&cache->mutex);
+
+ g_signal_emit (cache, signals[CHANGED], 0);
+
+ cache_state_free (state);
+}
+
+static gboolean
+shell_app_cache_do_update (gpointer user_data)
+{
+ ShellAppCache *cache = user_data;
+ g_autoptr(GTask) task = NULL;
+
+ cache->queued_update = 0;
+
+ /* Reset the cancellable state so we don't race with
+ * two updates coming back overlapped and applying the
+ * information in the wrong order.
+ */
+ g_cancellable_cancel (cache->cancellable);
+ g_clear_object (&cache->cancellable);
+ cache->cancellable = g_cancellable_new ();
+
+ task = g_task_new (cache, cache->cancellable, apply_update_cb, NULL);
+ g_task_set_source_tag (task, shell_app_cache_do_update);
+ g_task_run_in_thread (task, shell_app_cache_worker);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+shell_app_cache_queue_update (ShellAppCache *self)
+{
+ g_assert (SHELL_IS_APP_CACHE (self));
+
+ if (self->queued_update != 0)
+ g_source_remove (self->queued_update);
+
+ self->queued_update = g_timeout_add_seconds (DEFAULT_TIMEOUT_SECONDS,
+ shell_app_cache_do_update,
+ self);
+}
+
+static void
+shell_app_cache_monitor_changed_cb (ShellAppCache *self,
+ GAppInfoMonitor *monitor)
+{
+ g_assert (SHELL_IS_APP_CACHE (self));
+ g_assert (G_IS_APP_INFO_MONITOR (monitor));
+
+ shell_app_cache_queue_update (self);
+}
+
+static void
+shell_app_cache_finalize (GObject *object)
+{
+ ShellAppCache *self = (ShellAppCache *)object;
+
+ g_clear_object (&self->monitor);
+
+ if (self->queued_update)
+ {
+ g_source_remove (self->queued_update);
+ self->queued_update = 0;
+ }
+
+ g_clear_pointer (&self->folders, g_hash_table_unref);
+ g_list_free_full (self->app_infos, g_object_unref);
+ g_mutex_clear (&self->mutex);
+
+ G_OBJECT_CLASS (shell_app_cache_parent_class)->finalize (object);
+}
+
+static void
+shell_app_cache_class_init (ShellAppCacheClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = shell_app_cache_finalize;
+
+ /**
+ * ShellAppCache::changed:
+ *
+ * The "changed" signal is emitted when the cache has updated
+ * information about installed applications.
+ */
+ signals [CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+shell_app_cache_init (ShellAppCache *self)
+{
+ g_mutex_init (&self->mutex);
+
+ self->folders = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ load_folders (self->folders);
+
+ self->monitor = g_app_info_monitor_get ();
+ g_signal_connect_object (self->monitor,
+ "changed",
+ G_CALLBACK (shell_app_cache_monitor_changed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ self->app_infos = g_app_info_get_all ();
+}
+
+/**
+ * shell_app_cache_get_all:
+ * @cache: (nullable): a #ShellAppCache or %NULL
+ *
+ * Like g_app_info_get_all() but always returns a
+ * cached set of application info so the caller can be
+ * sure that I/O will not happen on the current thread.
+ *
+ * Returns: (transfer full) (element-type GAppInfo):
+ * a newly allocated GList of references to GAppInfos.
+ */
+GList *
+shell_app_cache_get_all (ShellAppCache *cache)
+{
+ GList *ret;
+
+ if (cache == NULL)
+ cache = shell_app_cache_get_default ();
+
+ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
+
+ g_mutex_lock (&cache->mutex);
+ ret = g_list_copy_deep (cache->app_infos, (GCopyFunc)g_object_ref, NULL);
+ g_mutex_unlock (&cache->mutex);
+
+ return ret;
+}
+
+/**
+ * shell_app_cache_get_info:
+ * @cache: (nullable): a #ShellAppCache or %NULL
+ * @id: the application id
+ *
+ * A replacement for g_desktop_app_info_new() that will lookup the information
+ * from the cache instead of (re)loading from disk.
+ *
+ * Returns: (nullable) (transfer full): a #GDesktopAppInfo or %NULL
+ */
+GDesktopAppInfo *
+shell_app_cache_get_info (ShellAppCache *cache,
+ const char *id)
+{
+ GDesktopAppInfo *ret = NULL;
+ const GList *iter;
+
+ if (cache == NULL)
+ cache = shell_app_cache_get_default ();
+
+ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
+
+ g_mutex_lock (&cache->mutex);
+
+ for (iter = cache->app_infos; iter != NULL; iter = iter->next)
+ {
+ GAppInfo *info = iter->data;
+
+ if (g_strcmp0 (id, g_app_info_get_id (info)) == 0)
+ {
+ ret = g_object_ref (G_DESKTOP_APP_INFO (info));
+ break;
+ }
+ }
+
+ g_mutex_unlock (&cache->mutex);
+
+ return g_steal_pointer (&ret);
+}
+
+/**
+ * shell_app_cache_translate_folder:
+ * @cache: (nullable): a #ShellAppCache or %NULL
+ * @name: the folder name
+ *
+ * Gets the translated folder name for @name if any exists. Otherwise
+ * a copy of @name is returned.
+ *
+ * Returns: the translated string
+ */
+char *
+shell_app_cache_translate_folder (ShellAppCache *cache,
+ const char *name)
+{
+ char *ret;
+
+ if (cache == NULL)
+ cache = shell_app_cache_get_default ();
+
+ g_return_val_if_fail (SHELL_IS_APP_CACHE (cache), NULL);
+
+ if (name == NULL)
+ return NULL;
+
+ g_mutex_lock (&cache->mutex);
+ ret = g_strdup (g_hash_table_lookup (cache->folders, name));
+ g_mutex_unlock (&cache->mutex);
+
+ if (ret == NULL)
+ ret = g_strdup (name);
+
+ return g_steal_pointer (&ret);
+}
diff --git a/src/shell-app-cache.h b/src/shell-app-cache.h
new file mode 100644
index 0000000000..e9086f8731
--- /dev/null
+++ b/src/shell-app-cache.h
@@ -0,0 +1,19 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_APP_CACHE__
+#define __SHELL_APP_CACHE__
+
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+
+#define SHELL_TYPE_APP_CACHE (shell_app_cache_get_type())
+
+G_DECLARE_FINAL_TYPE (ShellAppCache, shell_app_cache, SHELL, APP_CACHE, GObject)
+
+ShellAppCache *shell_app_cache_get_default (void);
+GList *shell_app_cache_get_all (ShellAppCache *cache);
+GDesktopAppInfo *shell_app_cache_get_info (ShellAppCache *cache,
+ const char *id);
+char *shell_app_cache_translate_folder (ShellAppCache *cache,
+ const char *name);
+
+#endif /* __SHELL_APP_CACHE__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]