From 890b2a747bbcd9ed220fb217aba41b9ef0188ba1 Mon Sep 17 00:00:00 2001 From: Ondrej Holy 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