Improve trash backend performance

Resolves: RHEL-138575
This commit is contained in:
Ondrej Holy 2026-06-03 12:29:54 +02:00
parent 862bb9e6e0
commit f53f2a2fed
11 changed files with 1218 additions and 1 deletions

View File

@ -0,0 +1,47 @@
From 9e38c56f99372b2474a780ac0c7c7d9b38e490cf Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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

View File

@ -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 <oholy@redhat.com> - 1.54.4-5
- Improve trash backend performance
Resolves: RHEL-138575
* Fri Feb 06 2026 Milan Crha <mcrha@redhat.com> - 1.54.4-4
- udisks2: Correct add to hash table for items which can clash in the monitor (RHEL-143935)

View File

@ -0,0 +1,60 @@
From 18e795eda715947b42a940b277a51b8e36b64062 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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

View File

@ -0,0 +1,30 @@
From f1cdb812f8fa6f06239183923f920025a05e0712 Mon Sep 17 00:00:00 2001
From: Maximiliano Sandoval <msandova@gnome.org>
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

View File

@ -0,0 +1,84 @@
From c6d70b441f9c6f50a844808e6ebfa0651b0a7f8f Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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

View File

@ -0,0 +1,119 @@
From 181da9f0d10a9e75795db5bb138214a89382271f Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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

View File

@ -0,0 +1,88 @@
From 68a056dd3764c795c5d9d5844e2cc3f20f9eaf1d Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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 <noreply@anthropic.com>
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

View File

@ -0,0 +1,47 @@
From 163dc91fe3c1b6fac6d8aed947b66d8c32ccc010 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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

View File

@ -0,0 +1,280 @@
From 0d3023a9e7303c7786f6510643fb7cad62e36568 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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

View File

@ -0,0 +1,222 @@
From 6cc1d7aa763dfbd6103429313b5af025ebb68e50 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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

View File

@ -0,0 +1,223 @@
From 0807d43c845796bf7260480c0349ca523932f953 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
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