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