Blame SOURCES/as-store-locking.patch

f39813
From e5f73b24c4950ec8e51f6970ad658d604baf6d24 Mon Sep 17 00:00:00 2001
f39813
From: Kalev Lember <klember@redhat.com>
f39813
Date: Fri, 14 Dec 2018 11:40:46 +0100
f39813
Subject: [PATCH 1/3] store: Add thread safe dup() functions for multithreaded
f39813
 clients
f39813
f39813
gnome-software spawns a new worker thread for each of its plugin
f39813
operations and occasionally this leads to issues where one thread is
f39813
reading from AsStore, and another one is changing it. There's also file
f39813
monitor callbacks in AsStore that run in a yet another thread and can
f39813
update internal data structures as well.
f39813
f39813
This leads to a situation where functions returning pointers to internal
f39813
data structures can't guarantee the data structure lifetime because
f39813
another thread might be changing it in the background.
f39813
f39813
To fix this, this commit adds new as_store_dup_apps() and
f39813
as_store_dup_apps_by_id_merge() functions that return a deep copy of the
f39813
internal structure, and updates existing "transfer container"
f39813
as_store_get_apps_by_id() to return a deep copy, instead just returning
f39813
a reffed container.
f39813
---
f39813
 libappstream-glib/as-self-test.c |  4 +-
f39813
 libappstream-glib/as-store.c     | 65 +++++++++++++++++++++++++++++++-
f39813
 libappstream-glib/as-store.h     |  3 ++
f39813
 3 files changed, 69 insertions(+), 3 deletions(-)
f39813
f39813
diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c
f39813
index d90af48..7fd7d75 100644
f39813
--- a/libappstream-glib/as-self-test.c
f39813
+++ b/libappstream-glib/as-self-test.c
f39813
@@ -3477,12 +3477,12 @@ as_test_store_flatpak_func (void)
f39813
 	AsApp *app;
f39813
 	AsFormat *format;
f39813
 	GError *error = NULL;
f39813
-	GPtrArray *apps;
f39813
 	gboolean ret;
f39813
 	g_autofree gchar *filename = NULL;
f39813
 	g_autofree gchar *filename_root = NULL;
f39813
 	g_autoptr(AsStore) store = NULL;
f39813
 	g_autoptr(GFile) file = NULL;
f39813
+	g_autoptr(GPtrArray) apps = NULL;
f39813
 
f39813
 	/* make throws us under a bus, yet again */
f39813
 	g_setenv ("AS_SELF_TEST_PREFIX_DELIM", "_", TRUE);
f39813
@@ -3503,7 +3503,7 @@ as_test_store_flatpak_func (void)
f39813
 	/* test extraction of symlink data */
f39813
 	g_assert_cmpstr (as_store_get_origin (store), ==, "flatpak");
f39813
 	g_assert_cmpint (as_store_get_size (store), ==, 1);
f39813
-	apps = as_store_get_apps (store);
f39813
+	apps = as_store_dup_apps (store);
f39813
 	g_assert_cmpint (apps->len, ==, 1);
f39813
 	app = g_ptr_array_index (apps, 0);
f39813
 	g_assert_cmpstr (as_app_get_id (app), ==, "flatpak:test.desktop");
f39813
diff --git a/libappstream-glib/as-store.c b/libappstream-glib/as-store.c
f39813
index d2075eb..59a7f1c 100644
f39813
--- a/libappstream-glib/as-store.c
f39813
+++ b/libappstream-glib/as-store.c
f39813
@@ -1,6 +1,7 @@
f39813
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
f39813
  *
f39813
  * Copyright (C) 2013-2016 Richard Hughes <richard@hughsie.com>
f39813
+ * Copyright (C) 2015-2018 Kalev Lember <klember@redhat.com>
f39813
  *
f39813
  * Licensed under the GNU Lesser General Public License Version 2.1
f39813
  *
f39813
@@ -264,6 +265,22 @@ as_store_changed_uninhibit_cb (void *v)
f39813
 
f39813
 #define _cleanup_uninhibit_ __attribute__ ((cleanup(as_store_changed_uninhibit_cb)))
f39813
 
