From b593bf334a7aada890d8ec4ce358c9f431b605ff Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Mon, 3 Dec 2018 13:09:47 +0100 Subject: [PATCH 1/4] shell-global: Make saving of persistent state asynchronous This is an expensive operation that is best avoided in the main loop. Given the call doesn't care much about returning error or status, it can just be made async within. Every operation on a given file will be destructive wrt previous operations on the same file, so we just cancel any pending operation on it before batching the current one. Closes: https://gitlab.gnome.org/GNOME/gnome-shell/issues/815 --- src/shell-global.c | 85 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/src/shell-global.c b/src/shell-global.c index 961fd3a70..8f5418677 100644 --- a/src/shell-global.c +++ b/src/shell-global.c @@ -88,6 +88,8 @@ struct _ShellGlobal { /* For sound notifications */ ca_context *sound_context; + GHashTable *save_ops; + gboolean has_modal; gboolean frame_timestamps; gboolean frame_finish_timestamp; @@ -331,6 +333,10 @@ shell_global_init (ShellGlobal *global) NULL); g_strfreev (search_path); + + global->save_ops = g_hash_table_new_full (g_file_hash, + (GEqualFunc) g_file_equal, + g_object_unref, g_object_unref); } static void @@ -350,6 +356,8 @@ shell_global_finalize (GObject *object) g_free (global->imagedir); g_free (global->userdatadir); + g_hash_table_unref (global->save_ops); + G_OBJECT_CLASS(shell_global_parent_class)->finalize (object); } @@ -1775,20 +1783,77 @@ shell_global_get_session_mode (ShellGlobal *global) } static void -save_variant (GFile *dir, - const char *property_name, - GVariant *variant) +delete_variant_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + ShellGlobal *global = user_data; + GError *error = NULL; + + if (!g_file_delete_finish (G_FILE (object), result, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_warning ("Could not delete runtime/persistent state file: %s\n", + error->message); + } + + g_error_free (error); + } + + g_hash_table_remove (global->save_ops, object); +} + +static void +replace_variant_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + ShellGlobal *global = user_data; + GError *error = NULL; + + if (!g_file_replace_contents_finish (G_FILE (object), result, NULL, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + g_warning ("Could not replace runtime/persistent state file: %s\n", + error->message); + } + + g_error_free (error); + } + + g_hash_table_remove (global->save_ops, object); +} + +static void +save_variant (ShellGlobal *global, + GFile *dir, + const char *property_name, + GVariant *variant) { GFile *path = g_file_get_child (dir, property_name); + GCancellable *cancellable; + + cancellable = g_hash_table_lookup (global->save_ops, path); + g_cancellable_cancel (cancellable); + + cancellable = g_cancellable_new (); + g_hash_table_insert (global->save_ops, g_object_ref (path), cancellable); if (variant == NULL || g_variant_get_data (variant) == NULL) - (void) g_file_delete (path, NULL, NULL); + { + g_file_delete_async (path, G_PRIORITY_DEFAULT, cancellable, + delete_variant_cb, global); + } else { - gsize size = g_variant_get_size (variant); - g_file_replace_contents (path, g_variant_get_data (variant), size, - NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, - NULL, NULL, NULL); + g_file_replace_contents_async (path, + g_variant_get_data (variant), + g_variant_get_size (variant), + NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + cancellable, replace_variant_cb, global); } g_object_unref (path); @@ -1842,7 +1907,7 @@ shell_global_set_runtime_state (ShellGlobal *global, const char *property_name, GVariant *variant) { - save_variant (global->runtime_state_path, property_name, variant); + save_variant (global, global->runtime_state_path, property_name, variant); } /** @@ -1877,7 +1942,7 @@ shell_global_set_persistent_state (ShellGlobal *global, const char *property_name, GVariant *variant) { - save_variant (global->userdatadir_path, property_name, variant); + save_variant (global, global->userdatadir_path, property_name, variant); } /** -- 2.23.0 From 6e898656365c18ad48349b7e3586e2d10b9ead9a Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Mon, 3 Dec 2018 13:18:19 +0100 Subject: [PATCH 2/4] appDisplay: Reduce g_app_info_get_all() calls Whenever the AllView needs (re)populating, we used to do one general g_app_info_get_all() to get all GAppInfo, plus one per app folder in order to check the ones that fall within that category. This calls results in a fair amount of I/O blocking the main loop. In order to ease this, keep the GAppInfo list around in AllView, and make the AppFolders use it when figuring out the contained apps. Since reloading the AllView results in AppFolders regenerated from scratch, the app info list is ensured to be up-to-date for any later change within the AppFolder (eg. through the GSettings key changing). As the list was already filtered in the first place, we can also remove the try{}catch() in AppFolder in order to discard desktop files with invalid encoding. Related: https://gitlab.gnome.org/GNOME/gnome-shell/issues/832 --- js/ui/appDisplay.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 070057c69..115eb8f92 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -498,15 +498,21 @@ var AllView = new Lang.Class({ }); }, + getAppInfos() { + return this._appInfoList; + }, + _loadApps() { - let apps = Gio.AppInfo.get_all().filter(appInfo => { + this._appInfoList = Gio.AppInfo.get_all().filter(appInfo => { try { let id = appInfo.get_id(); // catch invalid file encodings } catch(e) { return false; } return appInfo.should_show(); - }).map(app => app.get_id()); + }); + + let apps = this._appInfoList.map(app => app.get_id()); let appSys = Shell.AppSystem.get_default(); @@ -1329,15 +1335,13 @@ var FolderIcon = new Lang.Class({ folderApps.forEach(addAppId); let folderCategories = this._folder.get_strv('categories'); - Gio.AppInfo.get_all().forEach(appInfo => { + let appInfos = this._parentView.getAppInfos(); + appInfos.forEach(appInfo => { let appCategories = _getCategories(appInfo); if (!_listsIntersect(folderCategories, appCategories)) return; - try { - addAppId(appInfo.get_id()); // catch invalid file encodings - } catch(e) { - } + addAppId(appInfo.get_id()); }); this.actor.visible = this.view.getAllItems().length > 0; -- 2.23.0 From cf823de60bff23872d20dfbb05ee4c51e3ebe9a3 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Dec 2018 13:13:42 +0100 Subject: [PATCH 3/4] shell-app-system: Cache GAppInfos around This was called here just to end up emitting ::installed-changed, which would trigger other g_app_info_get_all() calls. Cache it here so it may be reused later on. --- src/shell-app-system.c | 30 +++++++++++++++++++++++++----- src/shell-app-system.h | 2 ++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/shell-app-system.c b/src/shell-app-system.c index 8edf757a9..58e0185d1 100644 --- a/src/shell-app-system.c +++ b/src/shell-app-system.c @@ -50,6 +50,7 @@ struct _ShellAppSystemPrivate { GHashTable *running_apps; GHashTable *id_to_app; GHashTable *startup_wm_class_to_id; + GList *installed_apps; }; static void shell_app_system_finalize (GObject *object); @@ -82,12 +83,14 @@ static void scan_startup_wm_class_to_id (ShellAppSystem *self) { ShellAppSystemPrivate *priv = self->priv; - GList *apps, *l; + GList *l; g_hash_table_remove_all (priv->startup_wm_class_to_id); - apps = g_app_info_get_all (); - for (l = apps; l != NULL; l = l->next) + g_list_free_full (priv->installed_apps, g_object_unref); + priv->installed_apps = g_app_info_get_all (); + + for (l = priv->installed_apps; l != NULL; l = l->next) { GAppInfo *info = l->data; const char *startup_wm_class, *id, *old_id; @@ -105,8 +108,6 @@ scan_startup_wm_class_to_id (ShellAppSystem *self) g_hash_table_insert (priv->startup_wm_class_to_id, g_strdup (startup_wm_class), g_strdup (id)); } - - g_list_free_full (apps, g_object_unref); } static gboolean @@ -198,6 +199,7 @@ shell_app_system_finalize (GObject *object) g_hash_table_destroy (priv->running_apps); g_hash_table_destroy (priv->id_to_app); g_hash_table_destroy (priv->startup_wm_class_to_id); + g_list_free_full (priv->installed_apps, g_object_unref); G_OBJECT_CLASS (shell_app_system_parent_class)->finalize (object); } @@ -437,3 +439,21 @@ shell_app_system_search (const char *search_string) return results; } + +/** + * shell_app_system_get_installed: + * @self: the #ShellAppSystem + * + * Returns all installed apps, as a list of #GAppInfo + * + * Returns: (transfer none) (element-type GAppInfo): a list of #GAppInfo + * describing all known applications. This memory is owned by the + * #ShellAppSystem and should not be freed. + **/ +GList * +shell_app_system_get_installed (ShellAppSystem *self) +{ + ShellAppSystemPrivate *priv = self->priv; + + return priv->installed_apps; +} diff --git a/src/shell-app-system.h b/src/shell-app-system.h index 445671f5b..8719dbcf2 100644 --- a/src/shell-app-system.h +++ b/src/shell-app-system.h @@ -27,4 +27,6 @@ ShellApp *shell_app_system_lookup_desktop_wmclass (ShellAppSystem *s GSList *shell_app_system_get_running (ShellAppSystem *self); char ***shell_app_system_search (const char *search_string); +GList *shell_app_system_get_installed (ShellAppSystem *self); + #endif /* __SHELL_APP_SYSTEM_H__ */ -- 2.23.0 From e294793e3d5525897e1c293cd9fd77ccf8983ca7 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Wed, 19 Dec 2018 13:26:54 +0100 Subject: [PATCH 4/4] appDisplay: Use GAppInfo list from ShellAppSystem It is now cached there, so the number of g_app_info_get_all() calls is reduced to one per change. --- js/ui/appDisplay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 115eb8f92..233deeb92 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -503,7 +503,7 @@ var AllView = new Lang.Class({ }, _loadApps() { - this._appInfoList = Gio.AppInfo.get_all().filter(appInfo => { + this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => { try { let id = appInfo.get_id(); // catch invalid file encodings } catch(e) { -- 2.23.0