[gnome-builder] egg-cache: make egg-cache more friendly to non-threaded async ops



commit c382f0ffe6ebd046b77ac3a76b13496d688617bb
Author: Christian Hergert <christian hergert me>
Date:   Tue May 12 17:13:01 2015 -0700

    egg-cache: make egg-cache more friendly to non-threaded async ops
    
    This allows the callback to determine whether or not it wants to do
    the work threaded.
    
    This should make it a bit easier to plug into the clang source.

 contrib/egg/egg-task-cache.c |  631 ++++++++++++++++++++++++++++++++++--------
 contrib/egg/egg-task-cache.h |   47 +++-
 tests/Makefile.am            |    6 +
 tests/test-egg-cache.c       |   65 +++++
 4 files changed, 624 insertions(+), 125 deletions(-)
---
diff --git a/contrib/egg/egg-task-cache.c b/contrib/egg/egg-task-cache.c
index 1c3b9bb..09657c1 100644
--- a/contrib/egg/egg-task-cache.c
+++ b/contrib/egg/egg-task-cache.c
@@ -16,23 +16,45 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define G_LOG_DOMAIN "egg-task-cache"
+
+#include <glib/gi18n.h>
+
 #include "egg-counter.h"
+#include "egg-heap.h"
 #include "egg-task-cache.h"
 
 struct _EggTaskCache
 {
-  GObject     parent_instance;
+  GObject               parent_instance;
+
+  GHashFunc             key_hash_func;
+  GEqualFunc            key_equal_func;
+  GBoxedCopyFunc        key_copy_func;
+  GBoxedFreeFunc        key_destroy_func;
+  EggTaskCacheCallback  populate_callback;
 
-  GHashTable *cache;
-  GHashTable *in_flight;
-  GHashTable *queued;
+  GHashTable           *cache;
+  GHashTable           *in_flight;
+  GHashTable           *queued;
+
+  EggHeap              *evict_heap;
+  guint                 evict_source;
+
+  gint64                time_to_live_usec;
 };
 
 typedef struct
 {
-  EggTaskCache *self;
-  gchar        *key;
-} CacheFault;
+  GObject *item;
+  gint64   evict_at;
+} CacheItem;
+
+typedef struct
+{
+  GSource  source;
+  EggHeap *heap;
+} EvictSource;
 
 G_DEFINE_TYPE (EggTaskCache, egg_task_cache, G_TYPE_OBJECT)
 
