[gnome-builder] egg-task-cache: add transparent cache for async task results



commit 2e5f5e89336750f175ce0be0b2a1d4449843a7bf
Author: Christian Hergert <christian hergert me>
Date:   Tue May 12 00:58:22 2015 -0700

    egg-task-cache: add transparent cache for async task results
    
    We need a way to multiplex multiple asynchronous requests for the same
    item into a single operation. EggTaskCache lets us do that for a
    specific subset, which requires string cache keys and GObject cache
    values.
    
    I intend to improve upon this adding such things as an eviction timeout.
    
    Counters have been added so we can inspect things.

 contrib/egg/Makefile.am      |    2 +
 contrib/egg/egg-counter.h    |   26 ++--
 contrib/egg/egg-task-cache.c |  275 ++++++++++++++++++++++++++++++++++++++++++
 contrib/egg/egg-task-cache.h |   42 +++++++
 4 files changed, 332 insertions(+), 13 deletions(-)
---
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
index a3cee45..598dbca 100644
--- a/contrib/egg/Makefile.am
+++ b/contrib/egg/Makefile.am
@@ -13,6 +13,8 @@ libegg_la_SOURCES = \
        egg-signal-group.h \
        egg-state-machine.c \
        egg-state-machine.h \
+       egg-task-cache.c \
+       egg-task-cache.h \
        $(NULL)
 
 libegg_la_CFLAGS = $(EGG_CFLAGS)
diff --git a/contrib/egg/egg-counter.h b/contrib/egg/egg-counter.h
index b5ce0e7..f46181a 100644
--- a/contrib/egg/egg-counter.h
+++ b/contrib/egg/egg-counter.h
@@ -172,13 +172,13 @@ G_BEGIN_DECLS
  * EGG_DEFINE_COUNTER (my_counter, "My", "Counter", "My Counter Description");
  * ]|
  */
