[libdazzle] recursive-monitor: rewrite to be safer



commit 6a046a2ccf00f7a7a253f7f3a79f787c8eb0e75d
Author: Christian Hergert <chergert redhat com>
Date:   Thu Nov 30 20:24:02 2017 -0800

    recursive-monitor: rewrite to be safer
    
    This tries to avoid having to have such strict thread-safety requirements
    at the potential for a slight race condition at startup (which always
    exists anyway).

 src/files/dzl-recursive-file-monitor.c |  377 ++++++++++++++++++++------------
 src/files/dzl-recursive-file-monitor.h |   21 ++-
 2 files changed, 247 insertions(+), 151 deletions(-)
---
diff --git a/src/files/dzl-recursive-file-monitor.c b/src/files/dzl-recursive-file-monitor.c
index f82c502..d05e23a 100644
--- a/src/files/dzl-recursive-file-monitor.c
+++ b/src/files/dzl-recursive-file-monitor.c
@@ -21,6 +21,8 @@
 #include "files/dzl-recursive-file-monitor.h"
 #include "util/dzl-macros.h"
 
+#define MONITOR_FLAGS 0
+
 /**
  * SECTION:dzl-recursive-file-monitor
  * @title: DzlRecursiveFileMonitor
@@ -43,15 +45,12 @@ struct _DzlRecursiveFileMonitor
   GFile                  *root;
   GCancellable           *cancellable;
 
-  GMutex                  monitor_lock;
   GHashTable             *monitors_by_file;
   GHashTable             *files_by_monitor;
 
   DzlRecursiveIgnoreFunc  ignore_func;
   gpointer                ignore_func_data;
   GDestroyNotify          ignore_func_data_destroy;
-
-  guint                   start_handler;
 };
 
 enum {
@@ -65,39 +64,23 @@ enum {
   N_SIGNALS
 };
 
-static gboolean
-dzl_recursive_file_monitor_watch (DzlRecursiveFileMonitor  *self,
-                                  GFile                    *directory,
-                                  GCancellable             *cancellable,
-                                  GError                  **error);
-
 G_DEFINE_TYPE (DzlRecursiveFileMonitor, dzl_recursive_file_monitor, G_TYPE_OBJECT)
 
 static GParamSpec *properties [N_PROPS];
 static guint signals [N_SIGNALS];
 
-/*
- * You must hold the lock when calling this function.
- */
-static gboolean
-dzl_recursive_file_monitor_ignored (DzlRecursiveFileMonitor *self,
-                                    GFile                   *file)
-{
-  g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
-  g_assert (G_IS_FILE (file));
-
-  return self->ignore_func (file, self->ignore_func_data);
-}
+static void
+dzl_recursive_file_monitor_track (DzlRecursiveFileMonitor *self,
+                                  GFile                   *dir,
+                                  GFileMonitor            *monitor);
 