@@ -43,165 +65,426 @@ EGG_DEFINE_COUNTER (cached,     "EggTaskCache", "Cache Size", "Number of cached
 EGG_DEFINE_COUNTER (hits,       "EggTaskCache", "Cache Hits", "Number of cache hits")
 EGG_DEFINE_COUNTER (misses,     "EggTaskCache", "Cache Miss", "Number of cache misses")
 
+enum {
+  PROP_0,
+  PROP_KEY_COPY_FUNC,
+  PROP_KEY_DESTROY_FUNC,
+  PROP_KEY_EQUAL_FUNC,
+  PROP_KEY_HASH_FUNC,
+  PROP_POPULATE_CALLBACK,
+  PROP_TIME_TO_LIVE,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
 static gboolean
-egg_task_cache_populate_from_cache (EggTaskCache *self,
-                                    const gchar  *key,
-                                    GTask        *task)
+evict_source_prepare (GSource *source,
+                      gint    *timeout)
 {
-  GObject *obj;
+  EvictSource *ev = (EvictSource *)source;
 
-  g_assert (self != NULL);
-  g_assert (key != NULL);
-  g_assert (G_IS_TASK (task));
+  if (ev->heap->len > 0)
+    {
+      CacheItem *item;
+
+      item = egg_heap_peek (ev->heap, gpointer);
+      *timeout = (item->evict_at - g_get_monotonic_time ());
+
+      return (*timeout) < 0;
+    }
+
+  *timeout = -1;
+
+  return FALSE;
+}
+
+static gboolean
+evict_source_check (GSource *source)
+{
+  EvictSource *ev = (EvictSource *)source;
+  CacheItem *item;
+
+  if ((ev->heap->len > 0) && (item = egg_heap_peek (ev->heap, gpointer)))
+    return (g_get_monotonic_time () <= item->evict_at);
+
+  return FALSE;
+}
+
+static gboolean
+evict_source_dispatch (GSource     *source,
+                       GSourceFunc  callback,
+                       gpointer     user_data)
+{
+  if (callback != NULL)
+    return callback (user_data);
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+evict_source_finalize (GSource *source)
+{
+  EvictSource *ev = (EvictSource *)source;
+
+  g_clear_pointer (&ev->heap, egg_heap_unref);
+}
+
+static GSourceFuncs evict_source_funcs = {
+  evict_source_prepare,
+  evict_source_check,
+  evict_source_dispatch,
+  evict_source_finalize,
+};
+
+static void
+cache_item_free (gpointer data)
+{
+  CacheItem *item = data;
+
+  g_object_unref (item->item);
+  g_slice_free (CacheItem, item);
+}
+
+static gint
+cache_item_compare_evict_at (gconstpointer a,
+                             gconstpointer b)
+{
+  const CacheItem *ci1 = a;
+  const CacheItem *ci2 = b;
 
-  obj = g_hash_table_lookup (self->cache, key);
+  return ci1->evict_at - ci2->evict_at;
+}
+
+static CacheItem *
+cache_item_new (GObject *item,
+                gint64   time_to_live_usec)
+{
+  CacheItem *ret;
+
+  ret = g_slice_new0 (CacheItem);
+  ret->item = g_object_ref (item);
+  if (time_to_live_usec > 0)
+    ret->evict_at = g_get_monotonic_time () + time_to_live_usec;
+
+  return ret;
+}
 
-  if (obj != NULL)
+gboolean
+egg_task_cache_evict (EggTaskCache  *self,
+                      gconstpointer  key)
+{
+  CacheItem *item;
+
+  g_return_val_if_fail (EGG_IS_TASK_CACHE (self), FALSE);
+
+  if ((item = g_hash_table_lookup (self->cache, key)))
     {
-      g_task_return_pointer (task, g_object_ref (obj), g_object_unref);
+      gsize i;
+
+      for (i = 0; i < self->evict_heap->len; i++)
+        {
+          if (item == egg_heap_index (self->evict_heap, gpointer, i))
+            {
+              egg_heap_extract_index (self->evict_heap, i, NULL);
+              break;
+            }
+        }
+
+      g_hash_table_remove (self->cache, key);
+
+      EGG_COUNTER_DEC (cached);
+
       return TRUE;
     }
 
   return FALSE;
 }
 
-static void
-egg_task_cache_populate_cb (GObject      *object,
-                            GAsyncResult *result,
-                            gpointer      user_data)
+/**
+ * egg_task_cache_peek:
+ * @self: An #EggTaskCache
+ * @key: The key for the cache
+ *
+ * Peeks to see @key is contained in the cache and returns the
+ * matching #GObject if it does.
+ *
+ * The reference count of the resulting #GObject is not incremented.
+ * For that reason, it is important to remember that this function
+ * may only be called from the main thread.
+ *
+ * Returns: (type GObject) (nullable): A #GObject or %NULL if the
+ *   key was not found in the cache.
+ */
+gpointer
+egg_task_cache_peek (EggTaskCache  *self,
+                     gconstpointer  key)
 {
-  GTask *task = (GTask *)result;
-  CacheFault *fault = user_data;
-  EggTaskCache *self;
-  GPtrArray *queued;
-  GObject *ret;
-  GError *error = NULL;
+  CacheItem *item;
 
-  g_assert (G_IS_TASK (task));
-  g_assert (fault != NULL);
+  g_return_val_if_fail (EGG_IS_TASK_CACHE (self), NULL);
 
-  self = fault->self;
-  g_assert (EGG_IS_TASK_CACHE (self));
+  if ((item = g_hash_table_lookup (self->cache, key)))
+    {
+      EGG_COUNTER_INC (hits);
+      return item->item;
+    }
+
+  return NULL;
+}
 
-  ret = g_task_propagate_pointer (task, &error);
-  g_assert (!ret || G_IS_OBJECT (ret));
+static void
+egg_task_cache_propagate_error (EggTaskCache  *self,
+                                gconstpointer  key,
+                                const GError  *error)
+{
+  g_autoptr(GPtrArray) queued = NULL;
+
+  g_assert (EGG_IS_TASK_CACHE (self));
+  g_assert (error != NULL);
 
-  if (ret != NULL)
+  if ((queued = g_hash_table_lookup (self->queued, key)))
     {
-      g_hash_table_insert (self->cache, g_strdup (fault->key), g_object_ref (ret));
+      gsize i;
+
+      g_hash_table_steal (self->queued, key);
+
+      for (i = 0; i < queued->len; i++)
+        {
+          GTask *task;
+
+          task = g_ptr_array_index (queued, i);
+          g_task_return_error (task, g_error_copy (error));
+        }
 
-      EGG_COUNTER_INC (cached);
+      EGG_COUNTER_SUB (queued, queued->len);
     }
+}
+
+static void
+egg_task_cache_populate (EggTaskCache  *self,
+                         gconstpointer  key,
+                         gpointer       value)
+{
+  CacheItem *item;
+
+  g_assert (EGG_IS_TASK_CACHE (self));
+  g_assert (G_IS_OBJECT (value));
+
+  item = cache_item_new (value, self->time_to_live_usec);
+
+  g_hash_table_insert (self->cache,
+                       self->key_copy_func ((gpointer)key),
+                       item);
+  egg_heap_insert_val (self->evict_heap, item);
 
-  g_hash_table_remove (self->in_flight, fault->key);
+  EGG_COUNTER_INC (cached);
+}
 
-  if ((queued = g_hash_table_lookup (self->queued, fault->key)))
+static void
+egg_task_cache_propagate_pointer (EggTaskCache  *self,
+                                  gconstpointer  key,
+                                  gpointer       value)
+{
+  g_autoptr(GPtrArray) queued = NULL;
+
+  g_assert (EGG_IS_TASK_CACHE (self));
+  g_assert (G_IS_OBJECT (value));
+
+  if ((queued = g_hash_table_lookup (self->queued, key)))
     {
       gsize i;
 
-      g_hash_table_steal (self->queued, fault->key);
+      g_hash_table_steal (self->queued, 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));
+          GTask *task;
+
+          task = g_ptr_array_index (queued, i);
+          g_task_return_pointer (task, g_object_ref (value), g_object_unref);
         }
 
       EGG_COUNTER_SUB (queued, queued->len);
+    }
+}
 
-      g_ptr_array_unref (queued);
+static void
+egg_task_cache_fetch_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  EggTaskCache *self = (EggTaskCache *)object;
+  GTask *task = (GTask *)result;
+  GError *error = NULL;
+  gpointer key = user_data;
+  gpointer ret;
+
+  g_assert (EGG_IS_TASK_CACHE (self));
+  g_assert (G_IS_TASK (task));
+
+  if (!(ret = g_task_propagate_pointer (task, &error)))
+    {
+      egg_task_cache_propagate_error (self, key, error);
+      g_clear_error (&error);
+    }
+  else
+    {
+      egg_task_cache_populate (self, key, ret);
+      egg_task_cache_propagate_pointer (self, key, ret);
+      g_clear_object (&ret);
     }
 
+  g_hash_table_remove (self->in_flight, key);
   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);
