diff --git a/daemon-Add-process-name-to-debug-handler-log.patch b/daemon-Add-process-name-to-debug-handler-log.patch new file mode 100644 index 0000000..c07cbbd --- /dev/null +++ b/daemon-Add-process-name-to-debug-handler-log.patch @@ -0,0 +1,47 @@ +From 9e38c56f99372b2474a780ac0c7c7d9b38e490cf Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Mon, 11 May 2026 17:48:26 +0200 +Subject: [PATCH] daemon: Add process name to debug handler log + +Made-with: Cursor (Claude) +--- + daemon/gvfsbackend.c | 17 +++++++++++++---- + 1 file changed, 13 insertions(+), 4 deletions(-) + +diff --git a/daemon/gvfsbackend.c b/daemon/gvfsbackend.c +index fa1389ea..7dfd5300 100644 +--- a/daemon/gvfsbackend.c ++++ b/daemon/gvfsbackend.c +@@ -636,16 +636,25 @@ g_vfs_backend_invocation_first_handler (GVfsDBusMount *object, + GDBusConnection *connection; + GCredentials *credentials; + pid_t pid = -1; ++ g_autofree gchar *comm = NULL; + + connection = g_dbus_method_invocation_get_connection (invocation); + credentials = g_dbus_connection_get_peer_credentials (connection); + if (credentials) + pid = g_credentials_get_unix_pid (credentials, NULL); + +- g_debug ("backend_dbus_handler %s:%s (pid=%ld)\n", +- g_dbus_method_invocation_get_interface_name (invocation), +- g_dbus_method_invocation_get_method_name (invocation), +- (long)pid); ++ if (pid > 0) ++ { ++ g_autofree gchar *comm_path = g_strdup_printf ("/proc/%ld/comm", (long)pid); ++ ++ if (g_file_get_contents (comm_path, &comm, NULL, NULL)) ++ g_strstrip (comm); ++ } ++ ++ g_debug ("backend_dbus_handler %s:%s (pid=%ld comm=%s)\n", ++ g_dbus_method_invocation_get_interface_name (invocation), ++ g_dbus_method_invocation_get_method_name (invocation), ++ (long)pid, comm ? comm : ""); + + if (backend->priv->block_requests) + { +-- +2.53.0 + diff --git a/gvfs.spec b/gvfs.spec index 70758b6..a4bc1c1 100644 --- a/gvfs.spec +++ b/gvfs.spec @@ -22,7 +22,7 @@ Name: gvfs Version: 1.54.4 -Release: 4%{?dist} +Release: 5%{?dist} Summary: Backends for the gio framework in GLib License: LGPL-2.0-or-later AND GPL-3.0-only AND MPL-2.0 AND BSD-3-Clause-Sun @@ -49,6 +49,19 @@ Patch: udisks2-monitor-performance-273.patch Patch: udisks2-monitor-performance-290.patch Patch: udisks2-monitor-performance-297.patch +# Improve trash backend performance +# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/287 +Patch: trash-Rate-limit-mount-updates.patch +Patch: trash-Add-debug-prints-for-mounts-handling.patch +Patch: trash-Use-GHashTable-for-mount-tracking.patch +Patch: trash-Use-GHashTable-for-trash-directory-items.patch +Patch: trash-Rate-limit-size-change-notifications.patch +Patch: trash-Chain-up-finalize.patch +Patch: trash-Use-weak-refs-for-monitor-lifecycle.patch +Patch: trash-Defer-mount-monitoring-to-active-watchers-only.patch +Patch: daemon-Add-process-name-to-debug-handler-log.patch +Patch: trash-Skip-mount-table-reparsing-when-unchanged.patch + BuildRequires: meson BuildRequires: gcc BuildRequires: pkgconfig(glib-2.0) >= %{glib2_version} @@ -450,6 +463,10 @@ killall -USR1 gvfsd >&/dev/null || : %changelog +* Tue Jun 02 2026 Ondrej Holy - 1.54.4-5 +- Improve trash backend performance + Resolves: RHEL-138575 + * Fri Feb 06 2026 Milan Crha - 1.54.4-4 - udisks2: Correct add to hash table for items which can clash in the monitor (RHEL-143935) diff --git a/trash-Add-debug-prints-for-mounts-handling.patch b/trash-Add-debug-prints-for-mounts-handling.patch new file mode 100644 index 0000000..2223d65 --- /dev/null +++ b/trash-Add-debug-prints-for-mounts-handling.patch @@ -0,0 +1,60 @@ +From 18e795eda715947b42a940b277a51b8e36b64062 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 16:01:09 +0200 +Subject: [PATCH] trash: Add debug prints for mounts handling + +Backport of commit e0018263. + +Adapted for older GLib API: `g_unix_mount_entry_get_device_path`/`get_mount_path`/`get_fs_type` replaced with `g_unix_mount_get_device_path`/`g_unix_mount_get_mount_path`/`g_unix_mount_get_fs_type`. +--- + daemon/trashlib/trashwatcher.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 506d03c..21809f2 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -279,12 +279,22 @@ trash_watcher_remount_do (TrashWatcher *watcher) + if (result < 0) + { + /* new entry. add it. */ ++ g_debug ("trash_watcher_remount_do: insert %s %s %s\n", ++ g_unix_mount_get_device_path (new->data), ++ g_unix_mount_get_mount_path (new->data), ++ g_unix_mount_get_fs_type (new->data)); ++ + trash_mount_insert (watcher, &old, new->data); + new = new->next; + } + else if (result > 0) + { + /* old entry. remove it. */ ++ g_debug ("trash_watcher_remount_do: remove %s %s %s\n", ++ g_unix_mount_get_device_path ((*old)->mount_entry), ++ g_unix_mount_get_mount_path ((*old)->mount_entry), ++ g_unix_mount_get_fs_type ((*old)->mount_entry)); ++ + trash_mount_remove (old); + } + else +@@ -305,6 +315,8 @@ trash_watcher_remount_timeout (gpointer user_data) + { + TrashWatcher *watcher = user_data; + ++ g_debug ("trash_watcher_remount_timeout\n"); ++ + watcher->update_id = 0; + + trash_watcher_remount_do (watcher); +@@ -315,6 +327,8 @@ trash_watcher_remount_timeout (gpointer user_data) + static void + trash_watcher_remount (TrashWatcher *watcher) + { ++ g_debug ("trash_watcher_remount\n"); ++ + if (watcher->update_id != 0) + return; + +-- +2.53.0 + diff --git a/trash-Chain-up-finalize.patch b/trash-Chain-up-finalize.patch new file mode 100644 index 0000000..2f52e7a --- /dev/null +++ b/trash-Chain-up-finalize.patch @@ -0,0 +1,30 @@ +From f1cdb812f8fa6f06239183923f920025a05e0712 Mon Sep 17 00:00:00 2001 +From: Maximiliano Sandoval +Date: Fri, 10 Apr 2026 23:52:16 +0200 +Subject: [PATCH] trash: Chain up finalize + +Currently, `trash_backend_finalize()` only releases backend-specific +monitors. Without chaining up, parent `GObject` finalization is +skipped, which can leave parent-side teardown incomplete. Let's call +`g_vfs_backend_trash_parent_class->finalize()` at the end of +`trash_backned_finalize()`. +--- + daemon/gvfsbackendtrash.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index 0966242a..1dcb44f0 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -987,6 +987,8 @@ trash_backend_finalize (GObject *object) + if (backend->dir_monitor) + g_object_unref (backend->dir_monitor); + backend->dir_monitor = NULL; ++ ++ G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize (object); + } + + static void +-- +2.53.0 + diff --git a/trash-Defer-mount-monitoring-to-active-watchers-only.patch b/trash-Defer-mount-monitoring-to-active-watchers-only.patch new file mode 100644 index 0000000..919eb55 --- /dev/null +++ b/trash-Defer-mount-monitoring-to-active-watchers-only.patch @@ -0,0 +1,84 @@ +From c6d70b441f9c6f50a844808e6ebfa0651b0a7f8f Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 16:01:50 +0200 +Subject: [PATCH] trash: Defer mount monitoring to active watchers only + +Backport of commit b53d45db. + +Adjusted context for target version. +--- + daemon/trashlib/trashwatcher.c | 24 +++++++++++++++++++++--- + 1 file changed, 21 insertions(+), 3 deletions(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 3c1aea1..60410df 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -141,6 +141,7 @@ struct OPAQUE_TYPE__TrashWatcher + TrashRoot *root; + + GUnixMountMonitor *mount_monitor; ++ gulong mounts_changed_id; + GHashTable *mounts; /* mount_path -> TrashMount */ + guint update_id; + +@@ -345,8 +346,7 @@ trash_watcher_new (TrashRoot *root) + watcher->watching = FALSE; + watcher->update_id = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); +- g_signal_connect_swapped (watcher->mount_monitor, "mounts_changed", +- G_CALLBACK (trash_watcher_remount), watcher); ++ watcher->mounts_changed_id = 0; + + user_datadir = g_file_new_for_path (g_get_user_data_dir ()); + homedir_trashdir = g_file_get_child (user_datadir, "Trash/files"); +@@ -384,9 +384,17 @@ trash_watcher_watch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + ++ g_debug ("trash_watcher_watch\n"); ++ + if (watcher->watching) + return; + ++ watcher->mounts_changed_id = ++ g_signal_connect_swapped (watcher->mount_monitor, "mounts_changed", ++ G_CALLBACK (trash_watcher_remount), watcher); ++ ++ trash_watcher_remount_do (watcher); ++ + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_watch (watcher->homedir_trashdir); + +@@ -410,9 +418,17 @@ trash_watcher_unwatch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + ++ g_debug ("trash_watcher_unwatch\n"); ++ + if (!watcher->watching) + return; + ++ g_signal_handler_disconnect (watcher->mount_monitor, ++ watcher->mounts_changed_id); ++ watcher->mounts_changed_id = 0; ++ ++ g_clear_handle_id (&watcher->update_id, g_source_remove); ++ + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_unwatch (watcher->homedir_trashdir); + +@@ -436,7 +452,9 @@ trash_watcher_rescan (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + +- if (watcher->update_id != 0) ++ if (!watcher->watching) ++ trash_watcher_remount_do (watcher); ++ else if (watcher->update_id != 0) + { + g_source_remove (watcher->update_id); + trash_watcher_remount_timeout (watcher); +-- +2.53.0 + diff --git a/trash-Rate-limit-mount-updates.patch b/trash-Rate-limit-mount-updates.patch new file mode 100644 index 0000000..751fc14 --- /dev/null +++ b/trash-Rate-limit-mount-updates.patch @@ -0,0 +1,119 @@ +From 181da9f0d10a9e75795db5bb138214a89382271f Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Fri, 21 Nov 2025 13:28:55 +0100 +Subject: [PATCH] trash: Rate limit mount updates + +Currently, the trash daemon processes every `mounts_changed` signal. +This leads to high CPU usage when many mount events occur in a short +timeframe. Let's rate limit mount processing to avoid high CPU usage +in this case. The timeout is randomly chosen within the interval +50-500 ms to better balance the load when multiple daemons are +running simultaneously. + +Related: https://gitlab.gnome.org/GNOME/gvfs/-/issues/814 +--- + daemon/trashlib/trashwatcher.c | 39 ++++++++++++++++++++++++++++++++-- + 1 file changed, 37 insertions(+), 2 deletions(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 80a24d3d..664380c6 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -142,6 +142,7 @@ struct OPAQUE_TYPE__TrashWatcher + + GUnixMountMonitor *mount_monitor; + TrashMount *mounts; ++ guint update_id; + + TrashDir *homedir_trashdir; + WatchType homedir_type; +@@ -158,6 +159,8 @@ struct _TrashMount + TrashMount *next; + }; + ++#define UPDATE_TIMEOUT 100 /* ms */ ++ + static void + trash_mount_insert (TrashWatcher *watcher, + TrashMount ***mount_ptr_ptr, +@@ -247,7 +250,7 @@ ignore_trash_mount (GUnixMountEntry *mount) + } + + static void +-trash_watcher_remount (TrashWatcher *watcher) ++trash_watcher_remount_do (TrashWatcher *watcher) + { + TrashMount **old; + GList *mounts; +@@ -298,6 +301,29 @@ trash_watcher_remount (TrashWatcher *watcher) + g_list_free (mounts); + } + ++static gboolean ++trash_watcher_remount_timeout (gpointer user_data) ++{ ++ TrashWatcher *watcher = user_data; ++ ++ watcher->update_id = 0; ++ ++ trash_watcher_remount_do (watcher); ++ ++ return G_SOURCE_REMOVE; ++} ++ ++static void ++trash_watcher_remount (TrashWatcher *watcher) ++{ ++ if (watcher->update_id != 0) ++ return; ++ ++ watcher->update_id = g_timeout_add (UPDATE_TIMEOUT * g_random_double_range (0.5, 5), ++ trash_watcher_remount_timeout, ++ watcher); ++} ++ + TrashWatcher * + trash_watcher_new (TrashRoot *root) + { +@@ -310,6 +336,7 @@ trash_watcher_new (TrashRoot *root) + watcher->root = root; + watcher->mounts = NULL; + watcher->watching = FALSE; ++ watcher->update_id = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); + g_signal_connect_swapped (watcher->mount_monitor, "mounts_changed", + G_CALLBACK (trash_watcher_remount), watcher); +@@ -328,7 +355,7 @@ trash_watcher_new (TrashRoot *root) + g_object_unref (homedir_trashdir); + g_object_unref (user_datadir); + +- trash_watcher_remount (watcher); ++ trash_watcher_remount_do (watcher); + + return watcher; + } +@@ -336,6 +363,8 @@ trash_watcher_new (TrashRoot *root) + void + trash_watcher_free (TrashWatcher *watcher) + { ++ g_clear_handle_id (&watcher->update_id, g_source_remove); ++ + /* We just leak everything here, as this is not normally hit. + This used to be a g_assert_not_reached(), and that got hit when + mounting the trash backend failed due to the trash already being +@@ -387,6 +416,12 @@ trash_watcher_rescan (TrashWatcher *watcher) + { + TrashMount *mount; + ++ if (watcher->update_id != 0) ++ { ++ g_source_remove (watcher->update_id); ++ trash_watcher_remount_timeout (watcher); ++ } ++ + if (!watcher->watching || watcher->homedir_type != TRASH_WATCHER_TRUSTED) + trash_dir_rescan (watcher->homedir_trashdir); + +-- +2.53.0 + diff --git a/trash-Rate-limit-size-change-notifications.patch b/trash-Rate-limit-size-change-notifications.patch new file mode 100644 index 0000000..63569d5 --- /dev/null +++ b/trash-Rate-limit-size-change-notifications.patch @@ -0,0 +1,88 @@ +From 68a056dd3764c795c5d9d5844e2cc3f20f9eaf1d Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 3 Feb 2026 16:47:50 +0100 +Subject: [PATCH] trash: Rate limit size change notifications + +Currently, the `attribute-changed` signal is emitted after every file +change. This can lead to higher CPU usage if too many changes occur +in a short time frame. To avoid this, let's limit the frequency of the +signal to a maximum of 100 ms. + +Co-Authored-By: Claude + +Related: https://gitlab.gnome.org/GNOME/gvfs/-/issues/814 +--- + daemon/trashlib/trashitem.c | 26 +++++++++++++++++++++++--- + 1 file changed, 23 insertions(+), 3 deletions(-) + +diff --git a/daemon/trashlib/trashitem.c b/daemon/trashlib/trashitem.c +index a5b5b166..08dd8ad8 100644 +--- a/daemon/trashlib/trashitem.c ++++ b/daemon/trashlib/trashitem.c +@@ -30,8 +30,12 @@ struct OPAQUE_TYPE__TrashRoot + + GHashTable *item_table; + int old_size; ++ ++ guint size_change_id; + }; + ++#define SIZE_CHANGE_TIMEOUT 100 /* ms */ ++ + struct OPAQUE_TYPE__TrashItem + { + TrashRoot *root; +@@ -283,6 +287,17 @@ trash_item_invoke_closure (NotifyClosure *closure) + g_slice_free (NotifyClosure, closure); + } + ++static gboolean ++trash_root_size_change_timeout (gpointer user_data) ++{ ++ TrashRoot *root = user_data; ++ ++ root->size_change_id = 0; ++ root->size_change (root->user_data); ++ ++ return G_SOURCE_REMOVE; ++} ++ + void + trash_root_thaw (TrashRoot *root) + { +@@ -308,10 +323,13 @@ trash_root_thaw (TrashRoot *root) + size_changed = root->old_size != size; + root->old_size = size; + +- g_rw_lock_writer_unlock (&root->lock); ++ /* Rate limit size_change notifications */ ++ if (size_changed && root->size_change_id == 0) ++ root->size_change_id = g_timeout_add (SIZE_CHANGE_TIMEOUT, ++ trash_root_size_change_timeout, ++ root); + +- if (size_changed) +- root->size_change (root->user_data); ++ g_rw_lock_writer_unlock (&root->lock); + } + + static void +@@ -341,6 +359,7 @@ trash_root_new (trash_item_notify create, + root->item_table = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, trash_item_removed); + root->old_size = 0; ++ root->size_change_id = 0; + + return root; + } +@@ -348,6 +367,7 @@ trash_root_new (trash_item_notify create, + void + trash_root_free (TrashRoot *root) + { ++ g_clear_handle_id (&root->size_change_id, g_source_remove); + g_hash_table_destroy (root->item_table); + + while (!g_queue_is_empty (root->notifications)) +-- +2.53.0 + diff --git a/trash-Skip-mount-table-reparsing-when-unchanged.patch b/trash-Skip-mount-table-reparsing-when-unchanged.patch new file mode 100644 index 0000000..0963f7a --- /dev/null +++ b/trash-Skip-mount-table-reparsing-when-unchanged.patch @@ -0,0 +1,47 @@ +From 163dc91fe3c1b6fac6d8aed947b66d8c32ccc010 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 16:01:50 +0200 +Subject: [PATCH] trash: Skip mount table reparsing when unchanged + +Backport of commit e1821cdc. + +Adapted for older GLib API: `g_unix_mount_entries_get`/`g_unix_mount_entries_changed_since` replaced with `g_unix_mounts_get`/`g_unix_mounts_changed_since`. +--- + daemon/trashlib/trashwatcher.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 60410df..93e1fae 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -144,6 +144,7 @@ struct OPAQUE_TYPE__TrashWatcher + gulong mounts_changed_id; + GHashTable *mounts; /* mount_path -> TrashMount */ + guint update_id; ++ guint64 last_mount_time; + + TrashDir *homedir_trashdir; + WatchType homedir_type; +@@ -247,7 +248,10 @@ trash_watcher_remount_do (TrashWatcher *watcher) + gpointer key, value; + const char *mount_path; + +- mounts = g_unix_mounts_get (NULL); ++ if (!g_unix_mounts_changed_since (watcher->last_mount_time)) ++ return; ++ ++ mounts = g_unix_mounts_get (&watcher->last_mount_time); + mount_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + for (l = mounts; l != NULL; l = l->next) +@@ -345,6 +349,7 @@ trash_watcher_new (TrashRoot *root) + watcher->mounts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + watcher->watching = FALSE; + watcher->update_id = 0; ++ watcher->last_mount_time = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); + watcher->mounts_changed_id = 0; + +-- +2.53.0 + diff --git a/trash-Use-GHashTable-for-mount-tracking.patch b/trash-Use-GHashTable-for-mount-tracking.patch new file mode 100644 index 0000000..790e2e8 --- /dev/null +++ b/trash-Use-GHashTable-for-mount-tracking.patch @@ -0,0 +1,280 @@ +From 0d3023a9e7303c7786f6510643fb7cad62e36568 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 16:01:50 +0200 +Subject: [PATCH] trash: Use GHashTable for mount tracking + +Backport of commit 64f8853f. + +Adapted for older GLib API: `g_unix_mount_entry_*` replaced with `g_unix_mount_*`, `g_unix_mount_entries_get` replaced with `g_unix_mounts_get`. +--- + daemon/trashlib/trashwatcher.c | 156 +++++++++++++++++---------------- + 1 file changed, 80 insertions(+), 76 deletions(-) + +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 21809f2..72b67e4 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -141,7 +141,7 @@ struct OPAQUE_TYPE__TrashWatcher + TrashRoot *root; + + GUnixMountMonitor *mount_monitor; +- TrashMount *mounts; ++ GHashTable *mounts; /* mount_path -> TrashMount */ + guint update_id; + + TrashDir *homedir_trashdir; +@@ -155,16 +155,13 @@ struct _TrashMount + GUnixMountEntry *mount_entry; + TrashDir *dirs[2]; + WatchType type; +- +- TrashMount *next; + }; + + #define UPDATE_TIMEOUT 100 /* ms */ + + static void +-trash_mount_insert (TrashWatcher *watcher, +- TrashMount ***mount_ptr_ptr, +- GUnixMountEntry *mount_entry) ++trash_mount_insert (TrashWatcher *watcher, ++ GUnixMountEntry *mount_entry) + { + const char *mountpoint; + gboolean watching; +@@ -194,24 +191,15 @@ trash_mount_insert (TrashWatcher *watcher, + mount->dirs[1] = trash_dir_new (watcher->root, watching, FALSE, mountpoint, + ".Trash-%d/files", (int) getuid ()); + +- mount->next = **mount_ptr_ptr; +- +- **mount_ptr_ptr = mount; +- *mount_ptr_ptr = &mount->next; ++ g_hash_table_insert (watcher->mounts, g_strdup (mountpoint), mount); + } + + static void +-trash_mount_remove (TrashMount **mount_ptr) ++trash_mount_free (TrashMount *mount) + { +- TrashMount *mount = *mount_ptr; +- +- /* first, the dirs */ + trash_dir_free (mount->dirs[0]); + trash_dir_free (mount->dirs[1]); + +- /* detach from list */ +- *mount_ptr = mount->next; +- + g_unix_mount_free (mount->mount_entry); + g_slice_free (TrashMount, mount); + } +@@ -251,63 +239,64 @@ ignore_trash_mount (GUnixMountEntry *mount) + static void + trash_watcher_remount_do (TrashWatcher *watcher) + { +- TrashMount **old; ++ GHashTable *mount_paths; ++ GHashTableIter iter; + GList *mounts; +- GList *new; ++ GList *l; ++ gpointer key, value; ++ const char *mount_path; + + mounts = g_unix_mounts_get (NULL); +- mounts = g_list_sort (mounts, (GCompareFunc) g_unix_mount_compare); ++ mount_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + +- old = &watcher->mounts; +- new = mounts; +- +- /* synchronise the two lists */ +- while (*old || new) ++ for (l = mounts; l != NULL; l = l->next) + { +- int result; ++ g_autoptr(GUnixMountEntry) mount_entry = l->data; + +- if (new && ignore_trash_mount (new->data)) ++ if (ignore_trash_mount (mount_entry)) + { +- g_unix_mount_free (new->data); +- new = new->next; ++ g_debug ("trash_watcher_remount_do: ignore %s %s %s\n", ++ g_unix_mount_get_device_path (mount_entry), ++ g_unix_mount_get_mount_path (mount_entry), ++ g_unix_mount_get_fs_type (mount_entry)); + continue; + } + +- if ((result = (new == NULL) - (*old == NULL)) == 0) +- result = g_unix_mount_compare (new->data, (*old)->mount_entry); +- +- if (result < 0) ++ mount_path = g_unix_mount_get_mount_path (mount_entry); ++ if (!g_hash_table_contains (watcher->mounts, mount_path)) + { +- /* new entry. add it. */ + g_debug ("trash_watcher_remount_do: insert %s %s %s\n", +- g_unix_mount_get_device_path (new->data), +- g_unix_mount_get_mount_path (new->data), +- g_unix_mount_get_fs_type (new->data)); ++ g_unix_mount_get_device_path (mount_entry), ++ g_unix_mount_get_mount_path (mount_entry), ++ g_unix_mount_get_fs_type (mount_entry)); + +- trash_mount_insert (watcher, &old, new->data); +- new = new->next; ++ trash_mount_insert (watcher, g_steal_pointer (&mount_entry)); + } +- else if (result > 0) +- { +- /* old entry. remove it. */ +- g_debug ("trash_watcher_remount_do: remove %s %s %s\n", +- g_unix_mount_get_device_path ((*old)->mount_entry), +- g_unix_mount_get_mount_path ((*old)->mount_entry), +- g_unix_mount_get_fs_type ((*old)->mount_entry)); + +- trash_mount_remove (old); +- } +- else ++ g_hash_table_add (mount_paths, g_strdup (mount_path)); ++ } ++ ++ g_list_free (mounts); ++ ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, &key, &value)) ++ { ++ TrashMount *mount = value; ++ mount_path = key; ++ ++ if (!g_hash_table_contains (mount_paths, mount_path)) + { +- /* match. no change. */ +- g_unix_mount_free (new->data); ++ g_debug ("trash_watcher_remount_do: remove %s %s %s\n", ++ g_unix_mount_get_device_path (mount->mount_entry), ++ g_unix_mount_get_mount_path (mount->mount_entry), ++ g_unix_mount_get_fs_type (mount->mount_entry)); + +- old = &(*old)->next; +- new = new->next; ++ trash_mount_free (mount); ++ g_hash_table_iter_remove (&iter); + } + } + +- g_list_free (mounts); ++ g_hash_table_destroy (mount_paths); + } + + static gboolean +@@ -347,7 +341,7 @@ trash_watcher_new (TrashRoot *root) + + watcher = g_slice_new (TrashWatcher); + watcher->root = root; +- watcher->mounts = NULL; ++ watcher->mounts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + watcher->watching = FALSE; + watcher->update_id = 0; + watcher->mount_monitor = g_unix_mount_monitor_get (); +@@ -387,19 +381,24 @@ trash_watcher_free (TrashWatcher *watcher) + void + trash_watcher_watch (TrashWatcher *watcher) + { +- TrashMount *mount; ++ GHashTableIter iter; ++ gpointer value; + + g_assert (!watcher->watching); + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_watch (watcher->homedir_trashdir); + +- for (mount = watcher->mounts; mount; mount = mount->next) +- if (mount->type != TRASH_WATCHER_NO_WATCH) +- { +- trash_dir_watch (mount->dirs[0]); +- trash_dir_watch (mount->dirs[1]); +- } ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, NULL, &value)) ++ { ++ TrashMount *mount = value; ++ if (mount->type != TRASH_WATCHER_NO_WATCH) ++ { ++ trash_dir_watch (mount->dirs[0]); ++ trash_dir_watch (mount->dirs[1]); ++ } ++ } + + watcher->watching = TRUE; + } +@@ -407,19 +406,24 @@ trash_watcher_watch (TrashWatcher *watcher) + void + trash_watcher_unwatch (TrashWatcher *watcher) + { +- TrashMount *mount; ++ GHashTableIter iter; ++ gpointer value; + + g_assert (watcher->watching); + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_unwatch (watcher->homedir_trashdir); + +- for (mount = watcher->mounts; mount; mount = mount->next) +- if (mount->type != TRASH_WATCHER_NO_WATCH) +- { +- trash_dir_unwatch (mount->dirs[0]); +- trash_dir_unwatch (mount->dirs[1]); +- } ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, NULL, &value)) ++ { ++ TrashMount *mount = value; ++ if (mount->type != TRASH_WATCHER_NO_WATCH) ++ { ++ trash_dir_unwatch (mount->dirs[0]); ++ trash_dir_unwatch (mount->dirs[1]); ++ } ++ } + + watcher->watching = FALSE; + } +@@ -427,7 +431,8 @@ trash_watcher_unwatch (TrashWatcher *watcher) + void + trash_watcher_rescan (TrashWatcher *watcher) + { +- TrashMount *mount; ++ GHashTableIter iter; ++ gpointer value; + + if (watcher->update_id != 0) + { +@@ -438,10 +443,14 @@ trash_watcher_rescan (TrashWatcher *watcher) + if (!watcher->watching || watcher->homedir_type != TRASH_WATCHER_TRUSTED) + trash_dir_rescan (watcher->homedir_trashdir); + +- for (mount = watcher->mounts; mount; mount = mount->next) +- if (!watcher->watching || mount->type != TRASH_WATCHER_TRUSTED) +- { +- trash_dir_rescan (mount->dirs[0]); +- trash_dir_rescan (mount->dirs[1]); +- } ++ g_hash_table_iter_init (&iter, watcher->mounts); ++ while (g_hash_table_iter_next (&iter, NULL, &value)) ++ { ++ TrashMount *mount = value; ++ if (!watcher->watching || mount->type != TRASH_WATCHER_TRUSTED) ++ { ++ trash_dir_rescan (mount->dirs[0]); ++ trash_dir_rescan (mount->dirs[1]); ++ } ++ } + } +-- +2.53.0 + diff --git a/trash-Use-GHashTable-for-trash-directory-items.patch b/trash-Use-GHashTable-for-trash-directory-items.patch new file mode 100644 index 0000000..d225c82 --- /dev/null +++ b/trash-Use-GHashTable-for-trash-directory-items.patch @@ -0,0 +1,222 @@ +From 6cc1d7aa763dfbd6103429313b5af025ebb68e50 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 16:01:50 +0200 +Subject: [PATCH] trash: Use GHashTable for trash directory items + +Backport of commit 665ff6b8. + +Adjusted context for target version. +--- + daemon/trashlib/trashdir.c | 122 +++++++++++++++---------------------- + 1 file changed, 48 insertions(+), 74 deletions(-) + +diff --git a/daemon/trashlib/trashdir.c b/daemon/trashlib/trashdir.c +index 707dff6..6b5f21c 100644 +--- a/daemon/trashlib/trashdir.c ++++ b/daemon/trashlib/trashdir.c +@@ -16,7 +16,7 @@ + struct OPAQUE_TYPE__TrashDir + { + TrashRoot *root; +- GSList *items; ++ GHashTable *items; /* basename -> GFile */ + + GFile *directory; + GFile *topdir; +@@ -27,28 +27,6 @@ struct OPAQUE_TYPE__TrashDir + GFileMonitor *monitor; + }; + +-static gint +-compare_basename (gconstpointer a, +- gconstpointer b) +-{ +- GFile *file_a, *file_b; +- char *name_a, *name_b; +- gint result; +- +- file_a = (GFile *) a; +- file_b = (GFile *) b; +- +- name_a = g_file_get_basename (file_a); +- name_b = g_file_get_basename (file_b); +- +- result = strcmp (name_a, name_b); +- +- g_free (name_a); +- g_free (name_b); +- +- return result; +-} +- + static GDateTime * + trash_dir_query_mtime (TrashDir *dir) + { +@@ -68,47 +46,36 @@ trash_dir_query_mtime (TrashDir *dir) + } + + static void +-trash_dir_set_files (TrashDir *dir, +- GSList *items) ++trash_dir_set_files (TrashDir *dir, ++ GHashTable *items) + { +- GSList **old, *new; +- +- items = g_slist_sort (items, (GCompareFunc) compare_basename); +- old = &dir->items; +- new = items; ++ GHashTableIter iter; ++ gpointer key, value; + +- while (new || *old) ++ g_hash_table_iter_init (&iter, dir->items); ++ while (g_hash_table_iter_next (&iter, &key, &value)) + { +- int result; +- +- if ((result = (new == NULL) - (*old == NULL)) == 0) +- result = compare_basename (new->data, (*old)->data); +- +- if (result < 0) +- { +- /* new entry. add it. */ +- *old = g_slist_prepend (*old, new->data); /* take reference */ +- old = &(*old)->next; +- trash_root_add_item (dir->root, new->data, dir->topdir, dir->is_homedir); +- new = new->next; +- } +- else if (result > 0) ++ if (!g_hash_table_contains (items, key)) + { + /* old entry. remove it. */ +- trash_root_remove_item (dir->root, (*old)->data, dir->is_homedir); +- g_object_unref ((*old)->data); +- *old = g_slist_delete_link (*old, *old); ++ trash_root_remove_item (dir->root, value, dir->is_homedir); ++ g_hash_table_iter_remove (&iter); + } +- else ++ } ++ ++ g_hash_table_iter_init (&iter, items); ++ while (g_hash_table_iter_next (&iter, &key, &value)) ++ { ++ if (!g_hash_table_contains (dir->items, key)) + { +- /* match. no change. */ +- old = &(*old)->next; +- g_object_unref (new->data); +- new = new->next; ++ /* new entry. add it. */ ++ g_hash_table_iter_steal (&iter); ++ g_hash_table_insert (dir->items, key, value); ++ trash_root_add_item (dir->root, value, dir->topdir, dir->is_homedir); + } + } + +- g_slist_free (items); ++ g_hash_table_unref (items); + + trash_root_thaw (dir->root); + } +@@ -116,17 +83,24 @@ trash_dir_set_files (TrashDir *dir, + static void + trash_dir_empty (TrashDir *dir) + { +- trash_dir_set_files (dir, NULL); ++ GHashTable *empty; ++ ++ empty = g_hash_table_new (g_str_hash, g_str_equal); ++ trash_dir_set_files (dir, empty); + } + + static void + trash_dir_enumerate (TrashDir *dir) + { + GFileEnumerator *enumerator; +- GSList *files = NULL; ++ GHashTable *files = NULL; + + g_clear_pointer (&dir->mtime, g_date_time_unref); + dir->mtime = trash_dir_query_mtime (dir); ++ ++ files = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, g_object_unref); ++ + enumerator = g_file_enumerate_children (dir->directory, + G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, +@@ -139,10 +113,11 @@ trash_dir_enumerate (TrashDir *dir) + while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL))) + { + GFile *file; ++ const gchar *name; + +- file = g_file_get_child (dir->directory, +- g_file_info_get_name (info)); +- files = g_slist_prepend (files, file); ++ name = g_file_info_get_name (info); ++ file = g_file_get_child (dir->directory, name); ++ g_hash_table_insert (files, g_strdup (name), file); + + g_object_unref (info); + } +@@ -164,24 +139,20 @@ trash_dir_changed (GFileMonitor *monitor, + + if (event_type == G_FILE_MONITOR_EVENT_CREATED) + { +- dir->items = g_slist_insert_sorted (dir->items, +- g_object_ref (file), +- (GCompareFunc) compare_basename); +- trash_root_add_item (dir->root, file, dir->topdir, dir->is_homedir); ++ g_autofree gchar *name = g_file_get_basename (file); ++ ++ if (!g_hash_table_contains (dir->items, name)) ++ { ++ g_hash_table_insert (dir->items, g_steal_pointer (&name), g_object_ref (file)); ++ trash_root_add_item (dir->root, file, dir->topdir, dir->is_homedir); ++ } + } + + else if (event_type == G_FILE_MONITOR_EVENT_DELETED) + { +- GSList *node; ++ g_autofree char *name = g_file_get_basename (file); + +- node = g_slist_find_custom (dir->items, +- file, +- (GCompareFunc) compare_basename); +- if (node) +- { +- g_object_unref (node->data); +- dir->items = g_slist_delete_link (dir->items, node); +- } ++ g_hash_table_remove (dir->items, name); + trash_root_remove_item (dir->root, file, dir->is_homedir); + } + +@@ -392,7 +363,8 @@ trash_dir_new (TrashRoot *root, + dir = g_slice_new (TrashDir); + + dir->root = root; +- dir->items = NULL; ++ dir->items = g_hash_table_new_full (g_str_hash, g_str_equal, ++ g_free, g_object_unref); + dir->topdir = g_file_new_for_path (mount_point); + dir->directory = g_file_get_child (dir->topdir, rel); + dir->monitor = NULL; +@@ -423,7 +395,9 @@ trash_dir_free (TrashDir *dir) + if (dir->monitor) + g_object_unref (dir->monitor); + +- trash_dir_set_files (dir, NULL); ++ trash_dir_empty (dir); ++ ++ g_hash_table_unref (dir->items); + + g_object_unref (dir->directory); + g_object_unref (dir->topdir); +-- +2.53.0 + diff --git a/trash-Use-weak-refs-for-monitor-lifecycle.patch b/trash-Use-weak-refs-for-monitor-lifecycle.patch new file mode 100644 index 0000000..0d1b979 --- /dev/null +++ b/trash-Use-weak-refs-for-monitor-lifecycle.patch @@ -0,0 +1,223 @@ +From 0807d43c845796bf7260480c0349ca523932f953 Mon Sep 17 00:00:00 2001 +From: Ondrej Holy +Date: Tue, 2 Jun 2026 16:01:50 +0200 +Subject: [PATCH] trash: Use weak refs for monitor lifecycle + +Backport of commit 1080ca11. + +Adjusted context for target version. +--- + daemon/gvfsbackendtrash.c | 119 ++++++++++++++++++++++----------- + daemon/trashlib/trashwatcher.c | 6 +- + 2 files changed, 84 insertions(+), 41 deletions(-) + +diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c +index 1dcb44f..6d0af56 100644 +--- a/daemon/gvfsbackendtrash.c ++++ b/daemon/gvfsbackendtrash.c +@@ -33,8 +33,8 @@ struct OPAQUE_TYPE__GVfsBackendTrash + { + GVfsBackend parent_instance; + +- GVfsMonitor *file_monitor; +- GVfsMonitor *dir_monitor; ++ GWeakRef file_monitor; ++ GWeakRef dir_monitor; + + GMainContext *worker_context; + GMainLoop *worker_loop; +@@ -111,15 +111,32 @@ trash_backend_worker_thread_queue (GVfsBackendTrash *backend, + } + + static gboolean +-watch_func (gpointer user_data) ++sync_watch_state_func (gpointer user_data) + { + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data); ++ GVfsMonitor *file_monitor; ++ GVfsMonitor *dir_monitor; + +- trash_watcher_watch (backend->watcher); ++ file_monitor = g_weak_ref_get (&backend->file_monitor); ++ dir_monitor = g_weak_ref_get (&backend->dir_monitor); ++ ++ if (file_monitor != NULL || dir_monitor != NULL) ++ trash_watcher_watch (backend->watcher); ++ else ++ trash_watcher_unwatch (backend->watcher); ++ ++ g_clear_object (&file_monitor); ++ g_clear_object (&dir_monitor); + + return G_SOURCE_REMOVE; + } + ++static void ++trash_backend_queue_watch_state_sync (GVfsBackendTrash *backend) ++{ ++ trash_backend_worker_thread_queue (backend, sync_watch_state_func); ++} ++ + static gboolean + rescan_func (gpointer user_data) + { +@@ -142,46 +159,62 @@ is_root (const char *filename) + return (filename[0] == '/' && filename[1] == '\0'); + } + ++static void ++trash_backend_file_monitor_destroyed (gpointer data, ++ GObject *where_the_object_was) ++{ ++ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (data); ++ ++ trash_backend_queue_watch_state_sync (backend); ++} ++ ++static void ++trash_backend_dir_monitor_destroyed (gpointer data, ++ GObject *where_the_object_was) ++{ ++ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (data); ++ ++ trash_backend_queue_watch_state_sync (backend); ++} ++ + static GVfsMonitor * + trash_backend_get_file_monitor (GVfsBackendTrash *backend, + gboolean create) + { +- if (backend->file_monitor == NULL && create == FALSE) +- return NULL; ++ GVfsMonitor *monitor; + +- else if (backend->file_monitor == NULL) +- { +- /* 'create' is only ever set in the main thread, so we will have +- * no possibility here for creating more than one new monitor. +- */ +- if (backend->dir_monitor == NULL) +- trash_backend_worker_thread_queue (backend, watch_func); ++ monitor = g_weak_ref_get (&backend->file_monitor); ++ if (monitor != NULL || create == FALSE) ++ return monitor; + +- backend->file_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); +- } ++ monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); ++ g_weak_ref_set (&backend->file_monitor, monitor); ++ g_object_weak_ref (G_OBJECT (monitor), ++ trash_backend_file_monitor_destroyed, backend); ++ ++ trash_backend_queue_watch_state_sync (backend); + +- return g_object_ref (backend->file_monitor); ++ return monitor; + } + + static GVfsMonitor * + trash_backend_get_dir_monitor (GVfsBackendTrash *backend, + gboolean create) + { +- if (backend->dir_monitor == NULL && create == FALSE) +- return NULL; ++ GVfsMonitor *monitor; + +- else if (backend->dir_monitor == NULL) +- { +- /* 'create' is only ever set in the main thread, so we will have +- * no possibility here for creating more than one new monitor. +- */ +- if (backend->file_monitor == NULL) +- trash_backend_worker_thread_queue (backend, watch_func); ++ monitor = g_weak_ref_get (&backend->dir_monitor); ++ if (monitor != NULL || create == FALSE) ++ return monitor; + +- backend->dir_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); +- } ++ monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend)); ++ g_weak_ref_set (&backend->dir_monitor, monitor); ++ g_object_weak_ref (G_OBJECT (monitor), ++ trash_backend_dir_monitor_destroyed, backend); ++ ++ trash_backend_queue_watch_state_sync (backend); + +- return g_object_ref (backend->dir_monitor); ++ return monitor; + } + + static void +@@ -802,8 +835,8 @@ trash_backend_mount (GVfsBackend *vfs_backend, + { + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend); + +- backend->file_monitor = NULL; +- backend->dir_monitor = NULL; ++ g_weak_ref_init (&backend->file_monitor, NULL); ++ g_weak_ref_init (&backend->dir_monitor, NULL); + + backend->worker_context = g_main_context_new (); + backend->worker_thread = g_thread_new ("Trash Worker Thread", +@@ -976,17 +1009,25 @@ static void + trash_backend_finalize (GObject *object) + { + GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (object); ++ GVfsMonitor *monitor; + +- /* get rid of these first to stop a flood of event notifications +- * from being emitted while we're tearing down the TrashWatcher +- */ +- if (backend->file_monitor) +- g_object_unref (backend->file_monitor); +- backend->file_monitor = NULL; ++ monitor = g_weak_ref_get (&backend->file_monitor); ++ if (monitor != NULL) ++ { ++ g_object_weak_unref (G_OBJECT (monitor), ++ trash_backend_file_monitor_destroyed, backend); ++ g_object_unref (monitor); ++ } ++ g_weak_ref_clear (&backend->file_monitor); + +- if (backend->dir_monitor) +- g_object_unref (backend->dir_monitor); +- backend->dir_monitor = NULL; ++ monitor = g_weak_ref_get (&backend->dir_monitor); ++ if (monitor != NULL) ++ { ++ g_object_weak_unref (G_OBJECT (monitor), ++ trash_backend_dir_monitor_destroyed, backend); ++ g_object_unref (monitor); ++ } ++ g_weak_ref_clear (&backend->dir_monitor); + + G_OBJECT_CLASS (g_vfs_backend_trash_parent_class)->finalize (object); + } +diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c +index 72b67e4..3c1aea1 100644 +--- a/daemon/trashlib/trashwatcher.c ++++ b/daemon/trashlib/trashwatcher.c +@@ -384,7 +384,8 @@ trash_watcher_watch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + +- g_assert (!watcher->watching); ++ if (watcher->watching) ++ return; + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_watch (watcher->homedir_trashdir); +@@ -409,7 +410,8 @@ trash_watcher_unwatch (TrashWatcher *watcher) + GHashTableIter iter; + gpointer value; + +- g_assert (watcher->watching); ++ if (!watcher->watching) ++ return; + + if (watcher->homedir_type != TRASH_WATCHER_NO_WATCH) + trash_dir_unwatch (watcher->homedir_trashdir); +-- +2.53.0 +