f39813
+static GPtrArray *
f39813
+_dup_app_array (GPtrArray *array)
f39813
+{
f39813
+	GPtrArray *array_dup;
f39813
+
f39813
+	g_return_val_if_fail (array != NULL, NULL);
f39813
+
f39813
+	array_dup = g_ptr_array_new_full (array->len, (GDestroyNotify) g_object_unref);
f39813
+	for (guint i = 0; i < array->len; i++) {
f39813
+		AsApp *app = g_ptr_array_index (array, i);
f39813
+		g_ptr_array_add (array_dup, g_object_ref (app));
f39813
+	}
f39813
+
f39813
+	return array_dup;
f39813
+}
f39813
+
f39813
 /**
f39813
  * as_store_add_filter:
f39813
  * @store: a #AsStore instance.
f39813
@@ -344,6 +361,27 @@ as_store_get_apps (AsStore *store)
f39813
 	return priv->array;
f39813
 }
f39813
 
f39813
+/**
f39813
+ * as_store_dup_apps:
f39813
+ * @store: a #AsStore instance.
f39813
+ *
f39813
+ * Gets an array of all the valid applications in the store.
f39813
+ *
f39813
+ * Returns: (element-type AsApp) (transfer container): an array
f39813
+ *
f39813
+ * Since: 0.7.15
f39813
+ **/
f39813
+GPtrArray *
f39813
+as_store_dup_apps (AsStore *store)
f39813
+{
f39813
+
f39813
+	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+
f39813
+	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
+
f39813
+	return _dup_app_array (priv->array);
f39813
+}
f39813
+
f39813
 /**
f39813
  * as_store_remove_all:
f39813
  * @store: a #AsStore instance.
f39813
@@ -471,7 +509,7 @@ as_store_get_apps_by_id (AsStore *store, const gchar *id)
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 	apps = g_hash_table_lookup (priv->hash_id, id);
f39813
 	if (apps != NULL)
f39813
-		return g_ptr_array_ref (apps);
f39813
+		return _dup_app_array (apps);
f39813
 	return g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
 }
f39813
 
f39813
@@ -494,6 +532,31 @@ as_store_get_apps_by_id_merge (AsStore *store, const gchar *id)
f39813
 	return g_hash_table_lookup (priv->hash_merge_id, id);
f39813
 }
f39813
 
f39813
+/**
f39813
+ * as_store_dup_apps_by_id_merge:
f39813
+ * @store: a #AsStore instance.
f39813
+ * @id: the application full ID.
f39813
+ *
f39813
+ * Gets an array of all the merge applications that match a specific ID.
f39813
+ *
f39813
+ * Returns: (element-type AsApp) (transfer container): an array
f39813
+ *
f39813
+ * Since: 0.7.15
f39813
+ **/
f39813
+GPtrArray *
f39813
+as_store_dup_apps_by_id_merge (AsStore *store, const gchar *id)
f39813
+{
f39813
+	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	GPtrArray *apps;
f39813
+
f39813
+	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
+
f39813
+	apps = g_hash_table_lookup (priv->hash_merge_id, id);
f39813
+	if (apps != NULL)
f39813
+		return _dup_app_array (apps);
f39813
+	return g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
+}
f39813
+
f39813
 /**
f39813
  * as_store_add_metadata_index:
f39813
  * @store: a #AsStore instance.
f39813
diff --git a/libappstream-glib/as-store.h b/libappstream-glib/as-store.h
f39813
index 3d8c749..b15aca4 100644
f39813
--- a/libappstream-glib/as-store.h
f39813
+++ b/libappstream-glib/as-store.h
f39813
@@ -209,10 +209,13 @@ void		 as_store_set_search_match	(AsStore	*store,
f39813
 guint16		 as_store_get_search_match	(AsStore	*store);
f39813
 void		 as_store_remove_all		(AsStore	*store);
f39813
 GPtrArray	*as_store_get_apps		(AsStore	*store);
f39813
+GPtrArray	*as_store_dup_apps		(AsStore	*store);
f39813
 GPtrArray	*as_store_get_apps_by_id	(AsStore	*store,
f39813
 						 const gchar	*id);
f39813
 GPtrArray	*as_store_get_apps_by_id_merge	(AsStore	*store,
f39813
 						 const gchar	*id);
f39813
+GPtrArray	*as_store_dup_apps_by_id_merge	(AsStore	*store,
f39813
+						 const gchar	*id);
f39813
 GPtrArray	*as_store_get_apps_by_metadata	(AsStore	*store,
f39813
 						 const gchar	*key,
f39813
 						 const gchar	*value);
f39813
-- 
f39813
2.19.1
f39813
f39813
f39813
From 0f79c943948abb62a54d0e54b1135ff89ebf5ab4 Mon Sep 17 00:00:00 2001
f39813
From: Kalev Lember <klember@redhat.com>
f39813
Date: Fri, 14 Dec 2018 11:52:19 +0100
f39813
Subject: [PATCH 2/3] store: Return deep copy in as_store_get_apps_by_metadata
f39813
f39813
This is strictly not necessary for making gnome-software's AsStore use
f39813
thread safe as gnome-software doesn't actually use this function, but
f39813
let's fix it anyway while I'm at it.
f39813
f39813
This also updates tests that test the performance of the function as the
f39813
deep copying makes it noticably slower (but that's fine because nothing
f39813
actually uses it and it's still reasonably fast).
f39813
---
f39813
 libappstream-glib/as-self-test.c | 2 +-
f39813
 libappstream-glib/as-store.c     | 2 +-
f39813
 2 files changed, 2 insertions(+), 2 deletions(-)
f39813
f39813
diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c
f39813
index 7fd7d75..981b790 100644
f39813
--- a/libappstream-glib/as-self-test.c
f39813
+++ b/libappstream-glib/as-self-test.c
f39813
@@ -4730,7 +4730,7 @@ static void
f39813
 as_test_store_metadata_index_func (void)
f39813
 {
f39813
 	GPtrArray *apps;
f39813
-	const guint repeats = 10000;
f39813
+	const guint repeats = 500;
f39813
 	guint i;
f39813
 	g_autoptr(AsStore) store = NULL;
f39813
 	g_autoptr(GTimer) timer = NULL;
f39813
diff --git a/libappstream-glib/as-store.c b/libappstream-glib/as-store.c
f39813
index 59a7f1c..018bdb5 100644
f39813
--- a/libappstream-glib/as-store.c
f39813
+++ b/libappstream-glib/as-store.c
f39813
@@ -473,7 +473,7 @@ as_store_get_apps_by_metadata (AsStore *store,
f39813
 		}
f39813
 		apps = g_hash_table_lookup (index, value);
f39813
 		if (apps != NULL)
f39813
-			return g_ptr_array_ref (apps);
f39813
+			return _dup_app_array (apps);
f39813
 		return g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
 	}
f39813
 
f39813
-- 
f39813
2.19.1
f39813
f39813
f39813
From 24913fb9ac74f671fdba7547049bcaa0899704ee Mon Sep 17 00:00:00 2001
f39813
From: Kalev Lember <klember@redhat.com>
f39813
Date: Fri, 14 Dec 2018 12:03:37 +0100
f39813
Subject: [PATCH 3/3] store: Add internal locking
f39813
f39813
This adds fine-grained locking around priv->array and various
f39813
priv-stored hash table use. There are more thread safe issues in
f39813
AsStore, but this should take care of those that cause frequent
f39813
gnome-software crashes in F29.
f39813
f39813
This may regress the perfomance a bit because it changes a few places to
f39813
keep a deep copy to simplify locking, but I haven't observed any visible
f39813
performance regressions in gnome-software. Also, gnome-software is in
f39813
the process of switching to libxmlb anyway so it shouldn't matter a
f39813
whole lot in the long run.
f39813
f39813
This patch takes care to make sure that locking is fine grained enough
f39813
so that we can be sure it doesn't lead into deadlocks, and also makes
f39813
sure that we never invoke any callbacks (signals) while locked to
f39813
prevent deadlocks when a client app calls back into AsStore code.
f39813
---
f39813
 libappstream-glib/as-store.c | 191 ++++++++++++++++++++++++++++-------
f39813
 1 file changed, 155 insertions(+), 36 deletions(-)
f39813
f39813
diff --git a/libappstream-glib/as-store.c b/libappstream-glib/as-store.c
f39813
index 018bdb5..0308585 100644
f39813
--- a/libappstream-glib/as-store.c
f39813
+++ b/libappstream-glib/as-store.c
f39813
@@ -68,6 +68,7 @@ typedef struct
f39813
 	GHashTable		*hash_merge_id;	/* of GPtrArray of AsApp{id} */