+  self->key_destroy_func (key);
+  g_object_unref (task);
 }
 
 void
-egg_task_cache_populate (EggTaskCache    *self,
-                         const gchar     *key,
-                         GTask           *task,
-                         GTaskThreadFunc  thread_func,
-                         gpointer         task_data,
-                         GDestroyNotify   task_data_destroy)
+egg_task_cache_get_async (EggTaskCache        *self,
+                          gconstpointer        key,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
 {
+  g_autoptr(GTask) task = NULL;
   GPtrArray *queued;
+  gpointer ret;
 
-  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);
+  g_return_if_fail (EGG_IS_TASK_CACHE (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  if (egg_task_cache_populate_from_cache (self, key, task))
-    {
-      if (task_data_destroy)
-        task_data_destroy (task_data);
-
-      EGG_COUNTER_INC (hits);
+  task = g_task_new (self, cancellable, callback, user_data);
 
+  /*
+   * If we have the answer, return it now.
+   */
+  if ((ret = egg_task_cache_peek (self, key)))
+    {
+      g_task_return_pointer (task, g_object_ref (ret), g_object_unref);
       return;
     }
 
   EGG_COUNTER_INC (misses);
 
-  if ((queued = g_hash_table_lookup (self->queued, key)) == NULL)
+  /*
+   * Always queue the request. If we need to dispatch the worker to
+   * fetch the result, that will happen with another task.
+   */
+  if (!(queued = g_hash_table_lookup (self->queued, key)))
     {
       queued = g_ptr_array_new_with_free_func (g_object_unref);
-      g_hash_table_insert (self->queued, g_strdup (key), queued);
+      g_hash_table_insert (self->queued,
+                           self->key_copy_func ((gpointer)key),
+                           queued);
     }
 
   g_ptr_array_add (queued, g_object_ref (task));
-
   EGG_COUNTER_INC (queued);
 
-  if (!g_hash_table_contains (self->in_flight, key))
+  /*
+   * The in_flight hashtable will have a bit set if we have queued
+   * an operation for this key.
+   */
+  if (!g_hash_table_lookup (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);
+      GTask *fetch_task;
 
-      fault_task = g_task_new (source_object,
+      fetch_task = g_task_new (self,
                                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_task_cache_fetch_cb,
+                               self->key_copy_func ((gpointer)key));
+      g_hash_table_insert (self->in_flight,
+                           self->key_copy_func ((gpointer)key),
+                           GINT_TO_POINTER (TRUE));
+      self->populate_callback (self, key, fetch_task);
 
       EGG_COUNTER_INC (in_flight);
     }
 }
 
-gboolean
-egg_task_cache_evict (EggTaskCache *self,
-                      const gchar  *key)
+gpointer
+egg_task_cache_get_finish (EggTaskCache  *self,
+                           GAsyncResult  *result,
+                           GError       **error)
 {
-  g_return_val_if_fail (EGG_IS_TASK_CACHE (self), FALSE);
+  GTask *task = (GTask *)result;
 
-  if (g_hash_table_remove (self->cache, key))
+  g_return_val_if_fail (EGG_IS_TASK_CACHE (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+  g_return_val_if_fail (G_IS_TASK (task), NULL);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+static void
+egg_task_cache_constructed (GObject *object)
+{
+  EggTaskCache *self = (EggTaskCache *)object;
+
+  G_OBJECT_CLASS (egg_task_cache_parent_class)->constructed (object);
+
+  if ((self->key_copy_func == NULL) ||
+      (self->key_destroy_func == NULL) ||
+      (self->key_equal_func == NULL) ||
+      (self->key_hash_func == NULL) ||
+      (self->populate_callback == NULL))
     {
-      EGG_COUNTER_DEC (cached);
-      return TRUE;
+      g_error ("EggTaskCache was configured improperly.");
+      return;
     }
 
-  return FALSE;
+  /*
+   * This is where the cached result objects live.
+   */
+  self->cache = g_hash_table_new_full (self->key_hash_func,
+                                       self->key_equal_func,
+                                       self->key_destroy_func,
+                                       cache_item_free);
+
+  /*
+   * This is where we store a bit to know if we have an inflight
+   * request for this cache key.
+   */
+  self->in_flight = g_hash_table_new_full (self->key_hash_func,
+                                           self->key_equal_func,
+                                           self->key_destroy_func,
+                                           NULL);
+
+  /*
+   * This is where tasks queue waiting for an in_flight callback.
+   */
+  self->queued = g_hash_table_new_full (self->key_hash_func,
+                                        self->key_equal_func,
+                                        self->key_destroy_func,
+                                        (GDestroyNotify)g_ptr_array_unref);
+
+  /*
+   * Register our eviction source if we have a time_to_live.
+   */
+  if (self->time_to_live_usec > 0)
+    {
+      EvictSource *source;
+      GMainContext *main_context;
+
+      source = (EvictSource *)g_source_new (&evict_source_funcs, sizeof (EvictSource));
+      source->heap = egg_heap_ref (self->evict_heap);
+
+      main_context = g_main_context_get_thread_default ();
+      self->evict_source =  g_source_attach ((GSource *)source, main_context);
+    }
+}
+
+static void
+count_queued_cb (gpointer key,
+                 gpointer value,
+                 gpointer user_data)
+{
+  GPtrArray *ar = value;
+  gint64 *count = user_data;
+
+  (*count) += ar->len;
 }
 
 static void
@@ -209,22 +492,42 @@ egg_task_cache_dispose (GObject *object)
 {
   EggTaskCache *self = (EggTaskCache *)object;
 
+  if (self->evict_source)
+    {
+      g_source_remove (self->evict_source);
+      self->evict_source = 0;
+    }
+
+  g_clear_pointer (&self->evict_heap, egg_heap_unref);
+
   if (self->cache != NULL)
     {
-      EGG_COUNTER_SUB (cached, g_hash_table_size (self->cache));
+      gint64 count;
+
+      count = g_hash_table_size (self->cache);
       g_clear_pointer (&self->cache, g_hash_table_unref);
+
+      EGG_COUNTER_SUB (cached, count);
     }
 
   if (self->queued != NULL)
     {
-      EGG_COUNTER_SUB (queued, g_hash_table_size (self->queued));
+      gint64 count = 0;
+
+      g_hash_table_foreach (self->queued, count_queued_cb, &count);
       g_clear_pointer (&self->queued, g_hash_table_unref);
+
+      EGG_COUNTER_SUB (queued, count);
     }
 
   if (self->in_flight != NULL)
     {
-      EGG_COUNTER_SUB (in_flight, g_hash_table_size (self->in_flight));
+      gint64 count;
+
+      count = g_hash_table_size (self->in_flight);
       g_clear_pointer (&self->in_flight, g_hash_table_unref);
+
+      EGG_COUNTER_SUB (in_flight, count);
     }
 
   G_OBJECT_CLASS (egg_task_cache_parent_class)->dispose (object);
@@ -239,12 +542,102 @@ egg_task_cache_finalize (GObject *object)
 }
 
 static void
+egg_task_cache_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  EggTaskCache *self = EGG_TASK_CACHE(object);
+
+  switch (prop_id)
+    {
+    case PROP_KEY_COPY_FUNC:
+      self->key_copy_func = g_value_get_pointer (value);
+      break;
+
+    case PROP_KEY_DESTROY_FUNC:
+      self->key_destroy_func = g_value_get_pointer (value);
+      break;
+
+    case PROP_KEY_EQUAL_FUNC:
+      self->key_equal_func = g_value_get_pointer (value);
+      break;
+
+    case PROP_KEY_HASH_FUNC:
+      self->key_hash_func = g_value_get_pointer (value);
+      break;
+
+    case PROP_POPULATE_CALLBACK:
+      self->populate_callback = g_value_get_pointer (value);
+      break;
+
+    case PROP_TIME_TO_LIVE:
+      self->time_to_live_usec = (g_value_get_int64 (value) * 1000L);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
 egg_task_cache_class_init (EggTaskCacheClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+  object_class->constructed = egg_task_cache_constructed;
   object_class->dispose = egg_task_cache_dispose;
   object_class->finalize = egg_task_cache_finalize;
+  object_class->set_property = egg_task_cache_set_property;
+
+  gParamSpecs [PROP_KEY_HASH_FUNC] =
+    g_param_spec_pointer ("key-hash-func",
+                         _("Key Hash Func"),
+                         _("Key Hash Func"),
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  gParamSpecs [PROP_KEY_EQUAL_FUNC] =
+    g_param_spec_pointer ("key-equal-func",
+                         _("Key Equal Func"),
+                         _("Key Equal Func"),
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  gParamSpecs [PROP_KEY_COPY_FUNC] =
+    g_param_spec_pointer ("key-copy-func",
+                         _("Key Copy Func"),
+                         _("Key Copy Func"),
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  gParamSpecs [PROP_KEY_DESTROY_FUNC] =
+    g_param_spec_pointer ("key-destroy-func",
+                         _("Key Destroy Func"),
+                         _("Key Destroy Func"),
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  gParamSpecs [PROP_POPULATE_CALLBACK] =
+    g_param_spec_pointer ("populate-callback",
+                         _("Populate Callback"),
+                         _("Populate Callback"),
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * EggTaskCache:time-to-live:
+   *
+   * This is the number of milliseconds before an item should be evicted
+   * from the cache.
+   *
+   * A value of zero indicates no eviction.
+   */
+  gParamSpecs [PROP_TIME_TO_LIVE] =
+    g_param_spec_int64 ("time-to-live",
+                        _("Time to Live"),
+                        _("The time to live in milliseconds."),
+                        0,
+                        G_MAXINT64,
+                        30 * 1000,
+                        (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
 }
 
 void
@@ -252,24 +645,30 @@ 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);
+  self->evict_heap = egg_heap_new (sizeof (gpointer),
+                                   cache_item_compare_evict_at);
 }
 
 EggTaskCache *
-egg_task_cache_new (void)
+egg_task_cache_new (GHashFunc            key_hash_func,
+                    GEqualFunc           key_equal_func,
+                    GBoxedCopyFunc       key_copy_func,
+                    GBoxedFreeFunc       key_destroy_func,
+                    EggTaskCacheCallback populate_callback,
+                    gint64               time_to_live)
 {
-  return g_object_new (EGG_TYPE_TASK_CACHE, NULL);
+  g_return_val_if_fail (key_hash_func, NULL);
+  g_return_val_if_fail (key_equal_func, NULL);
+  g_return_val_if_fail (key_copy_func, NULL);
+  g_return_val_if_fail (key_destroy_func, NULL);
+  g_return_val_if_fail (populate_callback, NULL);
+
+  return g_object_new (EGG_TYPE_TASK_CACHE,
+                       "key-hash-func", key_hash_func,
+                       "key-equal-func", key_equal_func,
+                       "key-copy-func", key_copy_func,
+                       "key-destroy-func", key_destroy_func,
+                       "populate-callback", populate_callback,
+                       "time-to-live", time_to_live,
+                       NULL);
 }
diff --git a/contrib/egg/egg-task-cache.h b/contrib/egg/egg-task-cache.h
index 52ff38b..10068f4 100644
--- a/contrib/egg/egg-task-cache.h
+++ b/contrib/egg/egg-task-cache.h
@@ -27,15 +27,44 @@ G_BEGIN_DECLS
 
 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);
+/**
+ * EggTaskCacheCallback:
+ * @self: An #EggTaskCache.
+ * @key: the key to fetch
+ * @task: the task to be completed
+ *
+ * #EggTaskCacheCallback is the prototype for a function to be executed to
+ * populate a an item in the cache.
+ *
+ * This function will be executed when a fault (cache miss) occurs from
+ * a caller requesting an item from the cache.
+ *
+ * The callee may complete the operation asynchronously, but MUST return
+ * either a GObject using g_task_return_pointer() or a #GError using
+ * g_task_return_error() or g_task_return_new_error().
+ */
+typedef void (*EggTaskCacheCallback) (EggTaskCache  *self,
+                                      gconstpointer  key,
+                                      GTask         *task);
+
+EggTaskCache *egg_task_cache_new        (GHashFunc              key_hash_func,
+                                         GEqualFunc             key_equal_func,
+                                         GBoxedCopyFunc         key_copy_func,
+                                         GBoxedFreeFunc         key_destroy_func,
+                                         EggTaskCacheCallback   populate_callback,
+                                         gint64                 time_to_live_msec);
+void          egg_task_cache_get_async  (EggTaskCache          *self,
+                                         gconstpointer          key,
+                                         GCancellable          *cancellable,
+                                         GAsyncReadyCallback    callback,
+                                         gpointer               user_data);
+gpointer      egg_task_cache_get_finish (EggTaskCache          *self,
+                                         GAsyncResult          *result,
+                                         GError               **error);
+gboolean      egg_task_cache_evict      (EggTaskCache          *self,
+                                         gconstpointer          key);
+gpointer      egg_task_cache_peek       (EggTaskCache          *self,
+                                         gconstpointer          key);
 
 G_END_DECLS
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 8ced8ca..2dd2fc0 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -106,6 +106,12 @@ test_egg_state_machine_CFLAGS = $(tests_cflags) -I$(top_srcdir)/contrib/egg
 test_egg_state_machine_LDADD = $(tests_libs) $(top_builddir)/contrib/egg/libegg.la
 
 
+TESTS += test-egg-cache
+test_egg_cache_SOURCES = test-egg-cache.c
+test_egg_cache_CFLAGS = $(tests_cflags) -I$(top_srcdir)/contrib/egg
+test_egg_cache_LDADD = $(tests_libs) $(top_builddir)/contrib/egg/libegg.la
+
+
 TESTS += test-egg-heap
 test_egg_heap_SOURCES = test-egg-heap.c
 test_egg_heap_CFLAGS = $(tests_cflags) -I$(top_srcdir)/contrib/egg
diff --git a/tests/test-egg-cache.c b/tests/test-egg-cache.c
new file mode 100644
index 0000000..0c90dd4
--- /dev/null
+++ b/tests/test-egg-cache.c
@@ -0,0 +1,65 @@
+#include "egg-task-cache.h"
+
+static GMainLoop *main_loop;
+static EggTaskCache *cache;
+static GObject *foo;
+
+static void
+populate_callback (EggTaskCache  *self,
+                   gconstpointer  key,
+                   GTask         *task)
+{
+  foo = g_object_new (G_TYPE_OBJECT, NULL);
+  g_object_add_weak_pointer (G_OBJECT (foo), (gpointer *)&foo);
+  g_task_return_pointer (task, foo, g_object_unref);
+}
+
+static void
+get_foo_cb (GObject      *object,
+            GAsyncResult *result,
+            gpointer      user_data)
+{
+  GError *error = NULL;
+  GObject *ret;
+
+  ret = egg_task_cache_get_finish (cache, result, &error);
+  g_assert_no_error (error);
+  g_assert (ret != NULL);
+  g_assert (ret == foo);
+
+  g_assert (egg_task_cache_evict (cache, "foo"));
+  g_object_unref (ret);
+
+  g_main_loop_quit (main_loop);
+}
+
+static void
+test_task_cache (void)
+{
+  main_loop = g_main_loop_new (NULL, FALSE);
+  cache = egg_task_cache_new (g_str_hash,
+                              g_str_equal,
+                              (GBoxedCopyFunc)g_strdup,
+                              (GBoxedFreeFunc)g_free,
+                              populate_callback,
+                              100 /* msec */);
+
+  g_assert (!egg_task_cache_peek (cache, "foo"));
+  g_assert (!egg_task_cache_evict (cache, "foo"));
+
+  egg_task_cache_get_async (cache, "foo", NULL, get_foo_cb, NULL);
+
+  g_main_loop_run (main_loop);
+  g_main_loop_unref (main_loop);
+
+  g_assert (foo == NULL);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/Egg/TaskCache/basic", test_task_cache);
+  return g_test_run ();
+}


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