Improve trash backend performance

Resolves: RHEL-127445
This commit is contained in:
Ondrej Holy 2026-06-03 12:30:27 +02:00
parent c64ab34ccb
commit b57f602cb4
18 changed files with 2049 additions and 1 deletions

View File

@ -0,0 +1,43 @@
From d1ab5870e828df259897598298c2ca66b587426a Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Wed, 29 Aug 2018 15:36:57 +0200
Subject: [PATCH] backend: Prevent usage of NULL
g_dbus_connection_get_peer_credentials may fail in some cases, see
the documentation, and thus we have to check the return value before
use in order to prevent warnings like the following:
g_credentials_get_unix_pid: assertion 'G_IS_CREDENTIALS (credentials)' failed
Print simply -1 if pid can't be obtained this way.
---
daemon/gvfsbackend.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/daemon/gvfsbackend.c b/daemon/gvfsbackend.c
index 673070fa..4fd3455c 100644
--- a/daemon/gvfsbackend.c
+++ b/daemon/gvfsbackend.c
@@ -609,14 +609,17 @@ g_vfs_backend_invocation_first_handler (GVfsDBusMount *object,
{
GDBusConnection *connection;
GCredentials *credentials;
+ pid_t pid = -1;
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=%u)\n",
+ 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),
- g_credentials_get_unix_pid (credentials, NULL));
+ (long)pid);
if (backend->priv->block_requests)
{
--
2.53.0

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

@ -25,7 +25,7 @@
Name: gvfs
Version: 1.36.2
Release: 20%{?dist}
Release: 21%{?dist}
Summary: Backends for the gio framework in GLib
License: GPLv3 and LGPLv2+ and BSD and MPLv2.0
@ -99,6 +99,29 @@ Patch28: udisks2-monitor-performance-273.patch
Patch29: udisks2-monitor-performance-290.patch
Patch30: udisks2-monitor-performance-297.patch
# Improve trash backend performance
# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/287
# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/175
# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/169
# https://gitlab.gnome.org/GNOME/gvfs/-/merge_requests/11
Patch31: backend-Prevent-usage-of-NULL.patch
Patch32: trash-Add-is_root-function.patch
Patch33: trash-Remove-unused-ui_hook-property.patch
Patch34: trash-Remove-unused-is_homedir-property.patch
Patch35: trash-Do-not-create-monitors-for-non-root-items.patch
Patch36: trash-Run-blocking-methods-on-a-thread-pool.patch
Patch37: trash-Add-worker-thread.patch
Patch38: trash-Chain-up-finalize.patch
Patch39: trash-Rate-limit-mount-updates.patch
Patch40: trash-Add-debug-prints-for-mounts-handling.patch
Patch41: trash-Use-GHashTable-for-mount-tracking.patch
Patch42: trash-Use-GHashTable-for-trash-directory-items.patch
Patch43: trash-Rate-limit-size-change-notifications.patch
Patch44: trash-Use-weak-refs-for-monitor-lifecycle.patch
Patch45: trash-Defer-mount-monitoring-to-active-watchers-only.patch
Patch46: daemon-Add-process-name-to-debug-handler-log.patch
Patch47: trash-Skip-mount-table-reparsing-when-unchanged.patch
BuildRequires: pkgconfig
BuildRequires: pkgconfig(glib-2.0) >= %{glib2_version}
BuildRequires: pkgconfig(dbus-glib-1)
@ -489,6 +512,10 @@ killall -USR1 gvfsd >&/dev/null || :
%{_datadir}/installed-tests
%changelog
* Tue Jun 02 2026 Ondrej Holy <oholy@redhat.com> - 1.36.2-21
- Improve trash backend performance
Resolves: RHEL-127445
* Fri Feb 06 2026 Milan Crha <mcrha@redhat.com> - 1.36.2-20
- udisks2: Correct add to hash table for items which can clash in the monitor (RHEL-76484)

View File

@ -0,0 +1,60 @@
From 78cc4e391d4f9e6e17af83e980f9d819b7fd208e Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Tue, 2 Jun 2026 15:32:58 +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 fe2ee44..c195234 100644
--- a/daemon/trashlib/trashwatcher.c
+++ b/daemon/trashlib/trashwatcher.c
@@ -245,12 +245,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
@@ -271,6 +281,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);
@@ -281,6 +293,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,94 @@
From 86805f19dbcb55a4ad6fa2a00670d00b927c9d47 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Thu, 19 Jan 2023 12:57:30 +0100
Subject: [PATCH] trash: Add is_root function
Let's make the code more readable using the is_root function instead
of char comparison.
---
daemon/gvfsbackendtrash.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c
index 9702b860..d7ec4f77 100644
--- a/daemon/gvfsbackendtrash.c
+++ b/daemon/gvfsbackendtrash.c
@@ -44,6 +44,12 @@ struct OPAQUE_TYPE__GVfsBackendTrash
G_DEFINE_TYPE (GVfsBackendTrash, g_vfs_backend_trash, G_VFS_TYPE_BACKEND);
+static gboolean
+is_root (const char *filename)
+{
+ return (filename[0] == '/' && filename[1] == '\0');
+}
+
static GVfsMonitor *
trash_backend_get_file_monitor (GVfsBackendTrash *backend,
gboolean create)
@@ -229,7 +235,7 @@ trash_backend_open_for_read (GVfsBackend *vfs_backend,
GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
GError *error = NULL;
- if (filename[1] == '\0')
+ if (is_root (filename))
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
_("Cant open directory"));
@@ -397,7 +403,7 @@ trash_backend_delete (GVfsBackend *vfs_backend,
GError *error = NULL;
g_debug ("before job: %d\n", G_OBJECT(job)->ref_count);
- if (filename[1] == '\0')
+ if (is_root (filename))
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
_("The trash folder may not be deleted"));
else
@@ -456,7 +462,7 @@ trash_backend_pull (GVfsBackend *vfs_backend,
GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
GError *error = NULL;
- if (source[1] == '\0')
+ if (is_root (source))
g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
_("The trash folder may not be deleted"));
else
@@ -683,7 +689,7 @@ trash_backend_enumerate (GVfsBackend *vfs_backend,
trash_watcher_rescan (backend->watcher);
- if (filename[1])
+ if (!is_root (filename))
trash_backend_enumerate_non_root (backend, job, filename,
attribute_matcher, flags);
else
@@ -729,7 +735,7 @@ trash_backend_query_info (GVfsBackend *vfs_backend,
if (!backend->file_monitor && !backend->dir_monitor)
trash_watcher_rescan (backend->watcher);
- if (filename[1])
+ if (!is_root (filename))
{
GError *error = NULL;
gboolean is_toplevel;
@@ -832,7 +838,7 @@ trash_backend_create_dir_monitor (GVfsBackend *vfs_backend,
GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
GVfsMonitor *monitor;
- if (filename[1])
+ if (!is_root (filename))
monitor = g_vfs_monitor_new (vfs_backend);
else
monitor = trash_backend_get_dir_monitor (backend, TRUE);
@@ -853,7 +859,7 @@ trash_backend_create_file_monitor (GVfsBackend *vfs_backend,
GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (vfs_backend);
GVfsMonitor *monitor;
- if (filename[1])
+ if (!is_root (filename))
monitor = g_vfs_monitor_new (vfs_backend);
else
monitor = trash_backend_get_file_monitor (backend, TRUE);
--
2.53.0

View File

@ -0,0 +1,295 @@
From 890b2a747bbcd9ed220fb217aba41b9ef0188ba1 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Wed, 7 Jun 2023 15:49:41 +0200
Subject: [PATCH] trash: Add worker thread
The previous commit makes the backend a bit more resilience of against
stale NFS mounts, but still a lot of I/O operations (e.g.
`trash_watcher_rescan`) run on the main thread and can easily block the
whole backend. This is especially problem for the `create_dir_monitor` and
`create_file_monitor` methods that doesn't have asynchronous variants.
This may cause hangs for nautilus, or file chooser dialog, but also for
the whole gnome-shell session (resp. desktop-icons extension). Let's add
a worker thread to ensure that the `create_dir_monitor` and
`create_file_monitor` methods response immediately.
Related: https://bugzilla.redhat.com/show_bug.cgi?id=2152538
---
daemon/gvfsbackendtrash.c | 156 ++++++++++++++++++++++++++++++++------
1 file changed, 131 insertions(+), 25 deletions(-)
diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c
index b4d3d089..0966242a 100644
--- a/daemon/gvfsbackendtrash.c
+++ b/daemon/gvfsbackendtrash.c
@@ -36,6 +36,10 @@ struct OPAQUE_TYPE__GVfsBackendTrash
GVfsMonitor *file_monitor;
GVfsMonitor *dir_monitor;
+ GMainContext *worker_context;
+ GMainLoop *worker_loop;
+ GThread *worker_thread;
+
TrashWatcher *watcher;
TrashRoot *root;
@@ -44,6 +48,94 @@ struct OPAQUE_TYPE__GVfsBackendTrash
G_DEFINE_TYPE (GVfsBackendTrash, g_vfs_backend_trash, G_VFS_TYPE_BACKEND);
+typedef struct
+{
+ GSourceFunc source_func;
+ gpointer user_data;
+ GMutex mutex;
+ GCond cond;
+ gboolean completed;
+} ContextInvokeData;
+
+static gboolean
+source_func_wrapper (gpointer user_data)
+{
+ ContextInvokeData *data = user_data;
+
+ g_mutex_lock (&data->mutex);
+
+ while (data->source_func (data->user_data));
+ data->completed = TRUE;
+
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&data->mutex);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+trash_backend_worker_thread_queue_and_wait (GVfsBackendTrash *backend,
+ GSourceFunc source_func)
+{
+ ContextInvokeData data;
+
+ data.source_func = source_func;
+ data.user_data = backend;
+
+ g_mutex_init (&data.mutex);
+ g_cond_init (&data.cond);
+ data.completed = FALSE;
+
+ g_mutex_lock (&data.mutex);
+
+ g_main_context_invoke (backend->worker_context,
+ source_func_wrapper,
+ &data);
+
+ while (!data.completed)
+ {
+ g_cond_wait (&data.cond, &data.mutex);
+ }
+
+ g_mutex_unlock (&data.mutex);
+
+ g_mutex_clear (&data.mutex);
+ g_cond_clear (&data.cond);
+}
+
+static void
+trash_backend_worker_thread_queue (GVfsBackendTrash *backend,
+ GSourceFunc source_func)
+{
+ g_main_context_invoke (backend->worker_context, source_func, backend);
+}
+
+static gboolean
+watch_func (gpointer user_data)
+{
+ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data);
+
+ trash_watcher_watch (backend->watcher);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+rescan_func (gpointer user_data)
+{
+ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data);
+
+ trash_watcher_rescan (backend->watcher);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+ready_func (gpointer user_data)
+{
+ return G_SOURCE_REMOVE;
+}
+
static gboolean
is_root (const char *filename)
{
@@ -63,7 +155,7 @@ trash_backend_get_file_monitor (GVfsBackendTrash *backend,
* no possibility here for creating more than one new monitor.
*/
if (backend->dir_monitor == NULL)
- trash_watcher_watch (backend->watcher);
+ trash_backend_worker_thread_queue (backend, watch_func);
backend->file_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend));
}
@@ -84,7 +176,7 @@ trash_backend_get_dir_monitor (GVfsBackendTrash *backend,
* no possibility here for creating more than one new monitor.
*/
if (backend->file_monitor == NULL)
- trash_watcher_watch (backend->watcher);
+ trash_backend_worker_thread_queue (backend, watch_func);
backend->dir_monitor = g_vfs_monitor_new (G_VFS_BACKEND (backend));
}
@@ -164,7 +256,6 @@ trash_backend_item_count_changed (gpointer user_data)
}
}
-
static GFile *
trash_backend_get_file (GVfsBackendTrash *backend,
const char *filename,
@@ -177,6 +268,8 @@ trash_backend_get_file (GVfsBackendTrash *backend,
TrashItem *item;
GFile *file;
+ trash_backend_worker_thread_queue_and_wait (backend, rescan_func);
+
file = NULL;
filename++;
@@ -243,9 +336,6 @@ trash_backend_open_for_read (GVfsBackend *vfs_backend,
{
GFile *real;
- if (!backend->file_monitor && !backend->dir_monitor)
- trash_watcher_rescan (backend->watcher);
-
real = trash_backend_get_file (backend, filename, NULL, NULL, &error);
if (real)
@@ -404,9 +494,6 @@ trash_backend_delete (GVfsBackend *vfs_backend,
TrashItem *item;
GFile *real;
- if (!backend->file_monitor && !backend->dir_monitor)
- trash_watcher_rescan (backend->watcher);
-
real = trash_backend_get_file (backend, filename,
&item, &is_toplevel, &error);
@@ -461,9 +548,6 @@ trash_backend_pull (GVfsBackend *vfs_backend,
TrashItem *item;
GFile *real;
- if (!backend->file_monitor && !backend->dir_monitor)
- trash_watcher_rescan (backend->watcher);
-
real = trash_backend_get_file (backend, source, &item,
&is_toplevel, &error);
@@ -585,6 +669,8 @@ trash_backend_enumerate_root (GVfsBackendTrash *backend,
g_vfs_job_succeeded (G_VFS_JOB (job));
+ trash_backend_worker_thread_queue_and_wait (backend, rescan_func);
+
items = trash_root_get_items (backend->root);
for (node = items; node; node = node->next)
@@ -675,8 +761,6 @@ trash_backend_enumerate (GVfsBackend *vfs_backend,
g_assert (filename[0] == '/');
- trash_watcher_rescan (backend->watcher);
-
if (!is_root (filename))
trash_backend_enumerate_non_root (backend, job, filename,
attribute_matcher, flags);
@@ -684,6 +768,31 @@ trash_backend_enumerate (GVfsBackend *vfs_backend,
trash_backend_enumerate_root (backend, job, attribute_matcher, flags);
}
+static gpointer
+thread_func (gpointer user_data)
+{
+ GVfsBackendTrash *backend = G_VFS_BACKEND_TRASH (user_data);
+
+ g_main_context_push_thread_default (backend->worker_context);
+ backend->worker_loop = g_main_loop_new (backend->worker_context, FALSE);
+
+ backend->root = trash_root_new (trash_backend_item_created,
+ trash_backend_item_deleted,
+ trash_backend_item_count_changed,
+ backend);
+ backend->watcher = trash_watcher_new (backend->root);
+
+ g_main_loop_run (backend->worker_loop);
+
+ trash_watcher_free (backend->watcher);
+ trash_root_free (backend->root);
+
+ g_main_context_pop_thread_default (backend->worker_context);
+ g_main_loop_unref (backend->worker_loop);
+
+ return NULL;
+}
+
static void
trash_backend_mount (GVfsBackend *vfs_backend,
GVfsJobMount *job,
@@ -695,11 +804,12 @@ trash_backend_mount (GVfsBackend *vfs_backend,
backend->file_monitor = NULL;
backend->dir_monitor = NULL;
- backend->root = trash_root_new (trash_backend_item_created,
- trash_backend_item_deleted,
- trash_backend_item_count_changed,
- backend);
- backend->watcher = trash_watcher_new (backend->root);
+
+ backend->worker_context = g_main_context_new ();
+ backend->worker_thread = g_thread_new ("Trash Worker Thread",
+ thread_func,
+ backend);
+ trash_backend_worker_thread_queue_and_wait (backend, ready_func);
g_vfs_job_succeeded (G_VFS_JOB (job));
}
@@ -716,9 +826,6 @@ trash_backend_query_info (GVfsBackend *vfs_backend,
g_assert (filename[0] == '/');
- if (!backend->file_monitor && !backend->dir_monitor)
- trash_watcher_rescan (backend->watcher);
-
if (!is_root (filename))
{
GError *error = NULL;
@@ -762,6 +869,8 @@ trash_backend_query_info (GVfsBackend *vfs_backend,
GIcon *icon;
int n_items;
+ trash_backend_worker_thread_queue_and_wait (backend, rescan_func);
+
n_items = trash_root_get_n_items (backend->root);
g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
@@ -878,9 +987,6 @@ trash_backend_finalize (GObject *object)
if (backend->dir_monitor)
g_object_unref (backend->dir_monitor);
backend->dir_monitor = NULL;
-
- trash_watcher_free (backend->watcher);
- trash_root_free (backend->root);
}
static void
--
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 5e34cc212b32e58fd7b6721d92548e983fecd991 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Tue, 2 Jun 2026 15:54:33 +0200
Subject: [PATCH] trash: Defer mount monitoring to active watchers only
Backport of commit b53d45db.
Adjusted context for target version.
---
daemon/trashlib/trashwatcher.c | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/daemon/trashlib/trashwatcher.c b/daemon/trashlib/trashwatcher.c
index bd658e1..3a1bc9f 100644
--- a/daemon/trashlib/trashwatcher.c
+++ b/daemon/trashlib/trashwatcher.c
@@ -139,6 +139,7 @@ struct OPAQUE_TYPE__TrashWatcher
TrashRoot *root;
GUnixMountMonitor *mount_monitor;
+ gulong mounts_changed_id;
GHashTable *mounts; /* mount_path -> TrashMount */
guint update_id;
@@ -339,8 +340,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");
@@ -378,9 +378,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);
@@ -404,9 +412,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);
@@ -430,7 +446,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,57 @@
From 85a789c800ef2773af6eb8884fef746d67d3cafe Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Fri, 20 Jan 2023 09:17:00 +0100
Subject: [PATCH] trash: Do not create monitors for non-root items
The `create_dir_monitor` and `create_file_monitor` methods never fail
currently. However, no events are emitted for non-root items. Let's
return the `G_IO_ERROR_NOT_SUPPORTED` error for non-root items instead.
---
daemon/gvfsbackendtrash.c | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c
index d7ec4f77..76c9e006 100644
--- a/daemon/gvfsbackendtrash.c
+++ b/daemon/gvfsbackendtrash.c
@@ -839,10 +839,15 @@ trash_backend_create_dir_monitor (GVfsBackend *vfs_backend,
GVfsMonitor *monitor;
if (!is_root (filename))
- monitor = g_vfs_monitor_new (vfs_backend);
- else
- monitor = trash_backend_get_dir_monitor (backend, TRUE);
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Operation not supported"));
+ return TRUE;
+ }
+ monitor = trash_backend_get_dir_monitor (backend, TRUE);
g_vfs_job_create_monitor_set_monitor (job, monitor);
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (monitor);
@@ -860,10 +865,15 @@ trash_backend_create_file_monitor (GVfsBackend *vfs_backend,
GVfsMonitor *monitor;
if (!is_root (filename))
- monitor = g_vfs_monitor_new (vfs_backend);
- else
- monitor = trash_backend_get_file_monitor (backend, TRUE);
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Operation not supported"));
+ return TRUE;
+ }
+ monitor = trash_backend_get_file_monitor (backend, TRUE);
g_vfs_job_create_monitor_set_monitor (job, monitor);
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (monitor);
--
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,24 @@
From f8504e445976830287dcb44a51514dc4c15fe528 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Wed, 7 Jun 2023 15:50:51 +0200
Subject: [PATCH] trash: Remove unused is_homedir property
---
daemon/trashlib/trashitem.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/daemon/trashlib/trashitem.c b/daemon/trashlib/trashitem.c
index 08912856..a5b5b166 100644
--- a/daemon/trashlib/trashitem.c
+++ b/daemon/trashlib/trashitem.c
@@ -29,7 +29,6 @@ struct OPAQUE_TYPE__TrashRoot
gpointer user_data;
GHashTable *item_table;
- gboolean is_homedir;
int old_size;
};
--
2.53.0

View File

@ -0,0 +1,68 @@
From 07f55a1dae9af0852fed9d774a6ab7d5a924d5e1 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Fri, 20 Jan 2023 15:43:10 +0100
Subject: [PATCH] trash: Remove unused ui_hook property
---
daemon/trashlib/trashdir.c | 11 -----------
daemon/trashlib/trashdir.h | 5 -----
2 files changed, 16 deletions(-)
diff --git a/daemon/trashlib/trashdir.c b/daemon/trashlib/trashdir.c
index 0d7d2b1b..707dff6c 100644
--- a/daemon/trashlib/trashdir.c
+++ b/daemon/trashlib/trashdir.c
@@ -373,8 +373,6 @@ trash_dir_rescan (TrashDir *dir)
trash_dir_empty (dir);
}
-static trash_dir_ui_hook ui_hook;
-
TrashDir *
trash_dir_new (TrashRoot *root,
gboolean watching,
@@ -411,20 +409,11 @@ trash_dir_new (TrashRoot *root,
else
dir->watch = NULL;
- if (ui_hook)
- ui_hook (dir, dir->directory);
-
g_free (rel);
return dir;
}
-void
-trash_dir_set_ui_hook (trash_dir_ui_hook _ui_hook)
-{
- ui_hook = _ui_hook;
-}
-
void
trash_dir_free (TrashDir *dir)
{
diff --git a/daemon/trashlib/trashdir.h b/daemon/trashlib/trashdir.h
index 5612f9ba..4c39d516 100644
--- a/daemon/trashlib/trashdir.h
+++ b/daemon/trashlib/trashdir.h
@@ -15,9 +15,6 @@
typedef struct OPAQUE_TYPE__TrashDir TrashDir;
-typedef void (*trash_dir_ui_hook) (TrashDir *dir,
- GFile *directory);
-
TrashDir *trash_dir_new (TrashRoot *root,
gboolean watching,
gboolean is_homedir,
@@ -31,6 +28,4 @@ void trash_dir_watch (TrashDir *dir);
void trash_dir_unwatch (TrashDir *dir);
void trash_dir_rescan (TrashDir *dir);
-void trash_dir_set_ui_hook (trash_dir_ui_hook ui_hook);
-
#endif /* _trashdir_h_ */
--
2.53.0

View File

@ -0,0 +1,241 @@
From 3889e4dec938a8023b8120545d4b53083b209dab Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Thu, 19 Jan 2023 12:12:52 +0100
Subject: [PATCH] trash: Run blocking methods on a thread pool
All jobs use `_try` methods currently. This is wrong as most of them can
block. This works quite ok except the case of stale NFS mounts that can
block the main loop forever. Let's run the jobs on a thread pool instead.
This reverts commit 01fe5a61 except for the `create_dir_monitor` and
`create_file_monitor` methods in order to avoid issues like
https://bugzilla.gnome.org/show_bug.cgi?id=723305.
---
daemon/gvfsbackendtrash.c | 68 ++++++++++++++-------------------------
1 file changed, 25 insertions(+), 43 deletions(-)
diff --git a/daemon/gvfsbackendtrash.c b/daemon/gvfsbackendtrash.c
index 76c9e006..b4d3d089 100644
--- a/daemon/gvfsbackendtrash.c
+++ b/daemon/gvfsbackendtrash.c
@@ -227,7 +227,7 @@ trash_backend_get_file (GVfsBackendTrash *backend,
}
/* ======================= method implementations ======================= */
-static gboolean
+static void
trash_backend_open_for_read (GVfsBackend *vfs_backend,
GVfsJobOpenForRead *job,
const char *filename)
@@ -261,18 +261,16 @@ trash_backend_open_for_read (GVfsBackend *vfs_backend,
g_vfs_job_open_for_read_set_can_seek (job, TRUE);
g_vfs_job_succeeded (G_VFS_JOB (job));
- return TRUE;
+ return;
}
}
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
-
- return TRUE;
}
-static gboolean
+static void
trash_backend_read (GVfsBackend *backend,
GVfsJobRead *job,
GVfsBackendHandle handle,
@@ -290,16 +288,14 @@ trash_backend_read (GVfsBackend *backend,
g_vfs_job_read_set_size (job, bytes);
g_vfs_job_succeeded (G_VFS_JOB (job));
- return TRUE;
+ return;
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
-
- return TRUE;
}
-static gboolean
+static void
trash_backend_seek_on_read (GVfsBackend *backend,
GVfsJobSeekRead *job,
GVfsBackendHandle handle,
@@ -313,13 +309,11 @@ trash_backend_seek_on_read (GVfsBackend *backend,
g_vfs_job_seek_read_set_offset (job, g_seekable_tell (handle));
g_vfs_job_succeeded (G_VFS_JOB (job));
- return TRUE;
+ return;
}
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
-
- return TRUE;
}
static void
@@ -349,7 +343,7 @@ trash_backend_query_info_on_read (GVfsBackend *backend,
}
}
-static gboolean
+static void
trash_backend_close_read (GVfsBackend *backend,
GVfsJobCloseRead *job,
GVfsBackendHandle handle)
@@ -361,15 +355,13 @@ trash_backend_close_read (GVfsBackend *backend,
g_vfs_job_succeeded (G_VFS_JOB (job));
g_object_unref (handle);
- return TRUE;
+ return;
}
g_object_unref (handle);
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
-
- return TRUE;
}
static gboolean
@@ -394,7 +386,7 @@ trash_backend_schedule_thaw (GVfsBackendTrash *backend)
backend);
}
-static gboolean
+static void
trash_backend_delete (GVfsBackend *vfs_backend,
GVfsJobDelete *job,
const char *filename)
@@ -435,7 +427,7 @@ trash_backend_delete (GVfsBackend *vfs_backend,
g_vfs_job_succeeded (G_VFS_JOB (job));
trash_item_unref (item);
- return TRUE;
+ return;
}
}
@@ -445,11 +437,9 @@ trash_backend_delete (GVfsBackend *vfs_backend,
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
-
- return TRUE;
}
-static gboolean
+static void
trash_backend_pull (GVfsBackend *vfs_backend,
GVfsJobPull *job,
const gchar *source,
@@ -505,7 +495,7 @@ trash_backend_pull (GVfsBackend *vfs_backend,
trash_item_unref (item);
g_object_unref (real);
- return TRUE;
+ return;
}
}
@@ -517,8 +507,6 @@ trash_backend_pull (GVfsBackend *vfs_backend,
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_error_free (error);
-
- return TRUE;
}
static void
@@ -676,7 +664,7 @@ trash_backend_enumerate_non_root (GVfsBackendTrash *backend,
g_error_free (error);
}
-static gboolean
+static void
trash_backend_enumerate (GVfsBackend *vfs_backend,
GVfsJobEnumerate *job,
const char *filename,
@@ -694,11 +682,9 @@ trash_backend_enumerate (GVfsBackend *vfs_backend,
attribute_matcher, flags);
else
trash_backend_enumerate_root (backend, job, attribute_matcher, flags);
-
- return TRUE;
}
-static gboolean
+static void
trash_backend_mount (GVfsBackend *vfs_backend,
GVfsJobMount *job,
GMountSpec *mount_spec,
@@ -716,11 +702,9 @@ trash_backend_mount (GVfsBackend *vfs_backend,
backend->watcher = trash_watcher_new (backend->root);
g_vfs_job_succeeded (G_VFS_JOB (job));
-
- return TRUE;
}
-static gboolean
+static void
trash_backend_query_info (GVfsBackend *vfs_backend,
GVfsJobQueryInfo *job,
const char *filename,
@@ -764,7 +748,7 @@ trash_backend_query_info (GVfsBackend *vfs_backend,
trash_item_unref (item);
g_object_unref (real_info);
- return TRUE;
+ return;
}
trash_item_unref (item);
@@ -798,8 +782,6 @@ trash_backend_query_info (GVfsBackend *vfs_backend,
g_vfs_job_succeeded (G_VFS_JOB (job));
}
-
- return TRUE;
}
static gboolean
@@ -926,17 +908,17 @@ g_vfs_backend_trash_class_init (GVfsBackendTrashClass *class)
gobject_class->finalize = trash_backend_finalize;
- backend_class->try_mount = trash_backend_mount;
- backend_class->try_open_for_read = trash_backend_open_for_read;
- backend_class->try_read = trash_backend_read;
- backend_class->try_seek_on_read = trash_backend_seek_on_read;
+ backend_class->mount = trash_backend_mount;
+ backend_class->open_for_read = trash_backend_open_for_read;
+ backend_class->read = trash_backend_read;
+ backend_class->seek_on_read = trash_backend_seek_on_read;
backend_class->query_info_on_read = trash_backend_query_info_on_read;
- backend_class->try_close_read = trash_backend_close_read;
- backend_class->try_query_info = trash_backend_query_info;
+ backend_class->close_read = trash_backend_close_read;
+ backend_class->query_info = trash_backend_query_info;
backend_class->try_query_fs_info = trash_backend_query_fs_info;
- backend_class->try_enumerate = trash_backend_enumerate;
- backend_class->try_delete = trash_backend_delete;
- backend_class->try_pull = trash_backend_pull;
+ backend_class->enumerate = trash_backend_enumerate;
+ backend_class->delete = trash_backend_delete;
+ backend_class->pull = trash_backend_pull;
backend_class->try_create_dir_monitor = trash_backend_create_dir_monitor;
backend_class->try_create_file_monitor = trash_backend_create_file_monitor;
}
--
2.53.0

View File

@ -0,0 +1,47 @@
From 1ae1e68794a01b524d91c3da0addeca4d060b795 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Tue, 2 Jun 2026 15:54:37 +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 e44ab06..59303f0 100644
--- a/daemon/trashlib/trashwatcher.c
+++ b/daemon/trashlib/trashwatcher.c
@@ -142,6 +142,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;
@@ -246,7 +247,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)
@@ -339,6 +343,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 0f166b6f4f3e38144c779b3e6b55d777c6637b0e Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Wed, 3 Jun 2026 08:37:56 +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 d8d6ef8..426f97f 100644
--- a/daemon/trashlib/trashwatcher.c
+++ b/daemon/trashlib/trashwatcher.c
@@ -139,7 +139,7 @@ struct OPAQUE_TYPE__TrashWatcher
TrashRoot *root;
GUnixMountMonitor *mount_monitor;
- TrashMount *mounts;
+ GHashTable *mounts; /* mount_path -> TrashMount */
guint update_id;
TrashDir *homedir_trashdir;
@@ -153,16 +153,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;
@@ -192,24 +189,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);
}
@@ -249,63 +237,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
@@ -345,7 +334,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 ();
@@ -385,19 +374,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;
}
@@ -405,19 +399,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;
}
@@ -425,7 +424,8 @@ trash_watcher_unwatch (TrashWatcher *watcher)
void
trash_watcher_rescan (TrashWatcher *watcher)
{
- TrashMount *mount;
+ GHashTableIter iter;
+ gpointer value;
if (watcher->update_id != 0)
{
@@ -436,10 +436,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.54.0

View File

@ -0,0 +1,221 @@
From 97d9831042ca1c5c13fdb6cd9bfb2e2fe77f94f8 Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Wed, 3 Jun 2026 08:45:00 +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 23aa0d7..610f2c3 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 void
trash_dir_query_mtime (TrashDir *dir, GTimeVal *mtime)
{
@@ -70,47 +48,36 @@ trash_dir_query_mtime (TrashDir *dir, GTimeVal *mtime)
}
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);
}
@@ -118,16 +85,23 @@ 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;
trash_dir_query_mtime (dir, &dir->mtime);
+
+ 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,
@@ -140,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);
}
@@ -165,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);
}
@@ -386,7 +356,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;
@@ -416,7 +387,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 b15490d80dc8044dd546c8f5fba171bacb92c49c Mon Sep 17 00:00:00 2001
From: Ondrej Holy <oholy@redhat.com>
Date: Tue, 2 Jun 2026 15:54:17 +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 1888bed..bd658e1 100644
--- a/daemon/trashlib/trashwatcher.c
+++ b/daemon/trashlib/trashwatcher.c
@@ -378,7 +378,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);
@@ -403,7 +404,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