f39813
 	GHashTable		*hash_unique_id;	/* of AsApp{unique_id} */
f39813
 	GHashTable		*hash_pkgname;	/* of AsApp{pkgname} */
f39813
+	GMutex			 mutex;
f39813
 	AsMonitor		*monitor;
f39813
 	GHashTable		*metadata_indexes;	/* GHashTable{key} */
f39813
 	GHashTable		*appinfo_dirs;	/* GHashTable{path:AsStorePathData} */
f39813
@@ -140,6 +141,7 @@ as_store_finalize (GObject *object)
f39813
 	g_hash_table_unref (priv->metadata_indexes);
f39813
 	g_hash_table_unref (priv->appinfo_dirs);
f39813
 	g_hash_table_unref (priv->search_blacklist);
f39813
+	g_mutex_clear (&priv->mutex);
f39813
 
f39813
 	G_OBJECT_CLASS (as_store_parent_class)->finalize (object);
f39813
 }
f39813
@@ -339,7 +341,11 @@ guint
f39813
 as_store_get_size (AsStore *store)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
+
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), 0);
f39813
+
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
 	return priv->array->len;
f39813
 }
f39813
 
f39813
@@ -357,7 +363,11 @@ GPtrArray *
f39813
 as_store_get_apps (AsStore *store)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
+
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
+
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
 	return priv->array;
f39813
 }
f39813
 
f39813
@@ -376,9 +386,11 @@ as_store_dup_apps (AsStore *store)
f39813
 {
f39813
 
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
 	return _dup_app_array (priv->array);
f39813
 }
f39813
 
f39813
@@ -394,7 +406,11 @@ void
f39813
 as_store_remove_all (AsStore *store)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
+
f39813
 	g_return_if_fail (AS_IS_STORE (store));
f39813
+
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
 	g_ptr_array_set_size (priv->array, 0);
f39813
 	g_hash_table_remove_all (priv->hash_id);
f39813
 	g_hash_table_remove_all (priv->hash_merge_id);