-#define EGG_DEFINE_COUNTER(Identifier, Category, Name, Description)           \
- static EggCounter Identifier = { NULL, Category, Name, Description };        \
- static void Identifier##_counter_init (void) __attribute__((constructor));   \
- static void                                                                  \
- Identifier##_counter_init (void)                                             \
- {                                                                            \
-   egg_counter_arena_register (egg_counter_arena_get_default(), &Identifier); \
+#define EGG_DEFINE_COUNTER(Identifier, Category, Name, Description)                 \
+ static EggCounter Identifier##_ctr = { NULL, Category, Name, Description };        \
+ static void Identifier##_ctr_init (void) __attribute__((constructor));             \
+ static void                                                                        \
+ Identifier##_ctr_init (void)                                                       \
+ {                                                                                  \
+   egg_counter_arena_register (egg_counter_arena_get_default(), &Identifier##_ctr); \
  }
 
 /**
@@ -225,14 +225,14 @@ G_BEGIN_DECLS
  * See #EggCounter for more information.
  */
 #ifdef EGG_COUNTER_REQUIRES_ATOMIC
-# define EGG_COUNTER_ADD(Identifier, Count)                                \
-  G_STMT_START {                                                           \
-    __sync_add_and_fetch ((gint64 *)&Identifier.values[0], (gint64)Count); \
+# define EGG_COUNTER_ADD(Identifier, Count)                                      \
+  G_STMT_START {                                                                 \
+    __sync_add_and_fetch ((gint64 *)&Identifier##_ctr.values[0], (gint64)Count); \
   } G_STMT_END
 #else
-# define EGG_COUNTER_ADD(Identifier, Count)                    \
-  G_STMT_START {                                               \
-    Identifier.values[egg_get_current_cpu()].value += (Count); \
+# define EGG_COUNTER_ADD(Identifier, Count)                          \
+  G_STMT_START {                                                     \
+    Identifier##_ctr.values[egg_get_current_cpu()].value += (Count); \
   } G_STMT_END
 #endif
 
diff --git a/contrib/egg/egg-task-cache.c b/contrib/egg/egg-task-cache.c
new file mode 100644
index 0000000..b23e7de
--- /dev/null
+++ b/contrib/egg/egg-task-cache.c
@@ -0,0 +1,275 @@
+/* egg-task-cache.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "egg-counter.h"
+#include "egg-task-cache.h"
+
+struct _EggTaskCache
+{
+  GObject     parent_instance;
+
+  GHashTable *cache;
+  GHashTable *in_flight;
+  GHashTable *queued;
+};
+
+typedef struct
+{
+  EggTaskCache *self;
+  gchar        *key;
+} CacheFault;
+
+G_DEFINE_TYPE (EggTaskCache, egg_task_cache, G_TYPE_OBJECT)
+
+EGG_DEFINE_COUNTER (instances,  "EggTaskCache", "Instances",  "Number of EggTaskCache instances")
+EGG_DEFINE_COUNTER (in_flight,  "EggTaskCache", "In Flight",  "Number of in flight operations")
+EGG_DEFINE_COUNTER (queued,     "EggTaskCache", "Queued",     "Number of queued operations")
+EGG_DEFINE_COUNTER (cached,     "EggTaskCache", "Cache Size", "Number of cached items")
+EGG_DEFINE_COUNTER (hits,       "EggTaskCache", "Cache Hits", "Number of cache hits")
+EGG_DEFINE_COUNTER (misses,     "EggTaskCache", "Cache Size", "Number of cache misses")
+
+static gboolean
+egg_task_cache_populate_from_cache (EggTaskCache *self,
+                                    const gchar  *key,
+                                    GTask        *task)
+{
+  GObject *obj;
+
+  g_assert (self != NULL);
+  g_assert (key != NULL);
+  g_assert (G_IS_TASK (task));
+
+  obj = g_hash_table_lookup (self->cache, key);
+
+  if (obj != NULL)
+    {
+      g_task_return_pointer (task, g_object_ref (obj), g_object_unref);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+egg_task_cache_populate_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  GTask *task = (GTask *)result;
+  CacheFault *fault = user_data;
+  EggTaskCache *self;
+  GPtrArray *queued;
+  GObject *ret;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (fault != NULL);
+
+  self = fault->self;
+  g_assert (EGG_IS_TASK_CACHE (self));
+
+  ret = g_task_propagate_pointer (task, &error);
+  g_assert (!ret || G_IS_OBJECT (ret));
+
+  if (ret != NULL)
+    {
+      g_hash_table_insert (self->cache, g_strdup (fault->key), g_object_ref (ret));
+
+      EGG_COUNTER_INC (cached);
+    }
+
+  g_hash_table_remove (self->in_flight, fault->key);
+
+  if ((queued = g_hash_table_lookup (self->queued, fault->key)))
+    {
+      gsize i;
+
+      g_hash_table_steal (self->queued, fault->key);
+
+      for (i = 0; i < queued->len; i++)
+        {
+          GTask *queued_task = g_ptr_array_index (queued, i);
+
+          if (ret != NULL)
+            g_task_return_pointer (queued_task,
+                                   g_object_ref (ret),
+                                   g_object_unref);
+          else
+            g_task_return_error (queued_task, g_error_copy (error));
+        }
+
+      EGG_COUNTER_SUB (queued, queued->len);
+
+      g_ptr_array_unref (queued);
+    }
+
+  EGG_COUNTER_DEC (in_flight);
+
+  g_clear_object (&ret);
+  g_clear_error (&error);
+  g_free (fault->key);
+  g_object_unref (fault->self);
+  g_slice_free (CacheFault, fault);
+}
+
+void
+egg_task_cache_populate (EggTaskCache    *self,
+                         const gchar     *key,
+                         GTask           *task,
+                         GTaskThreadFunc  thread_func,
+                         gpointer         task_data,
+                         GDestroyNotify   task_data_destroy)
+{
+  GPtrArray *queued;
+
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (key != NULL);
+  g_return_if_fail (G_IS_TASK (task));
+  g_return_if_fail (thread_func != NULL);
+
+  if (egg_task_cache_populate_from_cache (self, key, task))
+    {
+      if (task_data_destroy)
+        task_data_destroy (task_data);
+
+      EGG_COUNTER_INC (hits);
+
+      return;
+    }
+
+  EGG_COUNTER_INC (misses);
+
+  if ((queued = g_hash_table_lookup (self->queued, key)) == NULL)
+    {
+      queued = g_ptr_array_new_with_free_func (g_object_unref);
+      g_hash_table_insert (self->queued, g_strdup (key), queued);
+    }
+
+  g_ptr_array_add (queued, g_object_ref (task));
+
+  EGG_COUNTER_INC (queued);
+
+  if (!g_hash_table_contains (self->in_flight, key))
+    {
+      GCancellable *cancellable;
+      CacheFault *fault;
+      gpointer source_object;
+      GTask *fault_task;
+
+      source_object = g_task_get_source_object (task);
+      cancellable = g_task_get_cancellable (task);
+
+      fault = g_slice_new0 (CacheFault);
+      fault->self = g_object_ref (self);
+      fault->key = g_strdup (key);
+
+      fault_task = g_task_new (source_object,
+                               cancellable,
+                               egg_task_cache_populate_cb,
+                               fault);
+      g_task_set_task_data (fault_task, task_data, task_data_destroy);
+      g_task_run_in_thread (fault_task, thread_func);
+
+      EGG_COUNTER_INC (in_flight);
+    }
+}
+
+gboolean
+egg_task_cache_evict (EggTaskCache *self,
+                      const gchar  *key)
+{
+  g_return_val_if_fail (EGG_IS_TASK_CACHE (self), FALSE);
+
+  if (g_hash_table_remove (self->cache, key))
+    {
+      EGG_COUNTER_DEC (cached);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+egg_task_cache_dispose (GObject *object)
+{
+  EggTaskCache *self = (EggTaskCache *)object;
+
+  if (self->cache != NULL)
+    {
+      EGG_COUNTER_SUB (cached, g_hash_table_size (self->cache));
+      g_clear_pointer (&self->cache, g_hash_table_unref);
+    }
+
+  if (self->queued != NULL)
+    {
+      EGG_COUNTER_SUB (queued, g_hash_table_size (self->queued));
+      g_clear_pointer (&self->queued, g_hash_table_unref);
+    }
+
+  if (self->in_flight != NULL)
+    {
+      EGG_COUNTER_SUB (in_flight, g_hash_table_size (self->in_flight));
+      g_clear_pointer (&self->in_flight, g_hash_table_unref);
+    }
+
+  G_OBJECT_CLASS (egg_task_cache_parent_class)->dispose (object);
+}
+
+static void
+egg_task_cache_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (egg_task_cache_parent_class)->finalize (object);
+
+  EGG_COUNTER_DEC (instances);
+}
+
+static void
+egg_task_cache_class_init (EggTaskCacheClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = egg_task_cache_dispose;
+  object_class->finalize = egg_task_cache_finalize;
+}
+
+void
+egg_task_cache_init (EggTaskCache *self)
+{
+  EGG_COUNTER_INC (instances);
+
+  self->cache = g_hash_table_new_full (g_str_hash,
+                                       g_str_equal,
+                                       g_free,
+                                       g_object_unref);
+
+  self->in_flight = g_hash_table_new_full (g_str_hash,
+                                           g_str_equal,
+                                           g_free,
+                                           g_object_unref);
+
+  self->queued = g_hash_table_new_full (g_str_hash,
+                                        g_str_equal,
+                                        g_free,
+                                        (GDestroyNotify)g_ptr_array_unref);
+}
+
+EggTaskCache *
+egg_task_cache_new (void)
+{
+  return g_object_new (EGG_TYPE_TASK_CACHE, NULL);
+}
diff --git a/contrib/egg/egg-task-cache.h b/contrib/egg/egg-task-cache.h
new file mode 100644
index 0000000..52ff38b
--- /dev/null
+++ b/contrib/egg/egg-task-cache.h
@@ -0,0 +1,42 @@
+/* egg-task-cache.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_TASK_CACHE_H
+#define EGG_TASK_CACHE_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_TASK_CACHE (egg_task_cache_get_type())
+
+G_DECLARE_FINAL_TYPE (EggTaskCache, egg_task_cache, EGG, TASK_CACHE, GObject)
+
+EggTaskCache *egg_task_cache_new      (void);
+void          egg_task_cache_populate (EggTaskCache    *self,
+                                       const gchar     *key,
+                                       GTask           *task,
+                                       GTaskThreadFunc  thread_func,
+                                       gpointer         task_data,
+                                       GDestroyNotify   task_data_destroy);
+gboolean      egg_task_cache_evict    (EggTaskCache    *self,
+                                       const gchar     *key);
+
+G_END_DECLS
+
+#endif /* EGG_TASK_CACHE_H */


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