[gnome-builder] egg-cache: make egg-cache more friendly to non-threaded async ops
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] egg-cache: make egg-cache more friendly to non-threaded async ops
- Date: Wed, 13 May 2015 00:13:57 +0000 (UTC)
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]