f39813
@@ -461,9 +477,12 @@ as_store_get_apps_by_metadata (AsStore *store,
f39813
 	GHashTable *index;
f39813
 	GPtrArray *apps;
f39813
 	guint i;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	/* do we have this indexed? */
f39813
 	index = g_hash_table_lookup (priv->metadata_indexes, key);
f39813
 	if (index != NULL) {
f39813
@@ -506,7 +525,12 @@ as_store_get_apps_by_id (AsStore *store, const gchar *id)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	GPtrArray *apps;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
+
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
+
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	apps = g_hash_table_lookup (priv->hash_id, id);
f39813
 	if (apps != NULL)
f39813
 		return _dup_app_array (apps);
f39813
@@ -528,7 +552,11 @@ GPtrArray *
f39813
 as_store_get_apps_by_id_merge (AsStore *store, const gchar *id)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
+
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
+
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
 	return g_hash_table_lookup (priv->hash_merge_id, id);
f39813
 }
f39813
 
f39813
@@ -548,9 +576,12 @@ as_store_dup_apps_by_id_merge (AsStore *store, const gchar *id)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	GPtrArray *apps;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	apps = g_hash_table_lookup (priv->hash_merge_id, id);
f39813
 	if (apps != NULL)
f39813
 		return _dup_app_array (apps);
f39813
@@ -572,6 +603,12 @@ as_store_dup_apps_by_id_merge (AsStore *store, const gchar *id)
f39813
 void
f39813
 as_store_add_metadata_index (AsStore *store, const gchar *key)
f39813
 {
f39813
+	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
+
f39813
+	g_return_if_fail (AS_IS_STORE (store));
f39813
+
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
 	as_store_regen_metadata_index_key (store, key);
f39813
 }
f39813
 
f39813
@@ -594,7 +631,11 @@ as_store_get_app_by_id (AsStore *store, const gchar *id)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	GPtrArray *apps;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
+
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
+
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
 	apps = g_hash_table_lookup (priv->hash_id, id);
f39813
 	if (apps == NULL)
f39813
 		return NULL;
f39813
@@ -641,6 +682,7 @@ as_store_get_app_by_app (AsStore *store, AsApp *app)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	guint i;
f39813
+	g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
f39813
 
f39813
 	for (i = 0; i < priv->array->len; i++) {
f39813
 		AsApp *app_tmp = g_ptr_array_index (priv->array, i);
f39813
@@ -675,8 +717,10 @@ as_store_get_app_by_unique_id (AsStore *store,
f39813
 	g_return_val_if_fail (unique_id != NULL, NULL);
f39813
 
f39813
 	/* no globs */
f39813
-	if ((search_flags & AS_STORE_SEARCH_FLAG_USE_WILDCARDS) == 0)
f39813
+	if ((search_flags & AS_STORE_SEARCH_FLAG_USE_WILDCARDS) == 0) {
f39813
+		g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
f39813
 		return g_hash_table_lookup (priv->hash_unique_id, unique_id);
f39813
+	}
f39813
 
f39813
 	/* create virtual app using scope/system/origin/kind/id/branch */
f39813
 	app_tmp = _as_app_new_from_unique_id (unique_id);
f39813
@@ -706,11 +750,14 @@ as_store_get_app_by_provide (AsStore *store, AsProvideKind kind, const gchar *va
f39813
 	guint i;
f39813
 	guint j;
f39813
 	GPtrArray *provides;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 	g_return_val_if_fail (kind != AS_PROVIDE_KIND_UNKNOWN, NULL);
f39813
 	g_return_val_if_fail (value != NULL, NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	/* find an application that provides something */
f39813
 	for (i = 0; i < priv->array->len; i++) {
f39813
 		app = g_ptr_array_index (priv->array, i);
f39813
@@ -744,11 +791,14 @@ AsApp *
f39813
 as_store_get_app_by_launchable (AsStore *store, AsLaunchableKind kind, const gchar *value)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 	g_return_val_if_fail (kind != AS_LAUNCHABLE_KIND_UNKNOWN, NULL);
f39813
 	g_return_val_if_fail (value != NULL, NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	for (guint i = 0; i < priv->array->len; i++) {
f39813
 		AsApp *app = g_ptr_array_index (priv->array, i);
f39813
 		GPtrArray *launchables = as_app_get_launchables (app);
f39813
@@ -782,11 +832,14 @@ as_store_get_apps_by_provide (AsStore *store, AsProvideKind kind, const gchar *v
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	GPtrArray *apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 	g_return_val_if_fail (kind != AS_PROVIDE_KIND_UNKNOWN, NULL);
f39813
 	g_return_val_if_fail (value != NULL, NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	/* find an application that provides something */
f39813
 	for (guint i = 0; i < priv->array->len; i++) {
f39813
 		AsApp *app = g_ptr_array_index (priv->array, i);
f39813
@@ -820,10 +873,13 @@ as_store_get_app_by_id_ignore_prefix (AsStore *store, const gchar *id)
f39813
 	AsApp *app;
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	guint i;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 	g_return_val_if_fail (id != NULL, NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	/* find an application that provides something */
f39813
 	for (i = 0; i < priv->array->len; i++) {
f39813
 		app = g_ptr_array_index (priv->array, i);
f39813
@@ -1017,9 +1073,12 @@ as_store_get_app_by_pkgname (AsStore *store, const gchar *pkgname)
f39813
 	AsApp *app;
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	guint i;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	/* in most cases, we can use the cache */
f39813
 	app = g_hash_table_lookup (priv->hash_pkgname, pkgname);
f39813
 	if (app != NULL)
f39813
@@ -1059,6 +1118,7 @@ as_store_get_app_by_pkgnames (AsStore *store, gchar **pkgnames)
f39813
 	g_return_val_if_fail (pkgnames != NULL, NULL);
f39813
 
f39813
 	for (i = 0; pkgnames[i] != NULL; i++) {
f39813
+		g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
f39813
 		app = g_hash_table_lookup (priv->hash_pkgname, pkgnames[i]);
f39813
 		if (app != NULL)
f39813
 			return app;
f39813
@@ -1087,6 +1147,7 @@ as_store_remove_app (AsStore *store, AsApp *app)
f39813
 	g_signal_emit (store, signals[SIGNAL_APP_REMOVED], 0, app);
f39813
 
f39813
 	/* only remove this specific unique app */
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	apps = g_hash_table_lookup (priv->hash_id, as_app_get_id (app));
f39813
 	if (apps != NULL) {
f39813
 		g_ptr_array_remove (apps, app);
f39813
@@ -1100,6 +1161,7 @@ as_store_remove_app (AsStore *store, AsApp *app)
f39813
 	g_hash_table_remove (priv->hash_unique_id, as_app_get_unique_id (app));
f39813
 	g_ptr_array_remove (priv->array, app);
f39813
 	g_hash_table_remove_all (priv->metadata_indexes);
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 
f39813
 	/* removed */
f39813
 	as_store_perhaps_emit_changed (store, "remove-app");
f39813
@@ -1117,27 +1179,37 @@ as_store_remove_app (AsStore *store, AsApp *app)
f39813
 void
f39813
 as_store_remove_app_by_id (AsStore *store, const gchar *id)
f39813
 {
f39813
-	AsApp *app;
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
-	guint i;
f39813
+	g_autoptr(GPtrArray) apps = NULL;
f39813
 
f39813
 	g_return_if_fail (AS_IS_STORE (store));
f39813
 
f39813
-	if (!g_hash_table_remove (priv->hash_id, id))
f39813
+	g_mutex_lock (&priv->mutex);
f39813
+	if (!g_hash_table_remove (priv->hash_id, id)) {
f39813
+		g_mutex_unlock (&priv->mutex);
f39813
 		return;
f39813
-	for (i = 0; i < priv->array->len; i++) {
f39813
-		app = g_ptr_array_index (priv->array, i);
f39813
+	}
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
+
f39813
+	apps = as_store_dup_apps (store);
f39813
+	for (guint i = 0; i < apps->len; i++) {
f39813
+		AsApp *app = g_ptr_array_index (apps, i);
f39813
+
f39813
 		if (g_strcmp0 (id, as_app_get_id (app)) != 0)
f39813
 			continue;
f39813
 
f39813
 		/* emit before removal */
f39813
 		g_signal_emit (store, signals[SIGNAL_APP_REMOVED], 0, app);
f39813
 
f39813
+		g_mutex_lock (&priv->mutex);
f39813
 		g_ptr_array_remove (priv->array, app);
f39813
 		g_hash_table_remove (priv->hash_unique_id,
f39813
 				     as_app_get_unique_id (app));
f39813
+		g_mutex_unlock (&priv->mutex);
f39813
 	}
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	g_hash_table_remove_all (priv->metadata_indexes);
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 
f39813
 	/* removed */
f39813
 	as_store_perhaps_emit_changed (store, "remove-app-by-id");
f39813
@@ -1242,7 +1314,9 @@ as_store_add_app (AsStore *store, AsApp *app)
f39813
 	if (as_app_has_quirk (app, AS_APP_QUIRK_MATCH_ANY_PREFIX)) {
f39813
 		guint64 flags = AS_APP_SUBSUME_FLAG_MERGE;
f39813
 		AsAppMergeKind merge_kind = as_app_get_merge_kind (app);
f39813
+		g_autoptr(GPtrArray) apps_changed = NULL;
f39813
 
f39813
+		g_mutex_lock (&priv->mutex);
f39813
 		apps = g_hash_table_lookup (priv->hash_merge_id, id);
f39813
 		if (apps == NULL) {
f39813
 			apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
@@ -1254,11 +1328,16 @@ as_store_add_app (AsStore *store, AsApp *app)
f39813
 			 as_app_merge_kind_to_string (merge_kind),
f39813
 			 as_app_get_unique_id (app));
f39813
 		g_ptr_array_add (apps, g_object_ref (app));
f39813
+		g_mutex_unlock (&priv->mutex);
f39813
 
f39813
 		/* apply to existing components */
f39813
 		flags |= AS_APP_SUBSUME_FLAG_NO_OVERWRITE;
f39813
 		if (merge_kind == AS_APP_MERGE_KIND_REPLACE)
f39813
 			flags |= AS_APP_SUBSUME_FLAG_REPLACE;
f39813
+
f39813
+		apps_changed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
+
f39813
+		g_mutex_lock (&priv->mutex);
f39813
 		for (i = 0; i < priv->array->len; i++) {
f39813
 			AsApp *app_tmp = g_ptr_array_index (priv->array, i);
f39813
 			if (g_strcmp0 (as_app_get_id (app_tmp), id) != 0)
f39813
@@ -1267,7 +1346,11 @@ as_store_add_app (AsStore *store, AsApp *app)
f39813
 				 as_app_merge_kind_to_string (merge_kind),
f39813
 				 id, as_app_get_unique_id (app_tmp));
f39813
 			as_app_subsume_full (app_tmp, app, flags);
f39813
-
f39813
+			g_ptr_array_add (apps_changed, g_object_ref (app_tmp));
f39813
+		}
f39813
+		g_mutex_unlock (&priv->mutex);
f39813
+		for (i = 0; i < apps_changed->len; i++) {
f39813
+			AsApp *app_tmp = g_ptr_array_index (apps_changed, i);
f39813
 			/* emit after changes have been made */
f39813
 			g_signal_emit (store, signals[SIGNAL_APP_CHANGED],
f39813
 				       0, app_tmp);
f39813
@@ -1276,6 +1359,7 @@ as_store_add_app (AsStore *store, AsApp *app)
f39813
 	}
f39813
 
f39813
 	/* is there any merge components to add to this app */
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	apps = g_hash_table_lookup (priv->hash_merge_id, id);
f39813
 	if (apps != NULL) {
f39813
 		for (i = 0; i < apps->len; i++) {
f39813
@@ -1292,14 +1376,17 @@ as_store_add_app (AsStore *store, AsApp *app)
f39813
 			as_app_subsume_full (app, app_tmp, flags);
f39813
 		}
f39813
 	}
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 
f39813
 	/* find the item */
f39813
 	if (priv->add_flags & AS_STORE_ADD_FLAG_USE_UNIQUE_ID) {
f39813
 		item = as_store_get_app_by_app (store, app);
f39813
 	} else {
f39813
+		g_mutex_lock (&priv->mutex);
f39813
 		apps = g_hash_table_lookup (priv->hash_id, id);
f39813
 		if (apps != NULL && apps->len > 0)
f39813
 			item = g_ptr_array_index (apps, 0);
f39813
+		g_mutex_unlock (&priv->mutex);
f39813
 	}
f39813
 	if (item != NULL) {
f39813
 		AsFormat *app_format = as_app_get_format_default (app);
f39813
@@ -1427,6 +1514,7 @@ as_store_add_app (AsStore *store, AsApp *app)
f39813
 	}
f39813
 
f39813
 	/* create hash of id:[apps] if required */
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	apps = g_hash_table_lookup (priv->hash_id, id);
f39813
 	if (apps == NULL) {
f39813
 		apps = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
@@ -1448,6 +1536,7 @@ as_store_add_app (AsStore *store, AsApp *app)
f39813
 				     g_strdup (pkgname),
f39813
 				     g_object_ref (app));
f39813
 	}
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 
f39813
 	/* add helper objects */
f39813
 	as_app_set_stemmer (app, priv->stemmer);
f39813
@@ -1495,15 +1584,16 @@ as_store_match_addons (AsStore *store)
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	guint i;
f39813
 	g_autoptr(AsProfileTask) ptask = NULL;
f39813
+	g_autoptr(GPtrArray) apps = NULL;
f39813
 
f39813
 	/* profile */
f39813
 	ptask = as_profile_start_literal (priv->profile, "AsStore:match-addons");
f39813
 	g_assert (ptask != NULL);
f39813
-	for (i = 0; i < priv->array->len; i++) {
f39813
-		AsApp *app = g_ptr_array_index (priv->array, i);
f39813
-		if (as_app_get_kind (app) != AS_APP_KIND_ADDON)
f39813
-			continue;
f39813
-		as_store_match_addons_app (store, app);
f39813
+	apps = as_store_dup_apps (store);
f39813
+	for (i = 0; i < apps->len; i++) {
f39813
+		AsApp *app = g_ptr_array_index (apps, i);
f39813
+		if (as_app_get_kind (app) == AS_APP_KIND_ADDON)
f39813
+			as_store_match_addons_app (store, app);
f39813
 	}
f39813
 }
f39813
 
f39813
@@ -1858,15 +1948,15 @@ static void
f39813
 as_store_remove_by_source_file (AsStore *store, const gchar *filename)
f39813
 {
f39813
 	AsApp *app;
f39813
-	GPtrArray *apps;
f39813
 	guint i;
f39813
 	const gchar *tmp;
f39813
 	_cleanup_uninhibit_ guint32 *tok = NULL;
f39813
+	g_autoptr(GPtrArray) apps = NULL;
f39813
 	g_autoptr(GPtrArray) ids = NULL;
f39813
 
f39813
 	/* find any applications in the store with this source file */
f39813
 	ids = g_ptr_array_new_with_free_func (g_free);
f39813
-	apps = as_store_get_apps (store);
f39813
+	apps = as_store_dup_apps (store);
f39813
 	for (i = 0; i < apps->len; i++) {
f39813
 		AsFormat *format;
f39813
 		app = g_ptr_array_index (apps, i);
f39813
@@ -1909,16 +1999,21 @@ as_store_watch_source_added (AsStore *store, const gchar *filename)
f39813
 	if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
f39813
 		return;
f39813
 
f39813
-	/* we helpfully saved this */
f39813
 	dirname = g_path_get_dirname (filename);
f39813
 	g_debug ("parsing new file %s from %s", filename, dirname);
f39813
+
f39813
+	/* we helpfully saved this */
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	path_data = g_hash_table_lookup (priv->appinfo_dirs, filename);
f39813
 	if (path_data == NULL)
f39813
 		path_data = g_hash_table_lookup (priv->appinfo_dirs, dirname);
f39813
 	if (path_data == NULL) {
f39813
 		g_warning ("no path data for %s", dirname);
f39813
+		g_mutex_unlock (&priv->mutex);
f39813
 		return;
f39813
 	}
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
+
f39813
 	file = g_file_new_for_path (filename);
f39813
 	/* Do not watch the file for changes: we're already watching its
f39813
 	 * parent directory */
f39813
@@ -2010,7 +2105,9 @@ as_store_add_path_data (AsStore *store,
f39813
 	}
f39813
 
f39813
 	/* check not already exists */
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	path_data = g_hash_table_lookup (priv->appinfo_dirs, path);
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 	if (path_data != NULL) {
f39813
 		if (path_data->scope != scope ||
f39813
 		    g_strcmp0 (path_data->arch, arch) != 0) {
f39813
@@ -2033,7 +2130,9 @@ as_store_add_path_data (AsStore *store,
f39813
 	path_data = g_slice_new0 (AsStorePathData);
f39813
 	path_data->scope = scope;
f39813
 	path_data->arch = g_strdup (arch);
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	g_hash_table_insert (priv->appinfo_dirs, g_strdup (path), path_data);
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 }
f39813
 
f39813
 static gboolean
f39813
@@ -2287,6 +2386,7 @@ as_store_check_apps_for_veto (AsStore *store)
f39813
 	guint i;
f39813
 	AsApp *app;
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->mutex);
f39813
 
f39813
 	/* add any vetos */
f39813
 	for (i = 0; i < priv->array->len; i++) {
f39813
@@ -2306,27 +2406,28 @@ as_store_check_apps_for_veto (AsStore *store)
f39813
 void
f39813
 as_store_remove_apps_with_veto (AsStore *store)
f39813
 {
f39813
-	guint i;
f39813
-	AsApp *app;
f39813
-	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	_cleanup_uninhibit_ guint32 *tok = NULL;
f39813
+	g_autoptr(GPtrArray) apps = NULL;
f39813
+	g_autoptr(GPtrArray) apps_remove = NULL;
f39813
 
f39813
 	g_return_if_fail (AS_IS_STORE (store));
f39813
 
f39813
 	/* don't shortcut the list as we have to use as_store_remove_app()
f39813
 	 * rather than just removing from the GPtrArray */
f39813
 	tok = as_store_changed_inhibit (store);
f39813
-	do {
f39813
-		for (i = 0; i < priv->array->len; i++) {
f39813
-			app = g_ptr_array_index (priv->array, i);
f39813
-			if (as_app_get_vetos (app)->len > 0) {
f39813
-				g_debug ("removing %s as vetoed",
f39813
-					 as_app_get_id (app));
f39813
-				as_store_remove_app (store, app);
f39813
-				break;
f39813
-			}
f39813
-		}
f39813
-	} while (i < priv->array->len);
f39813
+	apps = as_store_dup_apps (store);
f39813
+	apps_remove = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
f39813
+	for (guint i = 0; i < apps->len; i++) {
f39813
+		AsApp *app = g_ptr_array_index (apps, i);
f39813
+		if (as_app_get_vetos (app)->len > 0)
f39813
+			g_ptr_array_add (apps_remove, g_object_ref (app));
f39813
+	}
f39813
+	for (guint i = 0; i < apps_remove->len; i++) {
f39813
+		AsApp *app = g_ptr_array_index (apps_remove, i);
f39813
+		g_debug ("removing %s as vetoed",
f39813
+		         as_app_get_id (app));
f39813
+		as_store_remove_app (store, app);
f39813
+	}
f39813
 	as_store_changed_uninhibit (&tok;;
f39813
 	as_store_perhaps_emit_changed (store, "remove-apps-with-veto");
f39813
 }
f39813
@@ -2379,22 +2480,28 @@ as_store_to_xml (AsStore *store, guint32 flags)
f39813
 		as_node_add_attribute (node_apps, "version", version);
f39813
 	}
f39813
 
f39813
-	/* sort by ID */
f39813
-	g_ptr_array_sort (priv->array, as_store_apps_sort_cb);
f39813
-
f39813
 	/* output is trusted, so include update_contact */
f39813
 	if (g_getenv ("APPSTREAM_GLIB_OUTPUT_TRUSTED") != NULL)
f39813
 		output_trusted = TRUE;
f39813
 
f39813
-	/* add applications */
f39813
 	ctx = as_node_context_new ();
f39813
 	as_node_context_set_version (ctx, priv->api_version);
f39813
 	as_node_context_set_output (ctx, AS_FORMAT_KIND_APPSTREAM);
f39813
 	as_node_context_set_output_trusted (ctx, output_trusted);
f39813
+
f39813
+	g_mutex_lock (&priv->mutex);
f39813
+
f39813
+	/* sort by ID */
f39813
+	g_ptr_array_sort (priv->array, as_store_apps_sort_cb);
f39813
+
f39813
+	/* add applications */
f39813
 	for (i = 0; i < priv->array->len; i++) {
f39813
 		app = g_ptr_array_index (priv->array, i);
f39813
 		as_app_node_insert (app, node_apps, ctx);
f39813
 	}
f39813
+
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
+
f39813
 	xml = as_node_to_xml (node_root, flags);
f39813
 	as_node_unref (node_root);
f39813
 	return xml;
f39813
@@ -2418,9 +2525,12 @@ as_store_convert_icons (AsStore *store, AsIconKind kind, GError **error)
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
 	AsApp *app;
f39813
 	guint i;
f39813
+	g_autoptr(GMutexLocker) locker = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), FALSE);
f39813
 
f39813
+	locker = g_mutex_locker_new (&priv->mutex);
f39813
+
f39813
 	/* convert application icons */
f39813
 	for (i = 0; i < priv->array->len; i++) {
f39813
 		app = g_ptr_array_index (priv->array, i);
f39813
@@ -2818,8 +2928,12 @@ as_store_load_app_info (AsStore *store,
f39813
 	_cleanup_uninhibit_ guint32 *tok = NULL;
f39813
 
f39813
 	/* Don't add the same dir twice, we're monitoring it for changes anyway */
f39813
-	if (g_hash_table_contains (priv->appinfo_dirs, path))
f39813
+	g_mutex_lock (&priv->mutex);
f39813
+	if (g_hash_table_contains (priv->appinfo_dirs, path)) {
f39813
+		g_mutex_unlock (&priv->mutex);
f39813
 		return TRUE;
f39813
+	}
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 
f39813
 	/* emit once when finished */
f39813
 	tok = as_store_changed_inhibit (store);
f39813
@@ -3329,10 +3443,12 @@ as_store_load_search_cache (AsStore *store)
f39813
 	pool = g_thread_pool_new (as_store_load_search_cache_cb,
f39813
 				  store, 4, TRUE, NULL);
f39813
 	g_assert (pool != NULL);
f39813
+	g_mutex_lock (&priv->mutex);
f39813
 	for (i = 0; i < priv->array->len; i++) {
f39813
 		AsApp *app = g_ptr_array_index (priv->array, i);
f39813
 		g_thread_pool_push (pool, app, NULL);
f39813
 	}
f39813
+	g_mutex_unlock (&priv->mutex);
f39813
 	g_thread_pool_free (pool, FALSE, TRUE);
f39813
 }
f39813
 
f39813
@@ -3510,6 +3626,7 @@ as_store_validate (AsStore *store, guint32 flags, GError **error)
f39813
 	GPtrArray *probs;
f39813
 	guint i;
f39813
 	g_autoptr(GHashTable) hash_names = NULL;
f39813
+	g_autoptr(GPtrArray) apps = NULL;
f39813
 
f39813
 	g_return_val_if_fail (AS_IS_STORE (store), NULL);
f39813
 
f39813
@@ -3546,14 +3663,15 @@ as_store_validate (AsStore *store, guint32 flags, GError **error)
f39813
 					    g_free, (GDestroyNotify) g_object_unref);
f39813
 
f39813
 	/* check each application */
f39813
-	for (i = 0; i < priv->array->len; i++) {
f39813
+	apps = as_store_dup_apps (store);
f39813
+	for (i = 0; i < apps->len; i++) {
f39813
 		AsApp *app_tmp;
f39813
 		AsProblem *prob;
f39813
 		guint j;
f39813
 		g_autofree gchar *app_key = NULL;
f39813
 		g_autoptr(GPtrArray) probs_app = NULL;
f39813
 
f39813
-		app = g_ptr_array_index (priv->array, i);
f39813
+		app = g_ptr_array_index (apps, i);
f39813
 		if (priv->api_version < 0.3) {
f39813
 			if (as_app_get_source_pkgname (app) != NULL) {
f39813
 				as_store_validate_add (probs,
f39813
@@ -3804,6 +3922,7 @@ static void
f39813
 as_store_init (AsStore *store)
f39813
 {
f39813
 	AsStorePrivate *priv = GET_PRIVATE (store);
f39813
+	g_mutex_init (&priv->mutex);
f39813
 	priv->profile = as_profile_new ();
f39813
 	priv->stemmer = as_stemmer_new ();
f39813
 	priv->api_version = AS_API_VERSION_NEWEST;
f39813
-- 
f39813
2.19.1
f39813