[gnome-builder] buffermanager: protect against double open race



commit d30ab4285de50b3f3d6650c52c0b2f35d0f8b0fa
Author: Christian Hergert <chergert redhat com>
Date:   Tue Aug 8 17:26:03 2017 -0700

    buffermanager: protect against double open race
    
    If the user double clicks in the project-tree, we could get into a race
    condition where the first buffer is not yet registered. This adds a loading
    hashtable so we can check if the buffer is being loaded.
    
    If so, we ignore the second request by synthesizing an error upfront.

 libide/buffers/ide-buffer-manager.c |   45 +++++++++++++++++++++++++++++++++++
 1 files changed, 45 insertions(+), 0 deletions(-)
---
diff --git a/libide/buffers/ide-buffer-manager.c b/libide/buffers/ide-buffer-manager.c
index d17ec57..69c9f4d 100644
--- a/libide/buffers/ide-buffer-manager.c
+++ b/libide/buffers/ide-buffer-manager.c
@@ -57,6 +57,7 @@ struct _IdeBufferManager
   IdeBuffer                *focus_buffer;
   GtkSourceCompletionWords *word_completion;
   GSettings                *settings;
+  GHashTable               *loading;
 
   gsize                     max_file_size;
 
@@ -775,6 +776,27 @@ ide_buffer_manager__load_file_read_cb (GObject      *object,
   IDE_EXIT;
 }
 
+static void
+ide_buffer_manager_load_task_completed (IdeBufferManager *self,
+                                        GParamSpec       *pspec,
+                                        GTask            *task)
+{
+  LoadState *state;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (self));
+  g_assert (pspec != NULL);
+  g_assert (g_strcmp0 ("completed", pspec->name) == 0);
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+  g_assert (state != NULL);
+  g_assert (state->file != NULL);
+  g_assert (IDE_IS_FILE (state->file));
+
+  if (self->loading != NULL)
+    g_hash_table_remove (self->loading, state->file);
+}
+
 /**
  * ide_buffer_manager_load_file_async:
  * @progress: (out) (nullable): A location for an #IdeProgress or %NULL.
@@ -814,6 +836,17 @@ ide_buffer_manager_load_file_async (IdeBufferManager       *self,
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+  g_task_set_source_tag (task, ide_buffer_manager_load_file_async);
+
+  if (g_hash_table_contains (self->loading, file))
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_BUSY,
+                               "The file is already loading");
+      IDE_EXIT;
+    }
 
   context = ide_object_get_context (IDE_OBJECT (self));
   ide_context_hold_for_object (context, task);
@@ -871,6 +904,14 @@ ide_buffer_manager_load_file_async (IdeBufferManager       *self,
 
   g_task_set_task_data (task, state, load_state_free);
 
+  g_hash_table_insert (self->loading, g_object_ref (file), NULL);
+
+  g_signal_connect_object (task,
+                           "notify::completed",
+                           G_CALLBACK (ide_buffer_manager_load_task_completed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
   if (progress)
     *progress = g_object_ref (state->progress);
 
@@ -1352,6 +1393,7 @@ ide_buffer_manager_finalize (GObject *object)
     g_warning ("Not all buffers have been destroyed.");
 
   g_clear_pointer (&self->buffers, g_ptr_array_unref);
+  g_clear_pointer (&self->loading, g_hash_table_unref);
   g_clear_pointer (&self->timeouts, g_hash_table_unref);
   g_clear_object (&self->settings);
 
@@ -1630,6 +1672,9 @@ ide_buffer_manager_init (IdeBufferManager *self)
   self->timeouts = g_hash_table_new (g_direct_hash, g_direct_equal);
   self->word_completion = g_object_new (IDE_TYPE_COMPLETION_WORDS, NULL);
   self->settings = g_settings_new ("org.gnome.builder.editor");
+  self->loading = g_hash_table_new_full ((GHashFunc)ide_file_hash,
+                                         (GEqualFunc)ide_file_equal,
+                                         g_object_unref, NULL);
 
   g_settings_bind (self->settings, "minimum-word-size", self->word_completion, "minimum-word-size", 
G_SETTINGS_BIND_GET);
   g_settings_bind (self->settings, "auto-save", self, "auto-save", G_SETTINGS_BIND_GET);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]