From 1cb66c9598e0222166ad6d5dad0320ffeb8799c3 Mon Sep 17 00:00:00 2001 From: Keith Bailey Date: Sat, 11 Feb 2023 22:16:13 -0500 Subject: [PATCH 1/3] thumbnails: Concurrent thumbnailing Thumbnailing currently processes one thumbnail at a time. Redesign so that multiple thumbnails can be created at once. Thumbnailing consists of generating and saving. Thumbnail generation is CPU bound, so the maximum concurrent generation amount is the number of logical processors on the system. Decreases execution time when multiple files need to be thumbnailed and the system has multiple logical processors. Partially inspired by @Yanpas's !660 (closed). Edited version of !700 to fix merge conflicts. Closes #856, #1397 --- src/nautilus-thumbnails.c | 648 ++++++++++++++++++++++++++++---------- 1 file changed, 490 insertions(+), 158 deletions(-) diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c index be8d48c9d2..939d45108f 100644 --- a/src/nautilus-thumbnails.c +++ b/src/nautilus-thumbnails.c @@ -56,10 +56,38 @@ static void thumbnail_thread_func (GTask *task, gpointer task_data, GCancellable *cancellable); +static void generate_thumbnails (void); + +static void generate_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void generate_thumbnail_skipped (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void generate_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void save_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + +static void save_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable); + /* structure used for making thumbnails, associating a uri with where the thumbnail is to be stored */ typedef struct { + GdkPixbuf *pixbuf; char *image_uri; char *mime_type; time_t original_file_mtime; @@ -73,25 +101,43 @@ typedef struct * idle handler is currently registered. */ static guint thumbnail_thread_starter_id = 0; -/* Our mutex used when accessing data shared between the main thread and the - * thumbnail thread, i.e. the thumbnail_thread_is_running flag and the - * thumbnails_to_make list. */ +/* Our mutex used when accessing data shared between the main thread, + * the thumbnail thread, and the various generate/save threads + * i.e. the thumbnail_thread_is_running flag and the thumbnails_to_make list. */ static GMutex thumbnails_mutex; /* A flag to indicate whether a thumbnail thread is running, so we don't * start more than one. Lock thumbnails_mutex when accessing this. */ -static volatile gboolean thumbnail_thread_is_running = FALSE; +static gboolean thumbnail_thread_is_running = FALSE; /* The list of NautilusThumbnailInfo structs containing information about the - * thumbnails we are making. Lock thumbnails_mutex when accessing this. */ -static volatile GQueue thumbnails_to_make = G_QUEUE_INIT; + * thumbnails we are going to make. Lock thumbnails_mutex when accessing this. */ +static GQueue thumbnails_to_make = G_QUEUE_INIT; /* Quickly check if uri is in thumbnails_to_make list */ static GHashTable *thumbnails_to_make_hash = NULL; -/* The currently thumbnailed icon. it also exists in the thumbnails_to_make list - * to avoid adding it again. Lock thumbnails_mutex when accessing this. */ -static NautilusThumbnailInfo *currently_thumbnailing = NULL; +/* The maximum number of threads that can generate a thumbnail concurrently. */ +static guint max_generate_threads = 1; + +/* The currently generating thumbnails. + * Lock thumbnails_mutex when accessing this. */ +static GHashTable *thumbnails_generating = NULL; + +/* Occurs when a thumbnail is finished generating. + * A new thumbnail is ready to be generated unless thumbnails_to_make is empty + * (when this signal is sent, not when g_cond_wait locks ). */ +static GCond generate_cond; + +/* The currently saving thumbnails. + * Lock thumbnails_mutex when accessing this. */ +static GHashTable *thumbnails_saving = NULL; + +/* Allows generation and loading of thumbnails. + * Once assigned a non NULL value, pointer never changes. + * If it might not have been created yet, lock thumbnails_mutex when accessing this. */ +static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL; + static gboolean get_file_mtime (const char *file_uri, @@ -124,49 +170,70 @@ get_file_mtime (const char *file_uri, static void free_thumbnail_info (NautilusThumbnailInfo *info) { + if (info->pixbuf != NULL) + { + g_object_unref (info->pixbuf); + } g_free (info->image_uri); g_free (info->mime_type); g_free (info); } -static GnomeDesktopThumbnailFactory * -get_thumbnail_factory (void) +/* Initialize thumbnail_factory if it hasn't been created yet. */ +static void +create_thumbnail_factory (void) { - static GnomeDesktopThumbnailFactory *thumbnail_factory = NULL; - if (thumbnail_factory == NULL) { - GdkDisplay *display = gdk_display_get_default (); - GListModel *monitors = gdk_display_get_monitors (display); - gint max_scale = 1; - GnomeDesktopThumbnailSize size; + DEBUG ("(Main Thread) Locking mutex\n"); - for (guint i = 0; i < g_list_model_get_n_items (monitors); i++) - { - g_autoptr (GdkMonitor) monitor = g_list_model_get_item (monitors, i); + g_mutex_lock (&thumbnails_mutex); - max_scale = MAX (max_scale, gdk_monitor_get_scale_factor (monitor)); - } + /********************************* + * MUTEX LOCKED + ******************** *************/ - if (max_scale <= 1) - { - size = GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE; - } - else if (max_scale <= 2) + if (thumbnail_factory == NULL) { - size = GNOME_DESKTOP_THUMBNAIL_SIZE_XLARGE; - } - else - { - size = GNOME_DESKTOP_THUMBNAIL_SIZE_XXLARGE; + GdkDisplay *display = gdk_display_get_default (); + GListModel *monitors = gdk_display_get_monitors (display); + gint max_scale = 1; + GnomeDesktopThumbnailSize size; + + for (guint i = 0; i < g_list_model_get_n_items (monitors); i++) + { + g_autoptr (GdkMonitor) monitor = g_list_model_get_item (monitors, i); + + max_scale = MAX (max_scale, gdk_monitor_get_scale_factor (monitor)); + } + + if (max_scale <= 1) + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE; + } + else if (max_scale <= 2) + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_XLARGE; + } + else + { + size = GNOME_DESKTOP_THUMBNAIL_SIZE_XXLARGE; + } + + thumbnail_factory = gnome_desktop_thumbnail_factory_new (size); + + thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_LARGE); } - thumbnail_factory = gnome_desktop_thumbnail_factory_new (size); - } + /********************************* + * MUTEX UNLOCKED + *********************************/ - return thumbnail_factory; -} + DEBUG ("(Main Thread) Unlocking mutex\n"); + g_mutex_unlock (&thumbnails_mutex); + } +} /* This function is added as a very low priority idle function to start the * thread to create any needed thumbnails. It is added with a very low priority @@ -175,7 +242,7 @@ get_thumbnail_factory (void) static gboolean thumbnail_thread_starter_cb (gpointer data) { - GTask *task; + GTask *thumbnail_thread_task; DEBUG ("(Main Thread) Creating thumbnails thread\n"); @@ -185,11 +252,11 @@ thumbnail_thread_starter_cb (gpointer data) * twice, as we also check thumbnail_thread_starter_id before * scheduling this idle function. */ thumbnail_thread_is_running = TRUE; - task = g_task_new (NULL, NULL, NULL, NULL); - g_task_run_in_thread (task, thumbnail_thread_func); + thumbnail_thread_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_run_in_thread (thumbnail_thread_task, thumbnail_thread_func); thumbnail_thread_starter_id = 0; - g_object_unref (task); + g_object_unref (thumbnail_thread_task); return FALSE; } @@ -211,7 +278,7 @@ nautilus_thumbnail_remove_from_queue (const char *file_uri) { node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri); - if (node && node->data != currently_thumbnailing) + if (node) { g_hash_table_remove (thumbnails_to_make_hash, file_uri); free_thumbnail_info (node->data); @@ -245,7 +312,7 @@ nautilus_thumbnail_prioritize (const char *file_uri) { node = g_hash_table_lookup (thumbnails_to_make_hash, file_uri); - if (node && node->data != currently_thumbnailing) + if (node) { g_queue_unlink ((GQueue *) &thumbnails_to_make, node); g_queue_push_head_link ((GQueue *) &thumbnails_to_make, node); @@ -351,7 +418,6 @@ nautilus_thumbnail_is_mimetype_limited_by_size (const char *mime_type) gboolean nautilus_can_thumbnail (NautilusFile *file) { - GnomeDesktopThumbnailFactory *factory; gboolean res; char *uri; time_t mtime; @@ -361,8 +427,18 @@ nautilus_can_thumbnail (NautilusFile *file) mime_type = nautilus_file_get_mime_type (file); mtime = nautilus_file_get_mtime (file); - factory = get_thumbnail_factory (); - res = gnome_desktop_thumbnail_factory_can_thumbnail (factory, + /********************************* + * MUTEX LOCKED + *********************************/ + + create_thumbnail_factory (); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + g_assert (thumbnail_factory != NULL); + res = gnome_desktop_thumbnail_factory_can_thumbnail (thumbnail_factory, uri, mime_type, mtime); @@ -412,13 +488,42 @@ nautilus_create_thumbnail (NautilusFile *file) if (thumbnails_to_make_hash == NULL) { - thumbnails_to_make_hash = g_hash_table_new (g_str_hash, - g_str_equal); + thumbnails_to_make_hash = g_hash_table_new (g_str_hash, g_str_equal); } + if (thumbnails_generating == NULL) + { + thumbnails_generating = g_hash_table_new (g_str_hash, g_str_equal); + } + + if (thumbnails_saving == NULL) + { + thumbnails_saving = g_hash_table_new (g_str_hash, g_str_equal); + } + + /* A thumbnail goes through data structures: + * thumbnails_to_make(_hash) -> thumbnails_generating (possibly skipped) -> thumbnails_saving + * All three data structures are disjoint for the key image_uri. */ + + /* A thumbnail goes through threads: + * Main -> thumbnail_thread_task -> generate_task -> generate_skipped_task -> Main + * -> generate_done_task -> save_task -> save_done_task -> Main */ + /* Check if it is already in the list of thumbnails to make. */ existing = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri); - if (existing == NULL) + if (existing != NULL) + { + DEBUG ("(Main Thread) Updating non-current mtime: %s\n", + info->image_uri); + + /* The file in the queue might need a new original mtime */ + existing_info = existing->data; + existing_info->original_file_mtime = info->original_file_mtime; + free_thumbnail_info (info); + } + /* Check if it isn't being thumbnailed yet and also hasn't been added to the list. */ + else if (!g_hash_table_contains (thumbnails_generating, info->image_uri) && + !g_hash_table_contains (thumbnails_saving, info->image_uri)) { /* Add the thumbnail to the list. */ DEBUG ("(Main Thread) Adding thumbnail: %s\n", @@ -438,16 +543,6 @@ nautilus_create_thumbnail (NautilusFile *file) thumbnail_thread_starter_id = g_idle_add_full (G_PRIORITY_LOW, thumbnail_thread_starter_cb, NULL, NULL); } } - else - { - DEBUG ("(Main Thread) Updating non-current mtime: %s\n", - info->image_uri); - - /* The file in the queue might need a new original mtime */ - existing_info = existing->data; - existing_info->original_file_mtime = info->original_file_mtime; - free_thumbnail_info (info); - } /********************************* * MUTEX UNLOCKED @@ -458,140 +553,377 @@ nautilus_create_thumbnail (NautilusFile *file) g_mutex_unlock (&thumbnails_mutex); } -/* thumbnail_thread is invoked as a separate thread to to make thumbnails. */ +/* thumbnail_thread is invoked as a separate thread to to make thumbnails. + * Makes thumbnails concurrently until thumbnails_to_make is empty */ static void thumbnail_thread_func (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { - GnomeDesktopThumbnailFactory *thumbnail_factory; - NautilusThumbnailInfo *info = NULL; - GdkPixbuf *pixbuf; - time_t current_orig_mtime = 0; - time_t current_time; - GList *node; - GError *error = NULL; + /********************************* + * MUTEX LOCKED + *********************************/ - thumbnail_factory = get_thumbnail_factory (); + create_thumbnail_factory (); - /* We loop until there are no more thumbails to make, at which point - * we exit the thread. */ - for (;;) - { - DEBUG ("(Thumbnail Thread) Locking mutex\n"); + /********************************* + * MUTEX UNLOCKED + *********************************/ - g_mutex_lock (&thumbnails_mutex); + DEBUG ("(Thumbnail Thread) Locking mutex\n"); - /********************************* - * MUTEX LOCKED - *********************************/ + g_mutex_lock (&thumbnails_mutex); - /* Pop the last thumbnail we just made off the head of the - * list and free it. I did this here so we only have to lock - * the mutex once per thumbnail, rather than once before - * creating it and once after. - * Don't pop the thumbnail off the queue if the original file - * mtime of the request changed. Then we need to redo the thumbnail. - */ - if (currently_thumbnailing && - currently_thumbnailing->original_file_mtime == current_orig_mtime) - { - g_assert (info == currently_thumbnailing); - node = g_hash_table_lookup (thumbnails_to_make_hash, info->image_uri); - g_assert (node != NULL); - g_hash_table_remove (thumbnails_to_make_hash, info->image_uri); - free_thumbnail_info (info); - g_queue_delete_link ((GQueue *) &thumbnails_to_make, node); - } - currently_thumbnailing = NULL; + /********************************* + * MUTEX LOCKED + *********************************/ - /* If there are no more thumbnails to make, reset the - * thumbnail_thread_is_running flag, unlock the mutex, and - * exit the thread. */ + max_generate_threads = g_get_num_processors (); + + /* Waits for generate_cond signal (when a generate finishes), + * then continues generating thumbnails in a loop until thumbnails_to_make is empty. */ + while (TRUE) + { + /* Exit thread if thumbnails_to_make is empty. */ if (g_queue_is_empty ((GQueue *) &thumbnails_to_make)) { - DEBUG ("(Thumbnail Thread) Exiting\n"); + DEBUG ("(Thumbnail Thread) Exiting from while loop.\n"); thumbnail_thread_is_running = FALSE; g_mutex_unlock (&thumbnails_mutex); return; } - /* Get the next one to make. We leave it on the list until it - * is created so the main thread doesn't add it again while we - * are creating it. */ - info = g_queue_peek_head ((GQueue *) &thumbnails_to_make); - currently_thumbnailing = info; - current_orig_mtime = info->original_file_mtime; + /* If thumbnails_to_make isn't empty then + * at least one generate_thumbnail thread will run. + * Which implies that at least one generate_cond will signal, + * meaning g_cond_wait won't be waiting indefinitely. */ + generate_thumbnails (); + /********************************* * MUTEX UNLOCKED *********************************/ - DEBUG ("(Thumbnail Thread) Unlocking mutex\n"); + DEBUG ("(Thumbnail Thread) Unlocking mutex with g_cond_wait \n"); - g_mutex_unlock (&thumbnails_mutex); + g_cond_wait (&generate_cond, &thumbnails_mutex); - time (¤t_time); + DEBUG ("(Thumbnail Thread) Locked mutex from generate_cond signal\n"); - /* Don't try to create a thumbnail if the file was modified recently. - * This prevents constant re-thumbnailing of changing files. */ - if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS && - current_time >= current_orig_mtime) - { - DEBUG ("(Thumbnail Thread) Skipping: %s\n", - info->image_uri); + /********************************* + * MUTEX LOCKED + *********************************/ + } +} - /* Reschedule thumbnailing via a change notification */ - g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed, - g_strdup (info->image_uri)); - continue; - } +/* Generates thumbnails concurrently. Always called from locked thumbnails_mutex. + * Generates as much thumbnails as possible, limited by max_generate_threads and length of thumbnails_to_make. */ +static void +generate_thumbnails (void) +{ + guint num_thumbs_to_generate; + NautilusThumbnailInfo *info; + GTask *generate_task; + + num_thumbs_to_generate = MIN ((max_generate_threads - g_hash_table_size (thumbnails_generating)), + (g_queue_get_length ((GQueue *) &thumbnails_to_make))); + + DEBUG ("(Thumbnail Thread (generate_thumbnails)) num_thumbs_to_generate: %d\n", + num_thumbs_to_generate); - /* Create the thumbnail. */ - DEBUG ("(Thumbnail Thread) Creating thumbnail: %s\n", + /* Take thumbnail(s) off thumbnails_to_make and insert onto thumbnails_generating. + * Run generate_thumbnail in GTask for each. */ + for (guint index = 0; index < num_thumbs_to_generate; index++) + { + info = (NautilusThumbnailInfo *) g_queue_pop_head ((GQueue *) &thumbnails_to_make); + g_assert (info != NULL); + g_hash_table_remove (thumbnails_to_make_hash, info->image_uri); + g_hash_table_insert (thumbnails_generating, info->image_uri, info); + + DEBUG ("(Thumbnail Thread (generate_thumbnails)) starting generate_task: %s\n", info->image_uri); - pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory, - info->image_uri, - info->mime_type, - NULL, - &error); + generate_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (generate_task, info, NULL); + g_task_run_in_thread (generate_task, generate_thumbnail); + g_object_unref (generate_task); + } - if (pixbuf) - { - DEBUG ("(Thumbnail Thread) Saving thumbnail: %s\n", - info->image_uri); - - gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory, - pixbuf, - info->image_uri, - current_orig_mtime, - NULL, - &error); - if (error) - { - DEBUG ("(Thumbnail Thread) Saving thumbnail failed: %s (%s)\n", - info->image_uri, error->message); - g_clear_error (&error); - } - g_object_unref (pixbuf); - } - else + DEBUG ("(Thumbnail Thread (generate_thumbnails)) Finished starting generate tasks"); + + g_assert ((g_hash_table_size (thumbnails_generating) == max_generate_threads) || + ((g_hash_table_size (thumbnails_generating) < max_generate_threads) && + ((g_queue_is_empty ((GQueue *) &thumbnails_to_make)) && + (g_hash_table_size (thumbnails_to_make_hash) == 0)))); +} + +/* Generates a thumbnail in a separate thread. CPU bound. + * Only max_generate_threads threads of this function should be ran concurrently. */ +static void +generate_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + time_t current_orig_mtime = 0; + time_t current_time; + GError *error = NULL; + GTask *generate_skipped_task; + GTask *generate_done_task; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + g_assert (thumbnail_factory != NULL); + + current_orig_mtime = info->original_file_mtime; + time (¤t_time); + + /* Don't try to create a thumbnail if the file was modified recently. + * This prevents constant re-thumbnailing of changing files. */ + if (current_time < current_orig_mtime + THUMBNAIL_CREATION_DELAY_SECS && + current_time >= current_orig_mtime) + { + DEBUG ("(Generate Thread) Skipping. Starting generate_skipped_task: %s\n", + info->image_uri); + + /* Handle generate skipped in a separate thread. Free thumbnail info on task end. */ + generate_skipped_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (generate_skipped_task, info, + (GDestroyNotify) free_thumbnail_info); + g_task_run_in_thread (generate_skipped_task, generate_thumbnail_skipped); + g_object_unref (generate_skipped_task); + } + else + { + DEBUG ("(Generate Thread) Starting thumbnail: %s\n", + info->image_uri); + + /* CPU bound. */ + info->pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumbnail_factory, + info->image_uri, + info->mime_type, + NULL, + &error); + + DEBUG ("(Generate Thread) Finished thumbnail. Starting generate_done_task: %s\n", + info->image_uri); + + /* Handle generate done in a separate thread. */ + generate_done_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (generate_done_task, info, NULL); + g_task_run_in_thread (generate_done_task, generate_thumbnail_done); + g_object_unref (generate_done_task); + } +} + +/* Generating a thumbnail was skipped due to a recent modification time. + * End of process of creating a thumbnail. */ +static void +generate_thumbnail_skipped (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + + DEBUG ("(Generate Skipped) Locking mutex: %s\n", + info->image_uri); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + g_hash_table_remove (thumbnails_generating, info->image_uri); + g_assert (g_hash_table_size (thumbnails_generating) < max_generate_threads); + + DEBUG ("(Generate Skipped) Signal generate_cond: %s\n", + info->image_uri); + + g_cond_signal (&generate_cond); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + g_mutex_unlock (&thumbnails_mutex); + + DEBUG ("(Generate Skipped) Unlocking mutex: %s\n", + info->image_uri); + + DEBUG ("(Generate Skipped) Reschedule thumbnailing: %s\n", + info->image_uri); + + /* Reschedule thumbnailing via a change notification. */ + g_timeout_add_seconds (1, thumbnail_thread_notify_file_changed, + g_strdup (info->image_uri)); +} + +/* Finishes generating a thumbnail and starts saving a thumbnail. */ +static void +generate_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + GTask *save_task; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + + /* Remove from thumbnails_generating and insert into thumbnails_saving. + * Signal to thumbnail_thread_func that a generate thread is finished. */ + + DEBUG ("(Generate Done) Locking mutex: %s\n", + info->image_uri); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + g_hash_table_remove (thumbnails_generating, info->image_uri); + g_assert (g_hash_table_size (thumbnails_generating) < max_generate_threads); + g_hash_table_insert (thumbnails_saving, info->image_uri, info); + + DEBUG ("(Generate Done) Signal generate_cond: %s\n", + info->image_uri); + + g_cond_signal (&generate_cond); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Generate Done) Unlocking mutex: %s\n", + info->image_uri); + + g_mutex_unlock (&thumbnails_mutex); + + DEBUG ("(Generate Done) Starting save_task: %s\n", + info->image_uri); + + /* Save thumbnail. Free info with free_thumbnail_info on task end. */ + save_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (save_task, info, NULL); + g_task_run_in_thread (save_task, save_thumbnail); + g_object_unref (save_task); +} + +/* Saves a thumbnail or a failed thumbnail. I/O bound. */ +static void +save_thumbnail (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + GTask *save_done_task; + GError *error = NULL; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + g_assert (thumbnail_factory != NULL); + + /* pixbuf isn't NULL if generate succeeded. */ + if (info->pixbuf != NULL) + { + DEBUG ("(Save) Saving thumbnail: %s\n", + info->image_uri); + + /* I/O bound. */ + gnome_desktop_thumbnail_factory_save_thumbnail (thumbnail_factory, + info->pixbuf, + info->image_uri, + info->original_file_mtime, + NULL, + &error); + if (error) { - DEBUG ("(Thumbnail Thread) Thumbnail failed: %s (%s)\n", - info->image_uri, error->message); + DEBUG ("(Save) Saving thumbnail failed: %s (%s)\n", + info->image_uri, error->message); g_clear_error (&error); - - gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, - info->image_uri, - current_orig_mtime, - NULL, NULL); } - /* We need to call nautilus_file_changed(), but I don't think that is - * thread safe. So add an idle handler and do it from the main loop. */ - g_idle_add_full (G_PRIORITY_HIGH_IDLE, - thumbnail_thread_notify_file_changed, - g_strdup (info->image_uri), NULL); } + else + { + DEBUG ("(Save) Thumbnail generate failed: %s (%s)\n", + info->image_uri, error->message); + + /* I/O bound. */ + gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, + info->image_uri, + info->original_file_mtime, + NULL, NULL); + } + + DEBUG ("(Save) Starting save_done_task: %s\n", + info->image_uri); + + /* Save thumbnail done. Free info with free_thumbnail_info on task end. */ + save_done_task = g_task_new (NULL, NULL, NULL, NULL); + g_task_set_task_data (save_done_task, info, + (GDestroyNotify) free_thumbnail_info); + g_task_run_in_thread (save_done_task, save_thumbnail_done); + g_object_unref (save_done_task); +} + +/* Finishes saving a thumbnail. End of process of creating a thumbnail. */ +static void +save_thumbnail_done (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + NautilusThumbnailInfo *info; + GSource *source; + GMainContext *context; + + info = (NautilusThumbnailInfo *) task_data; + + g_assert (info != NULL); + + DEBUG ("(Save Done) Locking mutex: %s\n", + info->image_uri); + + g_mutex_lock (&thumbnails_mutex); + + /********************************* + * MUTEX LOCKED + *********************************/ + + g_hash_table_remove (thumbnails_saving, info->image_uri); + + /********************************* + * MUTEX UNLOCKED + *********************************/ + + DEBUG ("(Save Done) Unlocking mutex: %s\n", + info->image_uri); + + g_mutex_unlock (&thumbnails_mutex); + + DEBUG ("(Save Done) Calling thumbnail_thread_notify_file_changed with HIGH_IDLE from main loop: %s\n", + info->image_uri); + + /* We need to call nautilus_file_changed(), but I don't think that is + * thread safe. So add an idle handler and do it from the main loop. */ + source = g_idle_source_new (); + context = g_main_context_default (); + g_source_set_callback (source, thumbnail_thread_notify_file_changed, g_strdup (info->image_uri), NULL); + g_source_set_priority (source, G_PRIORITY_HIGH_IDLE); + g_source_attach (source, context); + g_source_unref (source); } -- GitLab From c66cbec6636a275d807514338f95d78d5fee2a0f Mon Sep 17 00:00:00 2001 From: Keith Bailey Date: Sat, 11 Feb 2023 22:56:47 -0500 Subject: [PATCH 2/3] thumbnails: Concurrent thumbnailing Style check fixes. --- src/nautilus-thumbnails.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c index 939d45108f..e64bc7a648 100644 --- a/src/nautilus-thumbnails.c +++ b/src/nautilus-thumbnails.c @@ -853,14 +853,14 @@ save_thumbnail (GTask *task, if (error) { DEBUG ("(Save) Saving thumbnail failed: %s (%s)\n", - info->image_uri, error->message); + info->image_uri, error->message); g_clear_error (&error); } } else { DEBUG ("(Save) Thumbnail generate failed: %s (%s)\n", - info->image_uri, error->message); + info->image_uri, error->message); /* I/O bound. */ gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, -- GitLab From d5991f91f30238bfbfdbbb9a0259ec21905e6261 Mon Sep 17 00:00:00 2001 From: Keith Bailey Date: Tue, 4 Jul 2023 17:15:32 -0400 Subject: [PATCH 3/3] thumbnails: Concurrent thumbnailing small fix Fixes the segmentation fault due to refrencing a Null GError in a DEBUG message --- src/nautilus-thumbnails.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nautilus-thumbnails.c b/src/nautilus-thumbnails.c index e64bc7a648..b5f5029c85 100644 --- a/src/nautilus-thumbnails.c +++ b/src/nautilus-thumbnails.c @@ -707,6 +707,12 @@ generate_thumbnail (GTask *task, info->mime_type, NULL, &error); + if (error) + { + DEBUG ("(Generate Thread) Thumbnail generate failed: %s (%s)\n", + info->image_uri, error->message); + g_clear_error (&error); + } DEBUG ("(Generate Thread) Finished thumbnail. Starting generate_done_task: %s\n", info->image_uri); @@ -859,8 +865,8 @@ save_thumbnail (GTask *task, } else { - DEBUG ("(Save) Thumbnail generate failed: %s (%s)\n", - info->image_uri, error->message); + DEBUG ("(Save) Thumbnail generate failed: %s\n", + info->image_uri); /* I/O bound. */ gnome_desktop_thumbnail_factory_create_failed_thumbnail (thumbnail_factory, -- GitLab