From 9109e6a456c61695b532d0902b992c121d675f5e Mon Sep 17 00:00:00 2001 From: Andrew Chadwick <a.t.chadwick@gmail.com> Date: Wed, 5 Apr 2023 23:48:30 +0000 Subject: [PATCH] ThunarFile cache: improve mutex usage MR !264 Fixes #1060 --- thunar/thunar-file.c | 189 ++++++++++++++++++++++++++++--------------- 1 file changed, 124 insertions(+), 65 deletions(-) diff --git a/thunar/thunar-file.c b/thunar/thunar-file.c index cd67cd7ff..aad198ce0 100644 --- a/thunar/thunar-file.c +++ b/thunar/thunar-file.c @@ -111,13 +111,16 @@ static void thunar_file_monitor (GFileMonitor GFileMonitorEvent event_type, gpointer user_data); static void thunar_file_watch_reconnect (ThunarFile *file); +static gboolean thunar_file_load_unguarded (ThunarFile *file, + GCancellable *cancellable, + GError **error); static gboolean thunar_file_load (ThunarFile *file, GCancellable *cancellable, GError **error); static gboolean thunar_file_is_readable (const ThunarFile *file); static gboolean thunar_file_same_filesystem (const ThunarFile *file_a, const ThunarFile *file_b); - +static ThunarFile *thunar_file_cache_lookup_unguarded (const GFile *file); G_LOCK_DEFINE_STATIC (file_cache_mutex); @@ -125,7 +128,6 @@ G_LOCK_DEFINE_STATIC (file_content_type_mutex); G_LOCK_DEFINE_STATIC (file_rename_mutex); - static ThunarUserManager *user_manager; static GHashTable *file_cache; static guint32 effective_user_id; @@ -1175,24 +1177,15 @@ thunar_file_get_async_finish (GObject *object, -/** - * thunar_file_load: - * @file : a #ThunarFile. - * @cancellable : a #GCancellable. - * @error : return location for errors or %NULL. +/* Static, no-mutex version of thunar_file_load(). * - * Loads all information about the file. As this is a possibly - * blocking call, it can be cancelled using @cancellable. - * - * If loading the file fails or the operation is cancelled, - * @error will be set. - * - * Return value: %TRUE on success, %FALSE on error or interruption. - **/ + * The calling function must execute G_LOCK(file_cache_mutex) before calling this + * function, and G_UNLOCK(file_cache_mutex) afterwards. */ + static gboolean -thunar_file_load (ThunarFile *file, - GCancellable *cancellable, - GError **error) +thunar_file_load_unguarded (ThunarFile *file, + GCancellable *cancellable, + GError **error) { GError *err = NULL; @@ -1202,10 +1195,8 @@ thunar_file_load (ThunarFile *file, _thunar_return_val_if_fail (G_IS_FILE (file->gfile), FALSE); /* remove the file from cache */ - G_LOCK (file_cache_mutex); if (g_hash_table_lookup (file_cache, file->gfile) != NULL) g_hash_table_remove (file_cache, file->gfile); - G_UNLOCK (file_cache_mutex); /* reset the file */ thunar_file_info_clear (file); @@ -1237,17 +1228,53 @@ thunar_file_load (ThunarFile *file, /* (re)insert the file into the cache */ if (file != NULL && file->kind != G_FILE_TYPE_UNKNOWN) { - G_LOCK (file_cache_mutex); g_hash_table_insert (file_cache, g_object_ref (file->gfile), weak_ref_new (G_OBJECT (file))); - G_UNLOCK (file_cache_mutex); } return TRUE; } +/** + * thunar_file_load: + * @file : a #ThunarFile. + * @cancellable : a #GCancellable. + * @error : return location for errors or %NULL. + * + * Loads all information about the file. As this is a possibly + * blocking call, it can be cancelled using @cancellable. + * + * If loading the file fails or the operation is cancelled, + * @error will be set. + * + * Return value: %TRUE on success, %FALSE on error or interruption. + **/ +static gboolean +thunar_file_load (ThunarFile *file, + GCancellable *cancellable, + GError **error) +{ + gboolean result = FALSE; + + _thunar_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); + _thunar_return_val_if_fail (error == NULL || *error == NULL, FALSE); + _thunar_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + _thunar_return_val_if_fail (G_IS_FILE (file->gfile), FALSE); + + /* Ensure that all lookups and inserts happen under the same mutex */ + G_LOCK (file_cache_mutex); + + result = thunar_file_load_unguarded (file, cancellable, error); + + G_UNLOCK (file_cache_mutex); + + return result; +} + + + /** * thunar_file_get: * @file : a #GFile. @@ -1266,16 +1293,21 @@ ThunarFile* thunar_file_get (GFile *gfile, GError **error) { - ThunarFile *file; + ThunarFile *file = NULL; + gboolean file_load_failed = FALSE; _thunar_return_val_if_fail (G_IS_FILE (gfile), NULL); + /* Both lookup and insert must happen in the same critical section + * because the insert is contigent upon the lookup */ + G_LOCK (file_cache_mutex); + /* check if we already have a cached version of that file */ - file = thunar_file_cache_lookup (gfile); + file = thunar_file_cache_lookup_unguarded (gfile); if (G_UNLIKELY (file != NULL)) { /* return the file, it already has an additional ref set - * in thunar_file_cache_lookup */ + * in thunar_file_cache_lookup_unguarded */ } else { @@ -1283,27 +1315,33 @@ thunar_file_get (GFile *gfile, file = g_object_new (THUNAR_TYPE_FILE, NULL); file->gfile = g_object_ref (gfile); - if (thunar_file_load (file, NULL, error)) + /* load and cache the file, using the current lock */ + if (thunar_file_load_unguarded (file, NULL, error)) { - /* setup lock until the file is inserted */ - G_LOCK (file_cache_mutex); - - /* insert the file into the cache */ - g_hash_table_insert (file_cache, - g_object_ref (file->gfile), - weak_ref_new (G_OBJECT (file))); - - /* done inserting in the cache */ - G_UNLOCK (file_cache_mutex); + /* just check that it's been cached, if appropriate */ + if ((file != NULL) && (file->kind != G_FILE_TYPE_UNKNOWN)) + _thunar_assert (g_hash_table_contains (file_cache, file->gfile) == TRUE); } else { - /* failed loading, destroy the file */ + /* failed loading, destroy the file (outside the lock) */ + file_load_failed = TRUE; + } + } + + /* Finished related activity on the cache */ + G_UNLOCK (file_cache_mutex); + + if (file_load_failed == TRUE) + { + if (file != NULL) + { + /* Calls thunar_file_finalize(), which itself requires the lock */ g_object_unref (file); - /* make sure we return NULL */ - file = NULL; } + + return NULL; } return file; @@ -1340,12 +1378,16 @@ thunar_file_get_with_info (GFile *gfile, _thunar_return_val_if_fail (G_IS_FILE (gfile), NULL); _thunar_return_val_if_fail (G_IS_FILE_INFO (info), NULL); + /* All contingent lookups and inserts must happen in the same critical section */ + G_LOCK (file_cache_mutex); + /* check if we already have a cached version of that file */ - file = thunar_file_cache_lookup (gfile); + file = thunar_file_cache_lookup_unguarded (gfile); + if (G_UNLIKELY (file != NULL)) { /* return the file, it already has an additional ref set - * in thunar_file_cache_lookup */ + * in thunar_file_cache_lookup_unguarded */ } else { @@ -1366,18 +1408,16 @@ thunar_file_get_with_info (GFile *gfile, if (not_mounted) FLAG_UNSET (file, THUNAR_FILE_FLAG_IS_MOUNTED); - /* setup lock until the file is inserted */ - G_LOCK (file_cache_mutex); - /* insert the file into the cache */ g_hash_table_insert (file_cache, g_object_ref (file->gfile), weak_ref_new (G_OBJECT (file))); - /* done inserting in the cache */ - G_UNLOCK (file_cache_mutex); } - + + /* done reading and writing the cache for this file instance */ + G_UNLOCK (file_cache_mutex); + if (recent_info != NULL) file->recent_info = g_object_ref (recent_info); @@ -4605,6 +4645,42 @@ thunar_file_same_filesystem (const ThunarFile *file_a, +/* Static, no-mutex version of thunar_file_cache_lookup(). + * + * The calling function must execute G_LOCK(file_cache_mutex) before calling this + * function, and G_UNLOCK(file_cache_mutex) afterwards. + * + * This is responsible for creating the file cache too. */ + +static ThunarFile * +thunar_file_cache_lookup_unguarded (const GFile *file) +{ + GWeakRef *ref; + ThunarFile *cached_file; + + _thunar_return_val_if_fail (G_IS_FILE (file), NULL); + + /* allocate the ThunarFile cache on-demand */ + if (G_UNLIKELY (file_cache == NULL)) + { + file_cache = g_hash_table_new_full (g_file_hash, + (GEqualFunc) g_file_equal, + (GDestroyNotify) g_object_unref, + (GDestroyNotify) weak_ref_free); + } + + ref = g_hash_table_lookup (file_cache, file); + + if (ref == NULL) + cached_file = NULL; + else + cached_file = g_weak_ref_get (ref); + + return cached_file; +} + + + /** * thunar_file_cache_lookup: * @file : a #GFile. @@ -4625,29 +4701,12 @@ thunar_file_same_filesystem (const ThunarFile *file_a, ThunarFile * thunar_file_cache_lookup (const GFile *file) { - GWeakRef *ref; ThunarFile *cached_file; _thunar_return_val_if_fail (G_IS_FILE (file), NULL); G_LOCK (file_cache_mutex); - - /* allocate the ThunarFile cache on-demand */ - if (G_UNLIKELY (file_cache == NULL)) - { - file_cache = g_hash_table_new_full (g_file_hash, - (GEqualFunc) g_file_equal, - (GDestroyNotify) g_object_unref, - (GDestroyNotify) weak_ref_free); - } - - ref = g_hash_table_lookup (file_cache, file); - - if (ref == NULL) - cached_file = NULL; - else - cached_file = g_weak_ref_get (ref); - + cached_file = thunar_file_cache_lookup_unguarded (file); G_UNLOCK (file_cache_mutex); return cached_file; -- GitLab