From: Alexander Larsson Date: Fri, 24 Jan 2020 15:22:26 +0000 (+0100) Subject: GtkIconTheme: Make the base GtkIconTheme threadsafe X-Git-Tag: archive/raspbian/4.4.1+ds1-2+rpi1^2~18^2~20^2~126^2~38 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=aef67bd53aaab7faaca3b980ef19af8ee101dd8e;p=gtk4.git GtkIconTheme: Make the base GtkIconTheme threadsafe This makes the core icon theme object threadsafe, however its not yet very useful because the IconInfo objects are not threadsafe. --- diff --git a/gtk/gtkicontheme.c b/gtk/gtkicontheme.c index e5abec19fc..63c247cdd3 100644 --- a/gtk/gtkicontheme.c +++ b/gtk/gtkicontheme.c @@ -104,6 +104,53 @@ * ]| */ +/* Threading: + * + * GtkIconTheme is partially threadsafe, construction and setup can + * only be done on the main thread (and this is not really fixable + * since it uses other things like GdkDisplay and GSettings and + * signals on those. However, once the icon theme is set up on the + * main thread we can pass it to a thread and do basic lookups on + * it. This will cause any parallel calls on the main thread (or any + * other thread) to block until its done, but most of the time + * lookups are fast. The only time its not fast is when we need + * to rescan the theme, but then it would be slow if we didn't block + * and did the rescan ourselves anyway. + * + * The threadsafe calls are marked in the docs. + * + * All private functions that take a GtkIconTheme (or one of its + * private data types (like IconThemeDir, UnthemedIcon, etc) arg are + * expected to be called with the icon theme lock held, unless the + * funcion has a _unlocked suffix. Any similar function that must be + * called on the main thread, will have a _mainthread suffix. + * + * So the rules for such functions are: + * + * * non-_unlocked function cannot call _unlocked functions. + * * _unlocked must lock before calling a non-_unlocked. + * * non-_mainthread cannot call _mainthread. + * * Public APIs must lock before calling a non-_unlocked private function + * * Public APIs that never call _mainthread and threadsafe. + * + * Additionally there is a global "info_cache" G_LOCK, which protects + * both the GtkIconTheme->info_cache and its reverse pointer + * GtkIconInfo->in_cache. This is sometimes taken with the + * theme lock held (from the theme side) and sometimes not (from the + * icon info side), but we never take another lock after taking it, so + * this is safe. + * + * Sometimes there are references to the icon theme that are weak that + * can call into the icon theme. For example, from the "theme-changed" + * signal. Since these don't own the theme they can run in parallel + * with some other thread wich is finalizing the theme. To avoid this + * all such references are done via the GtkIconThemeRef object which + * contains an NULL:able pointer to the theme and the main lock for + * that theme. Using this we can safely generate a ref for the theme + * if it still lives (or get NULL if it doesn't). + */ + + #define FALLBACK_ICON_THEME "hicolor" typedef enum @@ -137,6 +184,9 @@ typedef enum typedef struct _GtkIconInfoClass GtkIconInfoClass; typedef struct _GtkIconThemeClass GtkIconThemeClass; + +typedef struct _GtkIconThemeRef GtkIconThemeRef; + /** * GtkIconTheme: * @@ -151,8 +201,9 @@ typedef struct _GtkIconThemeClass GtkIconThemeClass; struct _GtkIconTheme { GObject parent_instance; + GtkIconThemeRef *ref; - GHashTable *info_cache; + GHashTable *info_cache; /* Protected by info_cache lock */ GtkIconInfo *lru_cache[LRU_CACHE_SIZE]; int lru_cache_next; @@ -176,6 +227,7 @@ struct _GtkIconTheme /* GdkDisplay for the icon theme (may be NULL) */ GdkDisplay *display; + GtkSettings *display_settings; /* time when we last stat:ed for theme changes */ glong last_stat_time; @@ -203,6 +255,10 @@ struct _GtkIconInfoClass GObjectClass parent_class; }; +/* This lock protects both IconTheme.info_cache and the dependent IconInfo.in_cache. + * Its a global lock, so hold it only for short times. */ +G_LOCK_DEFINE_STATIC(info_cache); + /** * GtkIconInfo: * @@ -216,7 +272,7 @@ struct _GtkIconInfo /* Information about the source */ IconInfoKey key; - GtkIconTheme *in_cache; + GtkIconTheme *in_cache; /* Protected by info_cache lock */ gchar *filename; GLoadableIcon *loadable; @@ -300,6 +356,7 @@ typedef struct } IconThemeDirMtime; static void gtk_icon_theme_finalize (GObject *object); +static void gtk_icon_theme_dispose (GObject *object); static void theme_dir_destroy (IconThemeDir *dir); static void theme_destroy (IconTheme *theme); static GtkIconInfo *theme_lookup_icon (IconTheme *theme, @@ -326,9 +383,98 @@ static GtkIconInfo *icon_info_new (IconThemeDirType type, gint dir_scale); static IconSuffix suffix_from_name (const gchar *name); static gboolean icon_info_ensure_scale_and_texture (GtkIconInfo* icon_info); +static void unset_display (GtkIconTheme *self); +static void update_current_theme__mainthread (GtkIconTheme *self); static guint signal_changed = 0; +/* This is like a weak ref with a lock, anyone doing + * operations on the theme must take the lock in this, + * but you can also take the lock even if the theme + * has been finalized (but theme will then be NULL). + * + * This is used to avoid race conditions where signals + * like theme-changed happen on the main thread while + * the last active owning ref of the icon theme is + * on some thread. + */ +struct _GtkIconThemeRef +{ + gatomicrefcount count; + GMutex lock; + GtkIconTheme *theme; +}; + +static GtkIconThemeRef * +gtk_icon_theme_ref_new (GtkIconTheme *theme) +{ + GtkIconThemeRef *ref = g_new0 (GtkIconThemeRef, 1); + + g_atomic_ref_count_init (&ref->count); + g_mutex_init (&ref->lock); + ref->theme = theme; + + return ref; +} + +static GtkIconThemeRef * +gtk_icon_theme_ref_ref (GtkIconThemeRef *ref) +{ + g_atomic_ref_count_inc (&ref->count); + return ref; +} + +static void +gtk_icon_theme_ref_unref (GtkIconThemeRef *ref) +{ + if (g_atomic_ref_count_dec (&ref->count)) + { + g_assert (ref->theme == NULL); + g_mutex_clear (&ref->lock); + g_free (ref); + } +} + +/* Take the lock and if available ensure the theme lives until (at + * least) ref_release is called. */ +static GtkIconTheme * +gtk_icon_theme_ref_aquire (GtkIconThemeRef *ref) +{ + g_mutex_lock (&ref->lock); + if (ref->theme) + g_object_ref (ref->theme); + return ref->theme; +} + +static void +gtk_icon_theme_ref_release (GtkIconThemeRef *ref) +{ + if (ref->theme) + g_object_unref (ref->theme); + g_mutex_unlock (&ref->lock); +} + +static void +gtk_icon_theme_ref_dispose (GtkIconThemeRef *ref) +{ + gtk_icon_theme_ref_aquire (ref); + ref->theme = NULL; + gtk_icon_theme_ref_release (ref); +} + +static void +gtk_icon_theme_lock (GtkIconTheme *self) +{ + g_mutex_lock (&self->ref->lock); +} + +static void +gtk_icon_theme_unlock (GtkIconTheme *self) +{ + g_mutex_unlock (&self->ref->lock); +} + + static guint icon_info_key_hash (gconstpointer _key) { @@ -376,13 +522,8 @@ icon_info_key_equal (gconstpointer _a, G_DEFINE_TYPE (GtkIconTheme, gtk_icon_theme, G_TYPE_OBJECT) static void -add_to_lru_cache (GtkIconInfo *info) +add_to_lru_cache (GtkIconTheme *self, GtkIconInfo *info) { - GtkIconTheme *self = info->in_cache; - - if (!self) - return; - if (info->texture && info->texture->width <= MAX_LRU_TEXTURE_SIZE && info->texture->height <= MAX_LRU_TEXTURE_SIZE) @@ -480,6 +621,7 @@ gtk_icon_theme_class_init (GtkIconThemeClass *klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = gtk_icon_theme_finalize; + gobject_class->dispose = gtk_icon_theme_dispose; /** * GtkIconTheme::changed: @@ -504,28 +646,35 @@ gtk_icon_theme_class_init (GtkIconThemeClass *klass) * for the display, drop the reference */ static void -display_closed (GdkDisplay *display, - gboolean is_error, - GtkIconTheme *self) +display_closed__mainthread_unlocked (GdkDisplay *display, + gboolean is_error, + GtkIconThemeRef *ref) { - gboolean was_display_singleton = self->is_display_singleton; + GtkIconTheme *self = gtk_icon_theme_ref_aquire (ref); + gboolean was_display_singleton; - if (was_display_singleton) + if (self) { - g_object_set_data (G_OBJECT (display), I_("gtk-icon-theme"), NULL); - self->is_display_singleton = FALSE; - } + /* This is only set at construction and accessed here in the main thread, so no locking necessary */ + was_display_singleton = self->is_display_singleton; + if (was_display_singleton) + { + g_object_set_data (G_OBJECT (display), I_("gtk-icon-theme"), NULL); + self->is_display_singleton = FALSE; + } - gtk_icon_theme_set_display (self, NULL); + unset_display (self); + update_current_theme__mainthread (self); - if (was_display_singleton) - { - g_object_unref (self); + if (was_display_singleton) + g_object_unref (self); } + + gtk_icon_theme_ref_release (ref); } static void -update_current_theme (GtkIconTheme *self) +update_current_theme__mainthread (GtkIconTheme *self) { #define theme_changed(_old, _new) \ ((_old && !_new) || (!_old && _new) || \ @@ -560,30 +709,32 @@ update_current_theme (GtkIconTheme *self) /* Callback when the icon theme GtkSetting changes */ static void -theme_changed (GtkSettings *settings, - GParamSpec *pspec, - GtkIconTheme *self) +theme_changed__mainthread_unlocked (GtkSettings *settings, + GParamSpec *pspec, + GtkIconThemeRef *ref) { - update_current_theme (self); + GtkIconTheme *self = gtk_icon_theme_ref_aquire (ref); + + if (self) + update_current_theme__mainthread (self); + + gtk_icon_theme_ref_release (ref); } static void unset_display (GtkIconTheme *self) { - GtkSettings *settings; - if (self->display) { - settings = gtk_settings_get_for_display (self->display); - g_signal_handlers_disconnect_by_func (self->display, - (gpointer) display_closed, - self); - g_signal_handlers_disconnect_by_func (settings, - (gpointer) theme_changed, - self); + (gpointer) display_closed__mainthread_unlocked, + self->ref); + g_signal_handlers_disconnect_by_func (self->display_settings, + (gpointer) theme_changed__mainthread_unlocked, + self->ref); self->display = NULL; + self->display_settings = NULL; } } @@ -600,26 +751,33 @@ void gtk_icon_theme_set_display (GtkIconTheme *self, GdkDisplay *display) { - GtkSettings *settings; - g_return_if_fail (GTK_ICON_THEME (self)); g_return_if_fail (display == NULL || GDK_IS_DISPLAY (display)); + gtk_icon_theme_lock (self); + unset_display (self); - + if (display) { - settings = gtk_settings_get_for_display (display); - self->display = display; - - g_signal_connect (display, "closed", - G_CALLBACK (display_closed), self); - g_signal_connect (settings, "notify::gtk-icon-theme-name", - G_CALLBACK (theme_changed), self); + self->display_settings = gtk_settings_get_for_display (display); + + g_signal_connect_data (display, "closed", + G_CALLBACK (display_closed__mainthread_unlocked), + gtk_icon_theme_ref_ref (self->ref), + (GClosureNotify)gtk_icon_theme_ref_unref, + 0); + g_signal_connect_data (self->display_settings, "notify::gtk-icon-theme-name", + G_CALLBACK (theme_changed__mainthread_unlocked), + gtk_icon_theme_ref_ref (self->ref), + (GClosureNotify)gtk_icon_theme_ref_unref, + 0); } - update_current_theme (self); + update_current_theme__mainthread (self); + + gtk_icon_theme_unlock (self); } /* Checks whether a loader for SVG files has been registered @@ -657,7 +815,7 @@ pixbuf_supports_svg (void) return found_svg; } -/* The icon info was removed from the icon_info_hash hash table */ +/* The icon info was removed from the icon_info_hash hash table. */ static void icon_info_uncached (GtkIconInfo *icon_info) { @@ -667,7 +825,8 @@ icon_info_uncached (GtkIconInfo *icon_info) icon_info->key.size, icon_info->key.flags, self, icon_theme != NULL ? g_hash_table_size (self->info_cache) : 0)); - + /* This is a callback from the info_cache hashtable, so the info_cache lock is already held */ + g_assert (icon_info->in_cache != NULL); icon_info->in_cache = NULL; } @@ -677,6 +836,8 @@ gtk_icon_theme_init (GtkIconTheme *self) const gchar * const *xdg_data_dirs; int i, j; + self->ref = gtk_icon_theme_ref_new (self); + self->info_cache = g_hash_table_new_full (icon_info_key_hash, icon_info_key_equal, NULL, (GDestroyNotify)icon_info_uncached); @@ -719,18 +880,37 @@ free_dir_mtime (IconThemeDirMtime *dir_mtime) } static gboolean -theme_changed_idle (gpointer user_data) +theme_changed_idle__mainthread_unlocked (gpointer user_data) { + GtkIconThemeRef *ref = (GtkIconThemeRef *)user_data; GtkIconTheme *self; + GdkDisplay *display = NULL; + + self = gtk_icon_theme_ref_aquire (ref); + if (self) + { + g_object_ref (self); /* Ensure theme lives during the changed signal emissions */ + + self->theme_changed_idle = 0; - self = GTK_ICON_THEME (user_data); + if (self->display && self->is_display_singleton) + display = g_object_ref (self->display); + } + gtk_icon_theme_ref_release (ref); - g_signal_emit (self, signal_changed, 0); + if (self) + { + /* Emit signals outside locks. */ + g_signal_emit (self, signal_changed, 0); - if (self->display && self->is_display_singleton) - gtk_style_context_reset_widgets (self->display); + if (display) + { + gtk_style_context_reset_widgets (self->display); + g_object_unref (display); + } - self->theme_changed_idle = 0; + g_object_unref (self); + } return FALSE; } @@ -741,9 +921,9 @@ queue_theme_changed (GtkIconTheme *self) if (!self->theme_changed_idle) { self->theme_changed_idle = g_idle_add_full (GTK_PRIORITY_RESIZE - 2, - theme_changed_idle, - self, - NULL); + theme_changed_idle__mainthread_unlocked, + gtk_icon_theme_ref_ref (self->ref), + (GDestroyNotify)gtk_icon_theme_ref_unref); g_source_set_name_by_id (self->theme_changed_idle, "[gtk] theme_changed_idle"); } } @@ -751,7 +931,9 @@ queue_theme_changed (GtkIconTheme *self) static void do_theme_change (GtkIconTheme *self) { + G_LOCK (info_cache); g_hash_table_remove_all (self->info_cache); + G_UNLOCK (info_cache); clear_lru_cache (self); if (!self->themes_valid) @@ -780,13 +962,40 @@ blow_themes (GtkIconTheme *self) self->themes_valid = FALSE; } +static void +gtk_icon_theme_dispose (GObject *object) +{ + GtkIconTheme *self = GTK_ICON_THEME (object); + + /* We make sure all outstanding GtkIconThemeRefs to us are NULLed + * out so that no other threads than the one running finalize will + * refer to the icon theme after this. This could happen if + * we finalize on a thread and on the main thread some display or + * setting signal is emitted. + * + * It is possible that before we aquire the lock this happens + * and the other thread refs the icon theme for some reason, but + * this is ok as it is allowed to resurrect objects in dispose + * (but not in finalize). + */ + gtk_icon_theme_ref_dispose (self->ref); + + G_OBJECT_CLASS (gtk_icon_theme_parent_class)->dispose (object); +} + static void gtk_icon_theme_finalize (GObject *object) { GtkIconTheme *self = GTK_ICON_THEME (object); int i; + /* We don't actually need to take the lock here, because by now + there can be no other threads that own a ref to this object, but + technically this is considered "locked" */ + + G_LOCK(info_cache); g_hash_table_destroy (self->info_cache); + G_UNLOCK(info_cache); if (self->theme_changed_idle) g_source_remove (self->theme_changed_idle); @@ -804,6 +1013,8 @@ gtk_icon_theme_finalize (GObject *object) blow_themes (self); clear_lru_cache (self); + gtk_icon_theme_ref_unref (self->ref); + G_OBJECT_CLASS (gtk_icon_theme_parent_class)->finalize (object); } @@ -838,6 +1049,8 @@ gtk_icon_theme_set_search_path (GtkIconTheme *self, g_return_if_fail (GTK_IS_ICON_THEME (self)); + gtk_icon_theme_lock (self); + for (i = 0; i < self->search_path_len; i++) g_free (self->search_path[i]); @@ -850,6 +1063,8 @@ gtk_icon_theme_set_search_path (GtkIconTheme *self, self->search_path[i] = g_strdup (path[i]); do_theme_change (self); + + gtk_icon_theme_unlock (self); } /** @@ -871,9 +1086,11 @@ gtk_icon_theme_get_search_path (GtkIconTheme *self, g_return_if_fail (GTK_IS_ICON_THEME (self)); + gtk_icon_theme_lock (self); + if (n_elements) *n_elements = self->search_path_len; - + if (path) { *path = g_new (gchar *, self->search_path_len + 1); @@ -881,6 +1098,8 @@ gtk_icon_theme_get_search_path (GtkIconTheme *self, (*path)[i] = g_strdup (self->search_path[i]); (*path)[i] = NULL; } + + gtk_icon_theme_unlock (self); } /** @@ -898,12 +1117,16 @@ gtk_icon_theme_append_search_path (GtkIconTheme *self, g_return_if_fail (GTK_IS_ICON_THEME (self)); g_return_if_fail (path != NULL); + gtk_icon_theme_lock (self); + self->search_path_len++; self->search_path = g_renew (gchar *, self->search_path, self->search_path_len); self->search_path[self->search_path_len-1] = g_strdup (path); do_theme_change (self); + + gtk_icon_theme_unlock (self); } /** @@ -923,15 +1146,19 @@ gtk_icon_theme_prepend_search_path (GtkIconTheme *self, g_return_if_fail (GTK_IS_ICON_THEME (self)); g_return_if_fail (path != NULL); + gtk_icon_theme_lock (self); + self->search_path_len++; self->search_path = g_renew (gchar *, self->search_path, self->search_path_len); for (i = self->search_path_len - 1; i > 0; i--) self->search_path[i] = self->search_path[i - 1]; - + self->search_path[0] = g_strdup (path); do_theme_change (self); + + gtk_icon_theme_unlock (self); } /** @@ -958,9 +1185,13 @@ gtk_icon_theme_add_resource_path (GtkIconTheme *self, g_return_if_fail (GTK_IS_ICON_THEME (self)); g_return_if_fail (path != NULL); + gtk_icon_theme_lock (self); + self->resource_paths = g_list_append (self->resource_paths, g_strdup (path)); do_theme_change (self); + + gtk_icon_theme_unlock (self); } /** @@ -981,7 +1212,9 @@ gtk_icon_theme_set_custom_theme (GtkIconTheme *self, g_return_if_fail (GTK_IS_ICON_THEME (self)); g_return_if_fail (!self->is_display_singleton); - + + gtk_icon_theme_lock (self); + if (theme_name != NULL) { self->custom_theme = TRUE; @@ -998,9 +1231,11 @@ gtk_icon_theme_set_custom_theme (GtkIconTheme *self, if (self->custom_theme) { self->custom_theme = FALSE; - update_current_theme (self); + update_current_theme__mainthread (self); } } + + gtk_icon_theme_unlock (self); } static const gchar builtin_hicolor_index[] = @@ -1376,7 +1611,9 @@ ensure_valid_themes (GtkIconTheme *self) if (ABS (tv.tv_sec - self->last_stat_time) > 5 && rescan_themes (self)) { + G_LOCK(info_cache); g_hash_table_remove_all (self->info_cache); + G_UNLOCK(info_cache); blow_themes (self); clear_lru_cache (self); } @@ -1468,7 +1705,12 @@ real_choose_icon (GtkIconTheme *self, key.scale = scale; key.flags = flags; + G_LOCK(info_cache); icon_info = g_hash_table_lookup (self->info_cache, &key); + if (icon_info != NULL) + icon_info = g_object_ref (icon_info); + G_UNLOCK(info_cache); + if (icon_info != NULL) { DEBUG_CACHE (("cache hit %p (%s %d 0x%x) (cache size %d)\n", @@ -1477,10 +1719,8 @@ real_choose_icon (GtkIconTheme *self, icon_info->key.size, icon_info->key.flags, g_hash_table_size (self->info_cache))); - icon_info = g_object_ref (icon_info); - /* Move item to front in LRU cache */ - add_to_lru_cache (icon_info); + add_to_lru_cache (self, icon_info); return icon_info; } @@ -1621,13 +1861,15 @@ real_choose_icon (GtkIconTheme *self, icon_info->key.size = size; icon_info->key.scale = scale; icon_info->key.flags = flags; + G_LOCK(info_cache); icon_info->in_cache = self; + g_hash_table_insert (self->info_cache, &icon_info->key, icon_info); + G_UNLOCK(info_cache); DEBUG_CACHE (("adding %p (%s %d 0x%x) to cache (cache size %d)\n", icon_info, g_strjoinv (",", icon_info->key.icon_names), icon_info->key.size, icon_info->key.flags, g_hash_table_size (self->info_cache))); - g_hash_table_insert (self->info_cache, &icon_info->key, icon_info); } else { @@ -1798,7 +2040,10 @@ choose_icon (GtkIconTheme *self, * like gdk_surface_get_scale_factor(). Instead, you should use * gtk_icon_theme_lookup_icon_for_scale(), as the assets loaded * for a given scaling factor may be different. - * + * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: (nullable) (transfer full): a #GtkIconInfo object * containing information about the icon, or %NULL if the * icon wasn’t found. @@ -1835,6 +2080,9 @@ gtk_icon_theme_lookup_icon (GtkIconTheme *self, * gtk_icon_info_load_icon(). (gtk_icon_theme_load_icon() combines * these two steps if all you need is the pixbuf.) * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: (nullable) (transfer full): a #GtkIconInfo object * containing information about the icon, or %NULL if the * icon wasn’t found. @@ -1857,6 +2105,8 @@ gtk_icon_theme_lookup_icon_for_scale (GtkIconTheme *self, GTK_DISPLAY_NOTE (self->display, ICONTHEME, g_message ("looking up icon %s for scale %d", icon_name, scale)); + gtk_icon_theme_lock (self); + if (flags & GTK_ICON_LOOKUP_GENERIC_FALLBACK) { gchar **names, **nonsymbolic_names; @@ -1918,6 +2168,8 @@ gtk_icon_theme_lookup_icon_for_scale (GtkIconTheme *self, info = choose_icon (self, names, size, scale, flags); } + gtk_icon_theme_unlock (self); + return info; } @@ -1938,7 +2190,10 @@ gtk_icon_theme_lookup_icon_for_scale (GtkIconTheme *self, * If @icon_names contains more than one name, this function * tries them all in the given order before falling back to * inherited icon themes. - * + * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: (nullable) (transfer full): a #GtkIconInfo object * containing information about the icon, or %NULL if the icon wasn’t * found. @@ -1949,13 +2204,21 @@ gtk_icon_theme_choose_icon (GtkIconTheme *self, gint size, GtkIconLookupFlags flags) { + GtkIconInfo *info; + g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL); g_return_val_if_fail (icon_names != NULL, NULL); g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || (flags & GTK_ICON_LOOKUP_FORCE_SVG) == 0, NULL); g_warn_if_fail ((flags & GTK_ICON_LOOKUP_GENERIC_FALLBACK) == 0); - return choose_icon (self, icon_names, size, 1, flags); + gtk_icon_theme_lock (self); + + info = choose_icon (self, icon_names, size, 1, flags); + + gtk_icon_theme_unlock (self); + + return info; } /** @@ -1976,7 +2239,10 @@ gtk_icon_theme_choose_icon (GtkIconTheme *self, * If @icon_names contains more than one name, this function * tries them all in the given order before falling back to * inherited icon themes. - * + * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: (nullable) (transfer full): a #GtkIconInfo object * containing information about the icon, or %NULL if the * icon wasn’t found. @@ -1988,6 +2254,8 @@ gtk_icon_theme_choose_icon_for_scale (GtkIconTheme *self, gint scale, GtkIconLookupFlags flags) { + GtkIconInfo *info; + g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL); g_return_val_if_fail (icon_names != NULL, NULL); g_return_val_if_fail ((flags & GTK_ICON_LOOKUP_NO_SVG) == 0 || @@ -1995,7 +2263,13 @@ gtk_icon_theme_choose_icon_for_scale (GtkIconTheme *self, g_return_val_if_fail (scale >= 1, NULL); g_warn_if_fail ((flags & GTK_ICON_LOOKUP_GENERIC_FALLBACK) == 0); - return choose_icon (self, icon_names, size, scale, flags); + gtk_icon_theme_lock (self); + + info = choose_icon (self, icon_names, size, scale, flags); + + gtk_icon_theme_unlock (self); + + return info; } @@ -2119,10 +2393,13 @@ gtk_icon_theme_load_icon_for_scale (GtkIconTheme *self, * gtk_icon_theme_has_icon: * @self: a #GtkIconTheme * @icon_name: the name of an icon - * + * * Checks whether an icon theme includes an icon * for a particular name. - * + * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: %TRUE if @self includes an * icon for @icon_name. */ @@ -2131,28 +2408,40 @@ gtk_icon_theme_has_icon (GtkIconTheme *self, const gchar *icon_name) { GList *l; + gboolean res = FALSE; g_return_val_if_fail (GTK_IS_ICON_THEME (self), FALSE); g_return_val_if_fail (icon_name != NULL, FALSE); + gtk_icon_theme_lock (self); + ensure_valid_themes (self); for (l = self->dir_mtimes; l; l = l->next) { IconThemeDirMtime *dir_mtime = l->data; GtkIconCache *cache = dir_mtime->cache; - + if (cache && gtk_icon_cache_has_icon (cache, icon_name)) - return TRUE; + { + res = TRUE; + goto out; + } } for (l = self->themes; l; l = l->next) { if (theme_has_icon (l->data, icon_name)) - return TRUE; + { + res = TRUE; + goto out; + } } - return FALSE; + out: + gtk_icon_theme_unlock (self); + + return res; } static void @@ -2176,7 +2465,10 @@ add_size (gpointer key, * the icon is available without scaling. A size of -1 means * that the icon is available in a scalable format. The array * is zero-terminated. - * + * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: (array zero-terminated=1) (transfer full): A newly * allocated array describing the sizes at which the icon is * available. The array should be freed with g_free() when it is no @@ -2189,10 +2481,12 @@ gtk_icon_theme_get_icon_sizes (GtkIconTheme *self, GList *l, *d; GHashTable *sizes; gint *result, *r; - guint suffix; + guint suffix; g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL); - + + gtk_icon_theme_lock (self); + ensure_valid_themes (self); sizes = g_hash_table_new (g_direct_hash, g_direct_equal); @@ -2222,7 +2516,9 @@ gtk_icon_theme_get_icon_sizes (GtkIconTheme *self, g_hash_table_foreach (sizes, add_size, &r); g_hash_table_destroy (sizes); - + + gtk_icon_theme_unlock (self); + return result; } @@ -2261,6 +2557,9 @@ add_key_to_list (gpointer key, * The standard contexts are listed in the * [Icon Naming Specification](http://www.freedesktop.org/wiki/Specifications/icon-naming-spec). * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: (element-type utf8) (transfer full): a #GList list * holding the names of all the icons in the theme. You must * first free each element in the list with g_free(), then @@ -2273,7 +2572,9 @@ gtk_icon_theme_list_icons (GtkIconTheme *self, GHashTable *icons; GList *list, *l; GQuark context_quark; - + + gtk_icon_theme_lock (self); + ensure_valid_themes (self); if (context) @@ -2281,7 +2582,7 @@ gtk_icon_theme_list_icons (GtkIconTheme *self, context_quark = g_quark_try_string (context); if (!context_quark) - return NULL; + goto out; } else context_quark = 0; @@ -2307,7 +2608,11 @@ gtk_icon_theme_list_icons (GtkIconTheme *self, &list); g_hash_table_destroy (icons); - + + out: + + gtk_icon_theme_unlock (self); + return list; } @@ -2353,6 +2658,9 @@ rescan_themes (GtkIconTheme *self) * currently cached information is discarded and will be reloaded * next time @self is accessed. * + * This call is threadsafe, you can safely pass a GtkIconTheme + * to another thread and call this method on it. + * * Returns: %TRUE if the icon theme has changed and needed * to be reloaded. */ @@ -2363,10 +2671,14 @@ gtk_icon_theme_rescan_if_needed (GtkIconTheme *self) g_return_val_if_fail (GTK_IS_ICON_THEME (self), FALSE); + gtk_icon_theme_lock (self); + retval = rescan_themes (self); if (retval) do_theme_change (self); + gtk_icon_theme_unlock (self); + return retval; } @@ -3047,8 +3359,10 @@ gtk_icon_info_finalize (GObject *object) { GtkIconInfo *icon_info = (GtkIconInfo *) object; + G_LOCK(info_cache); if (icon_info->in_cache) g_hash_table_remove (icon_info->in_cache->info_cache, &icon_info->key); + G_UNLOCK(info_cache); g_strfreev (icon_info->key.icon_names); @@ -3172,6 +3486,25 @@ icon_info_get_pixbuf_ready (GtkIconInfo *icon_info) return FALSE; } +static void +icon_info_add_to_lru_cache (GtkIconInfo *info) +{ + GtkIconTheme *theme = NULL; + + G_LOCK(info_cache); + if (info->in_cache) + theme = g_object_ref (info->in_cache); + G_UNLOCK(info_cache); + + if (theme) + { + gtk_icon_theme_lock (theme); + add_to_lru_cache (theme, info); + gtk_icon_theme_unlock (theme); + g_object_unref (theme); + } +} + static GLoadableIcon * icon_info_get_loadable (GtkIconInfo *icon_info) { @@ -3404,7 +3737,7 @@ icon_info_ensure_scale_and_texture (GtkIconInfo *icon_info) } g_assert (icon_info->texture != NULL); - add_to_lru_cache (icon_info); + icon_info_add_to_lru_cache (icon_info); return TRUE; }