[gnome-photos/wip/rishi/task-cache: 1/11] Add EggTaskCache
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-photos/wip/rishi/task-cache: 1/11] Add EggTaskCache
- Date: Tue, 7 Mar 2017 12:26:43 +0000 (UTC)
commit e10033cb7e42a5c0726db8c527d908beeb9edf89
Author: Debarshi Ray <debarshir gnome org>
Date: Sun Feb 26 17:46:32 2017 +0100
Add EggTaskCache
... and its dependency EggHeap.
src/Makefile.am | 4 +
src/egg-heap.c | 387 ++++++++++++++++++
src/egg-heap.h | 55 +++
src/egg-task-cache.c | 1113 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/egg-task-cache.h | 82 ++++
5 files changed, 1641 insertions(+), 0 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 82c7cad..b8e6951 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -40,6 +40,10 @@ nodist_gnome_photos_SOURCES = \
gnome_photos_SOURCES = \
egg-counter.c \
egg-counter.h \
+ egg-heap.c \
+ egg-heap.h \
+ egg-task-cache.c \
+ egg-task-cache.h \
photos-application.c \
photos-application.h \
photos-base-manager.c \
diff --git a/src/egg-heap.c b/src/egg-heap.c
new file mode 100644
index 0000000..29586fa
--- /dev/null
+++ b/src/egg-heap.c
@@ -0,0 +1,387 @@
+/* egg-heap.c
+ *
+ * Copyright (C) 2014 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/>.
+ */
+
+#define G_LOG_DOMAIN "egg-heap"
+
+#include <string.h>
+
+#include "egg-heap.h"
+
+/**
+ * SECTION:eggheap
+ * @title: Heaps
+ * @short_description: efficient priority queues using min/max heaps
+ *
+ * Heaps are similar to a partially sorted tree but implemented as an
+ * array. They allow for efficient O(1) lookup of the highest priority
+ * item as it will always be the first item of the array.
+ *
+ * To create a new heap use egg_heap_new().
+ *
+ * To add items to the heap, use egg_heap_insert_val() or
+ * egg_heap_insert_vals() to insert in bulk.
+ *
+ * To access an item in the heap, use egg_heap_index().
+ *
+ * To remove an arbitrary item from the heap, use egg_heap_extract_index().
+ *
+ * To remove the highest priority item in the heap, use egg_heap_extract().
+ *
+ * To free a heap, use egg_heap_unref().
+ *
+ * Here is an example that stores integers in a #EggHeap:
+ * |[<!-- language="C" -->
+ * static int
+ * cmpint (gconstpointer a,
+ * gconstpointer b)
+ * {
+ * return *(const gint *)a - *(const gint *)b;
+ * }
+ *
+ * int
+ * main (gint argc,
+ * gchar *argv[])
+ * {
+ * EggHeap *heap;
+ * gint i;
+ * gint v;
+ *
+ * heap = egg_heap_new (sizeof (gint), cmpint);
+ *
+ * for (i = 0; i < 10000; i++)
+ * egg_heap_insert_val (heap, i);
+ * for (i = 0; i < 10000; i++)
+ * egg_heap_extract (heap, &v);
+ *
+ * egg_heap_unref (heap);
+ * }
+ * ]|
+ */
+
+#define MIN_HEAP_SIZE 16
+
+/*
+ * Based upon Mastering Algorithms in C by Kyle Loudon.
+ * Section 10 - Heaps and Priority Queues.
+ */
+
+G_DEFINE_BOXED_TYPE (EggHeap, egg_heap, egg_heap_ref, egg_heap_unref)
+
+typedef struct _EggHeapReal EggHeapReal;
+
+struct _EggHeapReal
+{
+ gchar *data;
+ gssize len;
+ volatile gint ref_count;
+ guint element_size;
+ gsize allocated_len;
+ GCompareFunc compare;
+ gchar tmp[0];
+};
+
+#define heap_parent(npos) (((npos)-1)/2)
+#define heap_left(npos) (((npos)*2)+1)
+#define heap_right(npos) (((npos)*2)+2)
+#define heap_index(h,i) ((h)->data + (i * (h)->element_size))
+#define heap_compare(h,a,b) ((h)->compare(heap_index(h,a), heap_index(h,b)))
+#define heap_swap(h,a,b) \
+ G_STMT_START { \
+ memcpy ((h)->tmp, heap_index (h, a), (h)->element_size); \
+ memcpy (heap_index (h, a), heap_index (h, b), (h)->element_size); \
+ memcpy (heap_index (h, b), (h)->tmp, (h)->element_size); \
+ } G_STMT_END
+
+/**
+ * egg_heap_new:
+ * @element_size: the size of each element in the heap
+ * @compare_func: (scope async): a function to compare to elements
+ *
+ * Creates a new #EggHeap. A heap is a tree-like structure stored in
+ * an array that is not fully sorted, but head is guaranteed to be either
+ * the max, or min value based on @compare_func. This is also known as
+ * a priority queue.
+ *
+ * Returns: (transfer full): A newly allocated #EggHeap
+ */
+EggHeap *
+egg_heap_new (guint element_size,
+ GCompareFunc compare_func)
+{
+ EggHeapReal *real;
+
+ g_return_val_if_fail (element_size, NULL);
+ g_return_val_if_fail (compare_func, NULL);
+
+ real = g_malloc_n (1, sizeof (EggHeapReal) + element_size);
+ real->data = NULL;
+ real->len = 0;
+ real->ref_count = 1;
+ real->element_size = element_size;
+ real->allocated_len = 0;
+ real->compare = compare_func;
+
+ return (EggHeap *)real;
+}
+
+/**
+ * egg_heap_ref:
+ * @heap: An #EggHeap
+ *
+ * Increments the reference count of @heap by one.
+ *
+ * Returns: (transfer full): @heap
+ */
+EggHeap *
+egg_heap_ref (EggHeap *heap)
+{
+ EggHeapReal *real = (EggHeapReal *)heap;
+
+ g_return_val_if_fail (heap, NULL);
+ g_return_val_if_fail (real->ref_count, NULL);
+
+ g_atomic_int_inc (&real->ref_count);
+
+ return heap;
+}
+
+static void
+egg_heap_real_free (EggHeapReal *real)
+{
+ g_assert (real);
+ g_assert_cmpint (real->ref_count, ==, 0);
+
+ g_free (real->data);
+ g_free (real);
+}
+
+/**
+ * egg_heap_unref:
+ * @heap: (transfer full): An #EggHeap
+ *
+ * Decrements the reference count of @heap by one, freeing the structure
+ * when the reference count reaches zero.
+ */
+void
+egg_heap_unref (EggHeap *heap)
+{
+ EggHeapReal *real = (EggHeapReal *)heap;
+
+ g_return_if_fail (heap);
+ g_return_if_fail (real->ref_count);
+
+ if (g_atomic_int_dec_and_test (&real->ref_count))
+ egg_heap_real_free (real);
+}
+
+static void
+egg_heap_real_grow (EggHeapReal *real)
+{
+ g_assert (real);
+ g_assert_cmpint (real->allocated_len, <, G_MAXSIZE / 2);
+
+ real->allocated_len = MAX (MIN_HEAP_SIZE, (real->allocated_len * 2));
+ real->data = g_realloc_n (real->data,
+ real->allocated_len,
+ real->element_size);
+}
+
+static void
+egg_heap_real_shrink (EggHeapReal *real)
+{
+ g_assert (real);
+ g_assert ((real->allocated_len / 2) >= (gsize)real->len);
+
+ real->allocated_len = MAX (MIN_HEAP_SIZE, real->allocated_len / 2);
+ real->data = g_realloc_n (real->data,
+ real->allocated_len,
+ real->element_size);
+}
+
+static void
+egg_heap_real_insert_val (EggHeapReal *real,
+ gconstpointer data)
+{
+ gint ipos;
+ gint ppos;
+
+ g_assert (real);
+ g_assert (data);
+
+ if (G_UNLIKELY ((gsize)real->len == real->allocated_len))
+ egg_heap_real_grow (real);
+
+ memcpy (real->data + (real->element_size * real->len),
+ data,
+ real->element_size);
+
+ ipos = real->len;
+ ppos = heap_parent (ipos);
+
+ while ((ipos > 0) && (heap_compare (real, ppos, ipos) < 0))
+ {
+ heap_swap (real, ppos, ipos);
+ ipos = ppos;
+ ppos = heap_parent (ipos);
+ }
+
+ real->len++;
+}
+
+void
+egg_heap_insert_vals (EggHeap *heap,
+ gconstpointer data,
+ guint len)
+{
+ EggHeapReal *real = (EggHeapReal *)heap;
+ const gchar *ptr = data;
+ guint i;
+
+ g_return_if_fail (heap);
+ g_return_if_fail (data);
+ g_return_if_fail (len);
+ g_return_if_fail ((G_MAXSSIZE - len) > real->len);
+
+ for (i = 0; i < len; i++, ptr += real->element_size)
+ egg_heap_real_insert_val (real, ptr);
+}
+
+gboolean
+egg_heap_extract (EggHeap *heap,
+ gpointer result)
+{
+ EggHeapReal *real = (EggHeapReal *)heap;
+ gint ipos;
+ gint lpos;
+ gint rpos;
+ gint mpos;
+
+ g_return_val_if_fail (heap, FALSE);
+
+ if (real->len == 0)
+ return FALSE;
+
+ if (result)
+ memcpy (result, heap_index (real, 0), real->element_size);
+
+ if (--real->len > 0)
+ {
+ memmove (real->data,
+ heap_index (real, real->len),
+ real->element_size);
+
+ ipos = 0;
+
+ while (TRUE)
+ {
+ lpos = heap_left (ipos);
+ rpos = heap_right (ipos);
+
+ if ((lpos < real->len) && (heap_compare (real, lpos, ipos) > 0))
+ mpos = lpos;
+ else
+ mpos = ipos;
+
+ if ((rpos < real->len) && (heap_compare (real, rpos, mpos) > 0))
+ mpos = rpos;
+
+ if (mpos == ipos)
+ break;
+
+ heap_swap (real, mpos, ipos);
+
+ ipos = mpos;
+ }
+ }
+
+ if ((real->len > MIN_HEAP_SIZE) && (real->allocated_len / 2) >= (gsize)real->len)
+ egg_heap_real_shrink (real);
+
+ return TRUE;
+}
+
+gboolean
+egg_heap_extract_index (EggHeap *heap,
+ gsize index_,
+ gpointer result)
+{
+ EggHeapReal *real = (EggHeapReal *)heap;
+ gssize ipos;
+ gssize lpos;
+ gssize mpos;
+ gssize ppos;
+ gssize rpos;
+
+ g_return_val_if_fail (heap, FALSE);
+ g_return_val_if_fail (index_ < G_MAXSSIZE, FALSE);
+ g_return_val_if_fail (index_ < (gsize)real->len, FALSE);
+
+ if (real->len <= 0)
+ return FALSE;
+
+ if (result)
+ memcpy (result, heap_index (real, index_), real->element_size);
+
+ real->len--;
+
+ if (real->len > 0 && index_ != (gsize)real->len)
+ {
+ memcpy (heap_index (real, index_),
+ heap_index (real, real->len),
+ real->element_size);
+
+ ipos = index_;
+ ppos = heap_parent (ipos);
+
+ while (heap_compare (real, ipos, ppos) > 0)
+ {
+ heap_swap (real, ipos, ppos);
+ ipos = ppos;
+ ppos = heap_parent (ppos);
+ }
+
+ if (ipos == (gssize)index_)
+ {
+ while (TRUE)
+ {
+ lpos = heap_left (ipos);
+ rpos = heap_right (ipos);
+
+ if ((lpos < real->len) && (heap_compare (real, lpos, ipos) > 0))
+ mpos = lpos;
+ else
+ mpos = ipos;
+
+ if ((rpos < real->len) && (heap_compare (real, rpos, mpos) > 0))
+ mpos = rpos;
+
+ if (mpos == ipos)
+ break;
+
+ heap_swap (real, mpos, ipos);
+
+ ipos = mpos;
+ }
+ }
+ }
+
+ if ((real->len > MIN_HEAP_SIZE) && (real->allocated_len / 2) >= (gsize)real->len)
+ egg_heap_real_shrink (real);
+
+ return TRUE;
+}
diff --git a/src/egg-heap.h b/src/egg-heap.h
new file mode 100644
index 0000000..41b913f
--- /dev/null
+++ b/src/egg-heap.h
@@ -0,0 +1,55 @@
+/* egg-heap.h
+ *
+ * Copyright (C) 2014-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_HEAP_H
+#define EGG_HEAP_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_HEAP (egg_heap_get_type())
+#define egg_heap_insert_val(h,v) egg_heap_insert_vals(h,&(v),1)
+#define egg_heap_index(h,t,i) (((t*)(void*)(h)->data)[i])
+#define egg_heap_peek(h,t) egg_heap_index(h,t,0)
+
+typedef struct _EggHeap EggHeap;
+
+struct _EggHeap
+{
+ gchar *data;
+ gsize len;
+};
+
+GType egg_heap_get_type (void);
+EggHeap *egg_heap_new (guint element_size,
+ GCompareFunc compare_func);
+EggHeap *egg_heap_ref (EggHeap *heap);
+void egg_heap_unref (EggHeap *heap);
+void egg_heap_insert_vals (EggHeap *heap,
+ gconstpointer data,
+ guint len);
+gboolean egg_heap_extract (EggHeap *heap,
+ gpointer result);
+gboolean egg_heap_extract_index (EggHeap *heap,
+ gsize index_,
+ gpointer result);
+
+G_END_DECLS
+
+#endif /* EGG_HEAP_H */
diff --git a/src/egg-task-cache.c b/src/egg-task-cache.c
new file mode 100644
index 0000000..0e38ba7
--- /dev/null
+++ b/src/egg-task-cache.c
@@ -0,0 +1,1113 @@
+/* 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/>.
+ */
+
+#define G_LOG_DOMAIN "egg-task-cache"
+
+#include <glib/gi18n.h>
+
+#include "egg-counter.h"
+#include "egg-heap.h"
+#include "egg-task-cache.h"
+
+typedef struct
+{
+ EggTaskCache *self;
+ gpointer key;
+ gpointer value;
+ gint64 evict_at;
+} CacheItem;
+
+typedef struct
+{
+ EggTaskCache *self;
+ GCancellable *cancellable;
+ gpointer key;
+ gulong cancelled_id;
+} CancelledData;
+
+typedef struct
+{
+ GSource source;
+ EggHeap *heap;
+} EvictSource;
+
+struct _EggTaskCache
+{
+ GObject parent_instance;
+
+ GHashFunc key_hash_func;
+ GEqualFunc key_equal_func;
+ GBoxedCopyFunc key_copy_func;
+ GBoxedFreeFunc key_destroy_func;
+ GBoxedCopyFunc value_copy_func;
+ GBoxedFreeFunc value_destroy_func;
+
+ EggTaskCacheCallback populate_callback;
+ gpointer populate_callback_data;
+ GDestroyNotify populate_callback_data_destroy;
+
+ GHashTable *cache;
+ GHashTable *in_flight;
+ GHashTable *queued;
+
+ gchar *name;
+
+ EggHeap *evict_heap;
+ GSource *evict_source;
+ guint evict_source_id;
+
+ gint64 time_to_live_usec;
+};
+
+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 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_POPULATE_CALLBACK_DATA,
+ PROP_POPULATE_CALLBACK_DATA_DESTROY,
+ PROP_TIME_TO_LIVE,
+ PROP_VALUE_COPY_FUNC,
+ PROP_VALUE_DESTROY_FUNC,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static gboolean
+evict_source_check (GSource *source)
+{
+ EvictSource *ev = (EvictSource *)source;
+
+ g_assert (ev != NULL);
+ g_assert (ev->heap != NULL);
+
+ if (ev->heap->len > 0)
+ {
+ CacheItem *item;
+ gint64 now;
+
+ now = g_source_get_time (source);
+ item = egg_heap_peek (ev->heap, gpointer);
+
+ return (item->evict_at <= now);
+ }
+
+ return FALSE;
+}
+
+static void
+evict_source_rearm (GSource *source)
+{
+ EvictSource *evict_source = (EvictSource *)source;
+ gint64 ready_time = -1;
+
+ g_assert (source != NULL);
+ g_assert (evict_source != NULL);
+
+ if (evict_source->heap->len > 0)
+ {
+ CacheItem *item;
+
+ item = egg_heap_peek (evict_source->heap, gpointer);
+ ready_time = item->evict_at;
+ }
+
+ g_source_set_ready_time (source, ready_time);
+}
+
+static gboolean
+evict_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ gboolean ret = G_SOURCE_CONTINUE;
+
+ if (callback != NULL)
+ ret = callback (user_data);
+
+ evict_source_rearm (source);
+
+ return ret;
+}
+
+static void
+evict_source_finalize (GSource *source)
+{
+ EvictSource *ev = (EvictSource *)source;
+
+ g_clear_pointer (&ev->heap, egg_heap_unref);
+}
+
+static GSourceFuncs evict_source_funcs = {
+ NULL,
+ evict_source_check,
+ evict_source_dispatch,
+ evict_source_finalize,
+};
+
+static void
+cache_item_free (gpointer data)
+{
+ CacheItem *item = data;
+
+ g_clear_pointer (&item->key, item->self->key_destroy_func);
+ g_clear_pointer (&item->value, item->self->value_destroy_func);
+ item->self = NULL;
+ item->evict_at = 0;
+
+ g_slice_free (CacheItem, item);
+}
+
+static gint
+cache_item_compare_evict_at (gconstpointer a,
+ gconstpointer b)
+{
+ const CacheItem **ci1 = (const CacheItem **)a;
+ const CacheItem **ci2 = (const CacheItem **)b;
+ gint64 ret;
+
+ /*
+ * While unlikely, but since we are working with 64-bit monotonic clock and
+ * 32-bit return values, we can't do the normal (a - b) trick. We need to
+ * ensure we are within the 32-bit boundary.
+ */
+
+ ret = (*ci2)->evict_at - (*ci1)->evict_at;
+
+ if (ret < 0)
+ return -1;
+ else if (ret > 0)
+ return 1;
+ else
+ return 0;
+}
+
+static CacheItem *
+cache_item_new (EggTaskCache *self,
+ gconstpointer key,
+ gconstpointer value)
+{
+ CacheItem *ret;
+
+ g_assert (EGG_IS_TASK_CACHE (self));
+
+ ret = g_slice_new0 (CacheItem);
+ ret->self = self;
+ ret->key = self->key_copy_func ((gpointer)key);
+ ret->value = self->value_copy_func ((gpointer)value);
+ if (self->time_to_live_usec > 0)
+ ret->evict_at = g_get_monotonic_time () + self->time_to_live_usec;
+
+ return ret;
+}
+
+static void
+cancelled_data_free (gpointer data)
+{
+ CancelledData *cancelled = data;
+
+ g_clear_pointer (&cancelled->key, cancelled->self->key_destroy_func);
+
+ g_cancellable_disconnect (cancelled->cancellable, cancelled->cancelled_id);
+ cancelled->cancelled_id = 0;
+ g_clear_object (&cancelled->cancellable);
+
+ cancelled->self = NULL;
+
+ g_slice_free (CancelledData, cancelled);
+}
+
+static CancelledData *
+cancelled_data_new (EggTaskCache *self,
+ GCancellable *cancellable,
+ gconstpointer key,
+ gulong cancelled_id)
+{
+ CancelledData *ret;
+
+ ret = g_slice_new0 (CancelledData);
+ ret->self = self;
+ ret->cancellable = (cancellable != NULL) ? g_object_ref (cancellable) : NULL;
+ ret->key = self->key_copy_func ((gpointer)key);
+ ret->cancelled_id = cancelled_id;
+
+ return ret;
+}
+
+static gpointer
+egg_task_cache_dummy_copy_func (gpointer boxed)
+{
+ return boxed;
+}
+
+static void
+egg_task_cache_dummy_destroy_func (gpointer boxed)
+{
+}
+
+static gboolean
+egg_task_cache_evict_full (EggTaskCache *self,
+ gconstpointer key,
+ gboolean check_heap)
+{
+ CacheItem *item;
+
+ g_return_val_if_fail (EGG_IS_TASK_CACHE (self), FALSE);
+
+ if ((item = g_hash_table_lookup (self->cache, key)))
+ {
+ if (check_heap)
+ {
+ 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);
+
+ g_debug ("Evicted 1 item from %s", self->name ?: "unnamed cache");
+
+ if (self->evict_source != NULL)
+ evict_source_rearm (self->evict_source);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+egg_task_cache_evict (EggTaskCache *self,
+ gconstpointer key)
+{
+ return egg_task_cache_evict_full (self, key, TRUE);
+}
+
+void
+egg_task_cache_evict_all (EggTaskCache *self)
+{
+ guint size;
+
+ g_return_if_fail (EGG_IS_TASK_CACHE (self));
+
+ size = g_hash_table_size (self->cache);
+
+ while (self->evict_heap->len > 0)
+ {
+ CacheItem *item;
+
+ /* The cache item is owned by the hashtable, so safe to "leak" here */
+ egg_heap_extract_index (self->evict_heap, self->evict_heap->len - 1, &item);
+ }
+
+ g_hash_table_remove_all (self->cache);
+
+ EGG_COUNTER_SUB (cached, size);
+
+ if (self->evict_source != NULL)
+ evict_source_rearm (self->evict_source);
+}
+
+/**
+ * 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.Object) (nullable) (transfer none): A #GObject or
+ * %NULL if the key was not found in the cache.
+ */
+gpointer
+egg_task_cache_peek (EggTaskCache *self,
+ gconstpointer key)
+{
+ CacheItem *item;
+
+ g_return_val_if_fail (EGG_IS_TASK_CACHE (self), NULL);
+
+ if ((item = g_hash_table_lookup (self->cache, key)))
+ {
+ EGG_COUNTER_INC (hits);
+ return item->value;
+ }
+
+ return NULL;
+}
+
+static void
+egg_task_cache_propagate_error (EggTaskCache *self,
+ gconstpointer key,
+ const GError *error)
+{
+ GPtrArray *queued;
+
+ g_assert (EGG_IS_TASK_CACHE (self));
+ g_assert (error != NULL);
+
+ if ((queued = g_hash_table_lookup (self->queued, key)))
+ {
+ gint64 count = queued->len;
+ gsize i;
+
+ /* we can't use steal because we want the key freed */
+ g_ptr_array_ref (queued);
+ g_hash_table_remove (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));
+ }
+
+ g_ptr_array_unref (queued);
+
+ EGG_COUNTER_SUB (queued, count);
+ }
+}
+
+static void
+egg_task_cache_populate (EggTaskCache *self,
+ gconstpointer key,
+ gpointer value)
+{
+ CacheItem *item;
+
+ g_assert (EGG_IS_TASK_CACHE (self));
+
+ item = cache_item_new (self, key, value);
+
+ if (g_hash_table_contains (self->cache, key))
+ egg_task_cache_evict (self, key);
+ g_hash_table_insert (self->cache, item->key, item);
+ egg_heap_insert_val (self->evict_heap, item);
+
+ EGG_COUNTER_INC (cached);
+
+ if (self->evict_source != NULL)
+ evict_source_rearm (self->evict_source);
+}
+
+static void
+egg_task_cache_propagate_pointer (EggTaskCache *self,
+ gconstpointer key,
+ gpointer value)
+{
+ GPtrArray *queued = NULL;
+
+ g_assert (EGG_IS_TASK_CACHE (self));
+
+ if ((queued = g_hash_table_lookup (self->queued, key)))
+ {
+ gint64 count = queued->len;
+ gsize i;
+
+ g_ptr_array_ref (queued);
+ g_hash_table_remove (self->queued, key);
+
+ for (i = 0; i < queued->len; i++)
+ {
+ GTask *task;
+
+ task = g_ptr_array_index (queued, i);
+ g_task_return_pointer (task,
+ self->value_copy_func (value),
+ self->value_destroy_func);
+ }
+
+ g_ptr_array_unref (queued);
+
+ EGG_COUNTER_SUB (queued, count);
+ }
+}
+
+static gboolean
+egg_task_cache_cancel_in_idle (gpointer user_data)
+{
+ EggTaskCache *self;
+ CancelledData *data;
+ GCancellable *cancellable;
+ GPtrArray *queued;
+ GTask *task = user_data;
+ gboolean cancelled = FALSE;
+
+ g_assert (G_IS_TASK (task));
+
+ self = g_task_get_source_object (task);
+ cancellable = g_task_get_cancellable (task);
+ data = g_task_get_task_data (task);
+
+ g_assert (EGG_IS_TASK_CACHE (self));
+ g_assert (G_IS_CANCELLABLE (cancellable));
+ g_assert (data != NULL);
+ g_assert (data->self == self);
+ g_assert (data->cancellable == cancellable);
+
+ if ((queued = g_hash_table_lookup (self->queued, data->key)))
+ {
+ for (guint i = 0; i < queued->len; i++)
+ {
+ GCancellable *queued_cancellable;
+ GTask *queued_task;
+
+ queued_task = g_ptr_array_index (queued, i);
+ queued_cancellable = g_task_get_cancellable (queued_task);
+
+ if (queued_task == task && queued_cancellable == cancellable)
+ {
+ cancelled = g_task_return_error_if_cancelled (task);
+ g_ptr_array_remove_index_fast (queued, i);
+
+ EGG_COUNTER_DEC (queued);
+ break;
+ }
+ }
+
+ if (queued->len == 0)
+ {
+ GTask *fetch_task;
+
+ if ((fetch_task = g_hash_table_lookup (self->in_flight, data->key)))
+ {
+ GCancellable *fetch_cancellable;
+
+ fetch_cancellable = g_task_get_cancellable (fetch_task);
+ g_cancellable_cancel (fetch_cancellable);
+ }
+ }
+ }
+
+ g_return_val_if_fail (cancelled, G_SOURCE_REMOVE);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+egg_task_cache_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ EggTaskCache *self;
+ CancelledData *data;
+ GMainContext *context;
+ g_autoptr(GSource) source = NULL;
+ GTask *task = user_data;
+
+ g_assert (G_IS_CANCELLABLE (cancellable));
+ g_assert (G_IS_TASK (task));
+
+ self = g_task_get_source_object (task);
+ data = g_task_get_task_data (task);
+
+ g_assert (EGG_IS_TASK_CACHE (self));
+ g_assert (data != NULL);
+ g_assert (data->self == self);
+ g_assert (data->cancellable == cancellable);
+
+ source = g_idle_source_new ();
+ g_source_set_callback (source, egg_task_cache_cancel_in_idle, g_object_ref (task), g_object_unref);
+ g_source_set_name (source, "[egg] egg_task_cache_cancel_in_idle");
+
+ context = g_main_context_get_thread_default ();
+ g_source_attach (source, context);
+}
+
+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));
+
+ g_hash_table_remove (self->in_flight, key);
+
+ ret = g_task_propagate_pointer (task, &error);
+
+ if (ret != NULL)
+ {
+ egg_task_cache_populate (self, key, ret);
+ egg_task_cache_propagate_pointer (self, key, ret);
+ self->value_destroy_func (ret);
+ }
+ else
+ {
+ egg_task_cache_propagate_error (self, key, error);
+ g_clear_error (&error);
+ }
+
+ self->key_destroy_func (key);
+ g_object_unref (task);
+
+ EGG_COUNTER_DEC (in_flight);
+}
+
+void
+egg_task_cache_get_async (EggTaskCache *self,
+ gconstpointer key,
+ gboolean force_update,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) fetch_task = NULL;
+ g_autoptr(GTask) task = NULL;
+ CancelledData *data;
+ GPtrArray *queued;
+ gpointer ret;
+ gulong cancelled_id = 0;
+
+ g_return_if_fail (EGG_IS_TASK_CACHE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_return_on_cancel (task, FALSE);
+
+ /*
+ * If we have the answer, return it now.
+ */
+ if (!force_update && (ret = egg_task_cache_peek (self, key)))
+ {
+ g_task_return_pointer (task,
+ self->value_copy_func (ret),
+ self->value_destroy_func);
+ return;
+ }
+
+ EGG_COUNTER_INC (misses);
+
+ /*
+ * 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,
+ self->key_copy_func ((gpointer)key),
+ queued);
+ }
+
+ g_ptr_array_add (queued, g_object_ref (task));
+ EGG_COUNTER_INC (queued);
+
+ /*
+ * The in_flight hashtable will have a bit set if we have queued
+ * an operation for this key.
+ */
+ if (!g_hash_table_contains (self->in_flight, key))
+ {
+ g_autoptr(GCancellable) fetch_cancellable = NULL;
+
+ fetch_cancellable = g_cancellable_new ();
+ fetch_task = g_task_new (self,
+ fetch_cancellable,
+ egg_task_cache_fetch_cb,
+ self->key_copy_func ((gpointer)key));
+ g_hash_table_insert (self->in_flight,
+ self->key_copy_func ((gpointer)key),
+ g_object_ref (fetch_task));
+ }
+
+ if (cancellable != NULL)
+ {
+ cancelled_id = g_cancellable_connect (cancellable,
+ G_CALLBACK (egg_task_cache_cancelled_cb),
+ task,
+ NULL);
+ }
+
+ data = cancelled_data_new (self, cancellable, key, cancelled_id);
+ g_task_set_task_data (task, data, cancelled_data_free);
+
+ if (fetch_task != NULL)
+ {
+ self->populate_callback (self,
+ key,
+ g_object_ref (fetch_task),
+ self->populate_callback_data);
+
+ EGG_COUNTER_INC (in_flight);
+ }
+}
+
+/**
+ * egg_task_cache_get_finish:
+ *
+ * Finish a call to egg_task_cache_get_async().
+ *
+ * Returns: (transfer full): The result from the cache.
+ */
+gpointer
+egg_task_cache_get_finish (EggTaskCache *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GTask *task = (GTask *)result;
+
+ 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 gboolean
+egg_task_cache_do_eviction (gpointer user_data)
+{
+ EggTaskCache *self = user_data;
+ gint64 now = g_get_monotonic_time ();
+
+ while (self->evict_heap->len > 0)
+ {
+ CacheItem *item;
+
+ item = egg_heap_peek (self->evict_heap, gpointer);
+
+ if (item->evict_at <= now)
+ {
+ egg_heap_extract (self->evict_heap, NULL);
+ egg_task_cache_evict_full (self, item->key, FALSE);
+ continue;
+ }
+
+ break;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+egg_task_cache_install_evict_source (EggTaskCache *self)
+{
+ GMainContext *main_context;
+ EvictSource *evict_source;
+ GSource *source;
+
+ main_context = g_main_context_get_thread_default ();
+
+ source = g_source_new (&evict_source_funcs, sizeof (EvictSource));
+ g_source_set_callback (source, egg_task_cache_do_eviction, self, NULL);
+ g_source_set_name (source, "EggTaskCache Eviction");
+ g_source_set_priority (source, G_PRIORITY_LOW);
+ g_source_set_ready_time (source, -1);
+
+ evict_source = (EvictSource *)source;
+ evict_source->heap = egg_heap_ref (self->evict_heap);
+
+ self->evict_source = source;
+ self->evict_source_id = g_source_attach (source, main_context);
+}
+
+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_equal_func == NULL) ||
+ (self->key_hash_func == NULL) ||
+ (self->value_copy_func == NULL) ||
+ (self->value_destroy_func == NULL) ||
+ (self->populate_callback == NULL))
+ {
+ g_error ("EggTaskCache was configured improperly.");
+ return;
+ }
+
+ if (self->key_copy_func == NULL)
+ self->key_copy_func = egg_task_cache_dummy_copy_func;
+
+ if (self->key_destroy_func == NULL)
+ self->key_destroy_func = egg_task_cache_dummy_destroy_func;
+
+ /*
+ * This is where the cached result objects live.
+ */
+ self->cache = g_hash_table_new_full (self->key_hash_func,
+ self->key_equal_func,
+ NULL,
+ 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,
+ g_object_unref);
+
+ /*
+ * 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)
+ egg_task_cache_install_evict_source (self);
+}
+
+static void
+count_queued_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ GPtrArray *ar = value;
+ gint64 *count = user_data;
+
+ (*count) += ar->len;
+}
+
+static void
+egg_task_cache_dispose (GObject *object)
+{
+ EggTaskCache *self = (EggTaskCache *)object;
+
+ if (self->evict_source_id != 0)
+ {
+ g_source_remove (self->evict_source_id);
+ self->evict_source_id = 0;
+ self->evict_source = NULL;
+ }
+
+ g_clear_pointer (&self->evict_heap, egg_heap_unref);
+
+ if (self->cache != NULL)
+ {
+ gint64 count;
+
+ count = g_hash_table_size (self->cache);
+ g_clear_pointer (&self->cache, g_hash_table_unref);
+
+ g_debug ("Evicted cache of %"G_GINT64_FORMAT" items from %s",
+ count, self->name ?: "unnamed cache");
+
+ EGG_COUNTER_SUB (cached, count);
+ }
+
+ if (self->queued != NULL)
+ {
+ 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)
+ {
+ gint64 count;
+
+ count = g_hash_table_size (self->in_flight);
+ g_clear_pointer (&self->in_flight, (GDestroyNotify)g_hash_table_unref);
+
+ EGG_COUNTER_SUB (in_flight, count);
+ }
+
+ if (self->populate_callback_data)
+ {
+ if (self->populate_callback_data_destroy)
+ self->populate_callback_data_destroy (self->populate_callback_data);
+ }
+
+ G_OBJECT_CLASS (egg_task_cache_parent_class)->dispose (object);
+}
+
+static void
+egg_task_cache_finalize (GObject *object)
+{
+ EggTaskCache *self = (EggTaskCache *)object;
+
+ g_clear_pointer (&self->name, g_free);
+
+ G_OBJECT_CLASS (egg_task_cache_parent_class)->finalize (object);
+
+ EGG_COUNTER_DEC (instances);
+}
+
+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_POPULATE_CALLBACK_DATA:
+ self->populate_callback_data = g_value_get_pointer (value);
+ break;
+
+ case PROP_POPULATE_CALLBACK_DATA_DESTROY:
+ self->populate_callback_data_destroy = g_value_get_pointer (value);
+ break;
+
+ case PROP_TIME_TO_LIVE:
+ self->time_to_live_usec = (g_value_get_int64 (value) * 1000L);
+ break;
+
+ case PROP_VALUE_COPY_FUNC:
+ self->value_copy_func = g_value_get_pointer (value);
+ break;
+
+ case PROP_VALUE_DESTROY_FUNC:
+ self->value_destroy_func = g_value_get_pointer (value);
+ 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;
+
+ properties [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));
+
+ properties [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));
+
+ properties [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));
+
+ properties [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));
+
+ properties [PROP_POPULATE_CALLBACK] =
+ g_param_spec_pointer ("populate-callback",
+ "Populate Callback",
+ "Populate Callback",
+ (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_POPULATE_CALLBACK_DATA] =
+ g_param_spec_pointer ("populate-callback-data",
+ "Populate Callback Data",
+ "Populate Callback Data",
+ (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_POPULATE_CALLBACK_DATA_DESTROY] =
+ g_param_spec_pointer ("populate-callback-data-destroy",
+ "Populate Callback Data Destroy",
+ "Populate Callback Data Destroy",
+ (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.
+ */
+ properties [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));
+
+ properties [PROP_VALUE_COPY_FUNC] =
+ g_param_spec_pointer ("value-copy-func",
+ "Value Copy Func",
+ "Value Copy Func",
+ (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_VALUE_DESTROY_FUNC] =
+ g_param_spec_pointer ("value-destroy-func",
+ "Value Destroy Func",
+ "Value Destroy Func",
+ (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+void
+egg_task_cache_init (EggTaskCache *self)
+{
+ EGG_COUNTER_INC (instances);
+
+ self->evict_heap = egg_heap_new (sizeof (gpointer),
+ cache_item_compare_evict_at);
+}
+
+/**
+ * egg_task_cache_new: (skip)
+ */
+EggTaskCache *
+egg_task_cache_new (GHashFunc key_hash_func,
+ GEqualFunc key_equal_func,
+ GBoxedCopyFunc key_copy_func,
+ GBoxedFreeFunc key_destroy_func,
+ GBoxedCopyFunc value_copy_func,
+ GBoxedFreeFunc value_destroy_func,
+ gint64 time_to_live,
+ EggTaskCacheCallback populate_callback,
+ gpointer populate_callback_data,
+ GDestroyNotify populate_callback_data_destroy)
+{
+ g_return_val_if_fail (key_hash_func, NULL);
+ g_return_val_if_fail (key_equal_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,
+ "populate-callback-data", populate_callback_data,
+ "populate-callback-data-destroy", populate_callback_data_destroy,
+ "time-to-live", time_to_live,
+ "value-copy-func", value_copy_func,
+ "value-destroy-func", value_destroy_func,
+ NULL);
+}
+
+/**
+ * egg_task_cache_get_values: (skip)
+ *
+ * Gets all the values in the cache.
+ *
+ * The caller owns the resulting GPtrArray, which itself owns a reference to the children.
+ *
+ * Returns: (transfer container): The values.
+ */
+GPtrArray *
+egg_task_cache_get_values (EggTaskCache *self)
+{
+ GPtrArray *ar;
+ GHashTableIter iter;
+ gpointer value;
+
+ g_return_val_if_fail (EGG_IS_TASK_CACHE (self), NULL);
+
+ ar = g_ptr_array_new_with_free_func (self->value_destroy_func);
+
+ g_hash_table_iter_init (&iter, self->cache);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value))
+ {
+ CacheItem *item = value;
+
+ g_ptr_array_add (ar, self->value_copy_func (item->value));
+ }
+
+ return ar;
+}
+
+void
+egg_task_cache_set_name (EggTaskCache *self,
+ const gchar *name)
+{
+ g_return_if_fail (EGG_IS_TASK_CACHE (self));
+
+ g_free (self->name);
+ self->name = g_strdup (name);
+
+ if (name && self->evict_source)
+ {
+ g_autofree gchar *full_name = NULL;
+
+ full_name = g_strdup_printf ("[egg_task_cache] %s", name);
+ g_source_set_name (self->evict_source, full_name);
+ }
+}
diff --git a/src/egg-task-cache.h b/src/egg-task-cache.h
new file mode 100644
index 0000000..1935ed4
--- /dev/null
+++ b/src/egg-task-cache.h
@@ -0,0 +1,82 @@
+/* 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)
+
+/**
+ * EggTaskCacheCallback:
+ * @self: An #EggTaskCache.
+ * @key: the key to fetch
+ * @task: the task to be completed
+ * @user_data: user_data registered at initialization.
+ *
+ * #EggTaskCacheCallback is the prototype for a function to be executed to
+ * populate 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,
+ gpointer user_data);
+
+EggTaskCache *egg_task_cache_new (GHashFunc key_hash_func,
+ GEqualFunc key_equal_func,
+ GBoxedCopyFunc key_copy_func,
+ GBoxedFreeFunc key_destroy_func,
+ GBoxedCopyFunc value_copy_func,
+ GBoxedFreeFunc value_free_func,
+ gint64 time_to_live_msec,
+ EggTaskCacheCallback populate_callback,
+ gpointer populate_callback_data,
+ GDestroyNotify populate_callback_data_destroy);
+void egg_task_cache_set_name (EggTaskCache *self,
+ const gchar *name);
+void egg_task_cache_get_async (EggTaskCache *self,
+ gconstpointer key,
+ gboolean force_update,
+ 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);
+void egg_task_cache_evict_all (EggTaskCache *self);
+gpointer egg_task_cache_peek (EggTaskCache *self,
+ gconstpointer key);
+GPtrArray *egg_task_cache_get_values (EggTaskCache *self);
+
+G_END_DECLS
+
+#endif /* EGG_TASK_CACHE_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]