-/*
- * You must hold the lock when calling this function.
- */
 static void
 dzl_recursive_file_monitor_unwatch (DzlRecursiveFileMonitor *self,
                                     GFile                   *file)
 {
   GFileMonitor *monitor;
 
+  dzl_assert_is_main_thread ();
   g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
   g_assert (G_IS_FILE (file));
 
@@ -114,12 +97,137 @@ dzl_recursive_file_monitor_unwatch (DzlRecursiveFileMonitor *self,
 }
 
 static void
+dzl_recursive_file_monitor_collect_recursive (GPtrArray    *dirs,
+                                              GFile        *parent,
+                                              GCancellable *cancellable)
+{
+  g_autoptr(GFileEnumerator) enumerator = NULL;
+  g_autoptr(GError) error = NULL;
+  guint begin;
+  guint end;
+
+  g_assert (dirs != NULL);
+  g_assert (G_IS_FILE (parent));
+  g_assert (G_IS_CANCELLABLE (cancellable));
+
+  begin = dirs->len;
+
+  enumerator = g_file_enumerate_children (parent,
+                                          G_FILE_ATTRIBUTE_STANDARD_NAME","
+                                          G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                          cancellable, &error);
+
+  if (error != NULL)
+    {
+      g_warning ("Failed to iterate children: %s", error->message);
+      g_clear_error (&error);
+    }
+
+  if (enumerator != NULL)
+    {
+      gpointer infoptr;
+
+      while (NULL != (infoptr = g_file_enumerator_next_file (enumerator, cancellable, NULL)))
+        {
+          g_autoptr(GFileInfo) info = infoptr;
+
+          if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+            {
+              const gchar *name = g_file_info_get_name (info);
+              g_ptr_array_add (dirs, g_file_get_child (parent, name));
+            }
+        }
+
+      g_file_enumerator_close (enumerator, cancellable, NULL);
+      g_clear_object (&enumerator);
+    }
+
+  end = dirs->len;
+
+  for (guint i = begin; i < end; i++)
+    {
+      GFile *child = g_ptr_array_index (dirs, i);
+
+      dzl_recursive_file_monitor_collect_recursive (dirs, child, cancellable);
+    }
+}
+
+static void
+dzl_recursive_file_monitor_collect_worker (GTask        *task,
+                                           gpointer      source_object,
+                                           gpointer      task_data,
+                                           GCancellable *cancellable)
+{
+  g_autoptr(GPtrArray) dirs = NULL;
+  GFile *root = task_data;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (G_IS_FILE (root));
+
+  dirs = g_ptr_array_new_with_free_func (g_object_unref);
+  g_ptr_array_add (dirs, g_object_ref (root));
+  dzl_recursive_file_monitor_collect_recursive (dirs, root, cancellable);
+
+  g_task_return_pointer (task,
+                         g_steal_pointer (&dirs),
+                         (GDestroyNotify)g_ptr_array_unref);
+}
+
+static void
+dzl_recursive_file_monitor_collect (DzlRecursiveFileMonitor *self,
+                                    GFile                   *root,
+                                    GCancellable            *cancellable,
+                                    GAsyncReadyCallback      callback,
+                                    gpointer                 user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
+  g_assert (G_IS_FILE (root));
+  g_assert (G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, dzl_recursive_file_monitor_collect);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+  g_task_set_task_data (task, g_object_ref (root), g_object_unref);
+  g_task_run_in_thread (task, dzl_recursive_file_monitor_collect_worker);
+}
+
+static GPtrArray *
+dzl_recursive_file_monitor_collect_finish (DzlRecursiveFileMonitor  *self,
+                                           GAsyncResult             *result,
+                                           GError                  **error)
+{
+  g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
+  g_assert (G_IS_TASK (result));
+  g_assert (g_task_is_valid (G_TASK (result), self));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static gboolean
+dzl_recursive_file_monitor_ignored (DzlRecursiveFileMonitor *self,
+                                    GFile                   *file)
+{
+  dzl_assert_is_main_thread ();
+  g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
+  g_assert (G_IS_FILE (file));
+
+  if (self->ignore_func != NULL)
+    return self->ignore_func (file, self->ignore_func_data);
+
+  return FALSE;
+}
+
+static void
 dzl_recursive_file_monitor_changed (DzlRecursiveFileMonitor *self,
                                     GFile                   *file,
                                     GFile                   *other_file,
                                     GFileMonitorEvent        event,
                                     GFileMonitor            *monitor)
 {
+  dzl_assert_is_main_thread ();
   g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
   g_assert (G_IS_FILE (file));
   g_assert (!other_file || G_IS_FILE (file));
@@ -128,10 +236,8 @@ dzl_recursive_file_monitor_changed (DzlRecursiveFileMonitor *self,
   if (g_cancellable_is_cancelled (self->cancellable))
     return;
 
-  g_mutex_lock (&self->monitor_lock);
-
   if (dzl_recursive_file_monitor_ignored (self, file))
-    goto unlock;
+    return;
 
   if (event == G_FILE_MONITOR_EVENT_DELETED)
     {
@@ -141,133 +247,145 @@ dzl_recursive_file_monitor_changed (DzlRecursiveFileMonitor *self,
   else if (event == G_FILE_MONITOR_EVENT_CREATED)
     {
       if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_DIRECTORY)
-        dzl_recursive_file_monitor_watch (self, file, self->cancellable, NULL);
+        {
+          g_autoptr(GPtrArray) dirs = NULL;
+
+          dirs = g_ptr_array_new_with_free_func (g_object_unref);
+          g_ptr_array_add (dirs, g_object_ref (file));
+
+          dzl_recursive_file_monitor_collect_recursive (dirs, file, self->cancellable);
+
+          for (guint i = 0; i < dirs->len; i++)
+            {
+              g_autoptr(GFileMonitor) dir_monitor = NULL;
+              GFile *dir = g_ptr_array_index (dirs, i);
+
+              if (!!(dir_monitor = g_file_monitor_directory (dir, MONITOR_FLAGS, self->cancellable, NULL)))
+                dzl_recursive_file_monitor_track (self, dir, dir_monitor);
+            }
+        }
     }
 
   g_signal_emit (self, signals [CHANGED], 0, file, other_file, event);
-
-unlock:
-  g_mutex_unlock (&self->monitor_lock);
 }
 
-/*
- * You must hold the lock when calling this function.
- */
-static gboolean
-dzl_recursive_file_monitor_watch (DzlRecursiveFileMonitor  *self,
-                                  GFile                    *directory,
-                                  GCancellable             *cancellable,
-                                  GError                  **error)
-{
-  g_autoptr(GFileMonitor) monitor = NULL;
 
+static void
+dzl_recursive_file_monitor_track (DzlRecursiveFileMonitor *self,
+                                  GFile                   *dir,
+                                  GFileMonitor            *monitor)
+{
+  dzl_assert_is_main_thread ();
   g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
-  g_assert (G_IS_FILE (directory));
-  g_assert (G_IS_CANCELLABLE (cancellable));
+  g_assert (G_IS_FILE (dir));
+  g_assert (G_IS_FILE_MONITOR (monitor));
 
-  monitor = g_file_monitor_directory (directory, 0, cancellable, error);
+  g_hash_table_insert (self->monitors_by_file,
+                       g_object_ref (dir),
+                       g_object_ref (monitor));
 
-  if (monitor == NULL)
-    return FALSE;
+  g_hash_table_insert (self->files_by_monitor,
+                       g_object_ref (monitor),
+                       g_object_ref (dir));
 
   g_signal_connect_object (monitor,
                            "changed",
                            G_CALLBACK (dzl_recursive_file_monitor_changed),
                            self,
                            G_CONNECT_SWAPPED);
-
-  g_hash_table_insert (self->monitors_by_file,
-                       g_object_ref (directory),
-                       g_object_ref (monitor));
-
-  g_hash_table_insert (self->files_by_monitor,
-                       g_object_ref (monitor),
-                       g_object_ref (directory));
-
-  return TRUE;
 }
 
 static void
-dzl_recursive_file_monitor_worker (GTask        *task,
-                                   gpointer      source_object,
-                                   gpointer      task_data,
-                                   GCancellable *cancellable)
+dzl_recursive_file_monitor_start_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
 {
-  DzlRecursiveFileMonitor *self = source_object;
-  g_autoptr(GFileEnumerator) enumerator = NULL;
+  DzlRecursiveFileMonitor *self = (DzlRecursiveFileMonitor *)object;
+  g_autoptr(GPtrArray) dirs = NULL;
   g_autoptr(GError) error = NULL;
-  gpointer infoptr;
-  GFile *root = task_data;
+  g_autoptr(GTask) task = user_data;
 
+  dzl_assert_is_main_thread ();
   g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
-  g_assert (G_IS_FILE (root));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
 
-  /* Short circuit if we were cancelled */
-  if (g_cancellable_is_cancelled (cancellable))
-    return;
-
-  /* Make sure our root is a directory that exists */
-  if (g_file_query_file_type (root, 0, cancellable) != G_FILE_TYPE_DIRECTORY)
-    return;
-
-  g_mutex_lock (&self->monitor_lock);
-
-  if (!dzl_recursive_file_monitor_watch (self, root, cancellable, &error))
-    goto cleanup;
-
-  enumerator = g_file_enumerate_children (root,
-                                          G_FILE_ATTRIBUTE_STANDARD_NAME","
-                                          G_FILE_ATTRIBUTE_STANDARD_TYPE,
-                                          G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
-                                          cancellable, &error);
+  dirs = dzl_recursive_file_monitor_collect_finish (self, result, &error);
 
-  while (NULL != (infoptr = g_file_enumerator_next_file (enumerator, cancellable, &error)))
+  if (dirs == NULL)
     {
-      g_autoptr(GFileInfo) info = infoptr;
-      g_autoptr(GFile) child = NULL;
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
 
-      if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
-        continue;
+  for (guint i = 0; i < dirs->len; i++)
+    {
+      GFile *dir = g_ptr_array_index (dirs, i);
+      g_autoptr(GFileMonitor) monitor = NULL;
 
-      child = g_file_get_child (root, g_file_info_get_name (info));
+      g_assert (G_IS_FILE (dir));
 
-      if (dzl_recursive_file_monitor_ignored (self, child))
+      if (dzl_recursive_file_monitor_ignored (self, dir))
         continue;
 
-      if (!dzl_recursive_file_monitor_watch (self, child, cancellable, &error))
-        break;
-    }
+      monitor = g_file_monitor_directory (dir, MONITOR_FLAGS, self->cancellable, &error);
 
-cleanup:
-  g_file_enumerator_close (enumerator, cancellable, NULL);
+      if (monitor == NULL)
+        {
+          g_warning ("Failed to monitor directory: %s", error->message);
+          g_clear_error (&error);
+          continue;
+        }
 
-  g_mutex_unlock (&self->monitor_lock);
+      dzl_recursive_file_monitor_track (self, dir, monitor);
+    }
 
-  if (error != NULL)
-    g_task_return_error (task, g_steal_pointer (&error));
-  else
-    g_task_return_boolean (task, TRUE);
+  g_task_return_boolean (task, TRUE);
 }
 
-static gboolean
-dzl_recursive_file_monitor_start (gpointer data)
+void
+dzl_recursive_file_monitor_start_async (DzlRecursiveFileMonitor *self,
+                                        GCancellable            *cancellable,
+                                        GAsyncReadyCallback      callback,
+                                        gpointer                 user_data)
 {
-  DzlRecursiveFileMonitor *self = data;
   g_autoptr(GTask) task = NULL;
 
-  g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (self));
-  g_assert (G_IS_FILE (self->root));
-
-  self->start_handler = 0;
+  g_return_if_fail (DZL_IS_RECURSIVE_FILE_MONITOR (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  task = g_task_new (self, self->cancellable, NULL, NULL);
-  g_task_set_source_tag (task, dzl_recursive_file_monitor_start);
-  g_task_set_priority (task, G_PRIORITY_LOW + 100);
-  g_task_set_task_data (task, g_object_ref (self->root), g_object_unref);
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, dzl_recursive_file_monitor_start_async);
   g_task_set_return_on_cancel (task, TRUE);
-  g_task_run_in_thread (task, dzl_recursive_file_monitor_worker);
+  g_task_set_task_data (task, g_object_ref (self->root), g_object_unref);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
+  if (self->root == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVAL,
+                               "Cannot start file monitor, no root directory set");
+      return;
+    }
 
-  return G_SOURCE_REMOVE;
+  dzl_recursive_file_monitor_collect (self,
+                                      self->root,
+                                      self->cancellable,
+                                      dzl_recursive_file_monitor_start_cb,
+                                      g_steal_pointer (&task));
+}
+
+gboolean
+dzl_recursive_file_monitor_start_finish (DzlRecursiveFileMonitor  *self,
+                                         GAsyncResult             *result,
+                                         GError                  **error)
+{
+  g_return_val_if_fail (DZL_IS_RECURSIVE_FILE_MONITOR (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+  g_return_val_if_fail (g_task_is_valid (G_TASK (result), self), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
 }
 
 static void
@@ -278,25 +396,7 @@ dzl_recursive_file_monitor_constructed (GObject *object)
   G_OBJECT_CLASS (dzl_recursive_file_monitor_parent_class)->constructed (object);
 
   if (self->root == NULL)
-    {
-      g_warning ("%s created without a root directory", G_OBJECT_TYPE_NAME (self));
-      return;
-    }
-
-  /*
-   * Defer start to the main loop so that the caller can set
-   * things like the ignore func, but not require us to have
-   * soem annoying start/stop API.
-   */
-
-  self->start_handler = g_idle_add (dzl_recursive_file_monitor_start, self);
-}
-
-static gboolean
-default_ignore_func (GFile    *file,
-                     gpointer  user_data)
-{
-  return FALSE;
+    g_warning ("%s created without a root directory", G_OBJECT_TYPE_NAME (self));
 }
 
 static void
@@ -304,7 +404,6 @@ dzl_recursive_file_monitor_dispose (GObject *object)
 {
   DzlRecursiveFileMonitor *self = (DzlRecursiveFileMonitor *)object;
 
-  dzl_clear_source (&self->start_handler);
   g_cancellable_cancel (self->cancellable);
   dzl_recursive_file_monitor_set_ignore_func (self, NULL, NULL, NULL);
 
@@ -406,14 +505,12 @@ dzl_recursive_file_monitor_class_init (DzlRecursiveFileMonitorClass *klass)
 static void
 dzl_recursive_file_monitor_init (DzlRecursiveFileMonitor *self)
 {
-  g_mutex_init (&self->monitor_lock);
   self->cancellable = g_cancellable_new ();
   self->files_by_monitor = g_hash_table_new_full (NULL, NULL, g_object_unref, g_object_unref);
   self->monitors_by_file = g_hash_table_new_full (g_file_hash,
                                                   (GEqualFunc) g_file_equal,
                                                   g_object_unref,
                                                   g_object_unref);
-  self->ignore_func = default_ignore_func;
 }
 
 DzlRecursiveFileMonitor *
@@ -487,17 +584,15 @@ dzl_recursive_file_monitor_set_ignore_func (DzlRecursiveFileMonitor *self,
                                             gpointer                 ignore_func_data,
                                             GDestroyNotify           ignore_func_data_destroy)
 {
+  dzl_assert_is_main_thread ();
   g_return_if_fail (DZL_IS_RECURSIVE_FILE_MONITOR (self));
 
   if (ignore_func == NULL)
     {
-      ignore_func = default_ignore_func;
       ignore_func_data = NULL;
       ignore_func_data_destroy = NULL;
     }
 
-  g_mutex_lock (&self->monitor_lock);
-
   if (self->ignore_func_data && self->ignore_func_data_destroy)
     {
       gpointer data = self->ignore_func_data;
@@ -507,16 +602,10 @@ dzl_recursive_file_monitor_set_ignore_func (DzlRecursiveFileMonitor *self,
       self->ignore_func_data = NULL;
       self->ignore_func_data_destroy = NULL;
 
-      g_mutex_unlock (&self->monitor_lock);
-
       notify (data);
-
-      g_mutex_lock (&self->monitor_lock);
     }
 
   self->ignore_func = ignore_func;
   self->ignore_func_data = ignore_func_data;
   self->ignore_func_data_destroy = ignore_func_data_destroy;
-
-  g_mutex_unlock (&self->monitor_lock);
 }
diff --git a/src/files/dzl-recursive-file-monitor.h b/src/files/dzl-recursive-file-monitor.h
index 69eaeb7..835d16f 100644
--- a/src/files/dzl-recursive-file-monitor.h
+++ b/src/files/dzl-recursive-file-monitor.h
@@ -30,13 +30,20 @@ G_DECLARE_FINAL_TYPE (DzlRecursiveFileMonitor, dzl_recursive_file_monitor, DZL,
 typedef gboolean (*DzlRecursiveIgnoreFunc) (GFile    *file,
                                             gpointer  user_data);
 
-DzlRecursiveFileMonitor *dzl_recursive_file_monitor_new             (GFile                   *root);
-GFile                   *dzl_recursive_file_monitor_get_root        (DzlRecursiveFileMonitor *self);
-void                     dzl_recursive_file_monitor_cancel          (DzlRecursiveFileMonitor *self);
-void                     dzl_recursive_file_monitor_set_ignore_func (DzlRecursiveFileMonitor *self,
-                                                                     DzlRecursiveIgnoreFunc   ignore_func,
-                                                                     gpointer                 
ignore_func_data,
-                                                                     GDestroyNotify           
ignore_func_data_destroy);
+DzlRecursiveFileMonitor *dzl_recursive_file_monitor_new             (GFile                    *root);
+GFile                   *dzl_recursive_file_monitor_get_root        (DzlRecursiveFileMonitor  *self);
+void                     dzl_recursive_file_monitor_start_async     (DzlRecursiveFileMonitor  *self,
+                                                                     GCancellable             *cancellable,
+                                                                     GAsyncReadyCallback       callback,
+                                                                     gpointer                  user_data);
+gboolean                 dzl_recursive_file_monitor_start_finish    (DzlRecursiveFileMonitor  *self,
+                                                                     GAsyncResult             *result,
+                                                                     GError                  **error);
+void                     dzl_recursive_file_monitor_cancel          (DzlRecursiveFileMonitor  *self);
+void                     dzl_recursive_file_monitor_set_ignore_func (DzlRecursiveFileMonitor  *self,
+                                                                     DzlRecursiveIgnoreFunc    ignore_func,
+                                                                     gpointer                  
ignore_func_data,
+                                                                     GDestroyNotify            
ignore_func_data_destroy);
 
 G_END_DECLS
 


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