/* dzl-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 "dzl-task-cache"
#include "config.h"
#include <glib/gi18n.h>
#include "cache/dzl-task-cache.h"
#include "util/dzl-heap.h"
typedef struct
{
DzlTaskCache *self;
gpointer key;
gpointer value;
gint64 evict_at;
} CacheItem;
typedef struct
{
DzlTaskCache *self;
GCancellable *cancellable;
gpointer key;
gulong cancelled_id;
} CancelledData;
typedef struct
{
GSource source;
DzlHeap *heap;
} EvictSource;
struct _DzlTaskCache
{
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;
DzlTaskCacheCallback populate_callback;
gpointer populate_callback_data;
GDestroyNotify populate_callback_data_destroy;
GHashTable *cache;
GHashTable *in_flight;
GHashTable *queued;
gchar *name;
DzlHeap *evict_heap;
GSource *evict_source;
guint evict_source_id;
gint64 time_to_live_usec;
};
G_DEFINE_TYPE (DzlTaskCache, dzl_task_cache, G_TYPE_OBJECT)
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 = dzl_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 = dzl_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, dzl_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 (DzlTaskCache *self,
gconstpointer key,
gconstpointer value)
{
CacheItem *ret;
g_assert (DZL_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 (DzlTaskCache *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
dzl_task_cache_dummy_copy_func (gpointer boxed)
{
return boxed;
}
static void
dzl_task_cache_dummy_destroy_func (gpointer boxed)
{
}
static gboolean
dzl_task_cache_evict_full (DzlTaskCache *self,
gconstpointer key,
gboolean check_heap)
{
CacheItem *item;
g_return_val_if_fail (DZL_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 == dzl_heap_index (self->evict_heap, gpointer, i))
{
dzl_heap_extract_index (self->evict_heap, i, NULL);
break;
}
}
}
g_hash_table_remove (self->cache, key);
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
dzl_task_cache_evict (DzlTaskCache *self,
gconstpointer key)
{
return dzl_task_cache_evict_full (self, key, TRUE);
}
void
dzl_task_cache_evict_all (DzlTaskCache *self)
{
g_return_if_fail (DZL_IS_TASK_CACHE (self));
while (self->evict_heap->len > 0)
{
CacheItem *item;
/* The cache item is owned by the hashtable, so safe to "leak" here */
dzl_heap_extract_index (self->evict_heap, self->evict_heap->len - 1, &item);
}
g_hash_table_remove_all (self->cache);
if (self->evict_source != NULL)
evict_source_rearm (self->evict_source);
}
/**
* dzl_task_cache_peek:
* @self: An #DzlTaskCache
* @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
dzl_task_cache_peek (DzlTaskCache *self,
gconstpointer key)
{
CacheItem *item;
g_return_val_if_fail (DZL_IS_TASK_CACHE (self), NULL);
if (NULL != (item = g_hash_table_lookup (self->cache, key)))
return item->value;
return NULL;
}
static void
dzl_task_cache_propagate_error (DzlTaskCache *self,
gconstpointer key,
const GError *error)
{
GPtrArray *queued;
g_assert (DZL_IS_TASK_CACHE (self));
g_assert (error != NULL);
if (NULL != (queued = g_hash_table_lookup (self->queued, key)))
{
/* we can't use steal because we want the key freed */
g_ptr_array_ref (queued);
g_hash_table_remove (self->queued, key);
for (guint 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);
}
}
static void
dzl_task_cache_populate (DzlTaskCache *self,
gconstpointer key,
gpointer value)
{
CacheItem *item;
g_assert (DZL_IS_TASK_CACHE (self));
item = cache_item_new (self, key, value);
if (g_hash_table_contains (self->cache, key))
dzl_task_cache_evict (self, key);
g_hash_table_insert (self->cache, item->key, item);
dzl_heap_insert_val (self->evict_heap, item);
if (self->evict_source != NULL)
evict_source_rearm (self->evict_source);
}
static void
dzl_task_cache_propagate_pointer (DzlTaskCache *self,
gconstpointer key,
gpointer value)
{
GPtrArray *queued = NULL;
g_assert (DZL_IS_TASK_CACHE (self));
if (NULL != (queued = g_hash_table_lookup (self->queued, key)))
{
g_ptr_array_ref (queued);
g_hash_table_remove (self->queued, key);
for (guint i = 0; i < queued->len; i++)
{
GTask *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);
}
}
static gboolean
dzl_task_cache_cancel_in_idle (gpointer user_data)
{
DzlTaskCache *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 (DZL_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);
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
dzl_task_cache_cancelled_cb (GCancellable *cancellable,
gpointer user_data)
{
DzlTaskCache *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 (DZL_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, dzl_task_cache_cancel_in_idle, g_object_ref (task), g_object_unref);
g_source_set_name (source, "[dzl] dzl_task_cache_cancel_in_idle");
context = g_main_context_get_thread_default ();
g_source_attach (source, context);
}
static void
dzl_task_cache_fetch_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
DzlTaskCache *self = (DzlTaskCache *)object;
GTask *task = (GTask *)result;
GError *error = NULL;
gpointer key = user_data;
gpointer ret;
g_assert (DZL_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)
{
dzl_task_cache_populate (self, key, ret);
dzl_task_cache_propagate_pointer (self, key, ret);
self->value_destroy_func (ret);
}
else
{
dzl_task_cache_propagate_error (self, key, error);
g_clear_error (&error);
}
self->key_destroy_func (key);
g_object_unref (task);
}
void
dzl_task_cache_get_async (DzlTaskCache *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 (DZL_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 = dzl_task_cache_peek (self, key)))
{
g_task_return_pointer (task,
self->value_copy_func (ret),
self->value_destroy_func);
return;
}
/*
* 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));
/*
* 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,
dzl_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 (dzl_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);
}
}
/**
* dzl_task_cache_get_finish:
*
* Finish a call to dzl_task_cache_get_async().
*
* Returns: (transfer full): The result from the cache.
*/
gpointer
dzl_task_cache_get_finish (DzlTaskCache *self,
GAsyncResult *result,
GError **error)
{
GTask *task = (GTask *)result;
g_return_val_if_fail (DZL_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
dzl_task_cache_do_eviction (gpointer user_data)
{
DzlTaskCache *self = user_data;
gint64 now = g_get_monotonic_time ();
while (self->evict_heap->len > 0)
{
CacheItem *item;
item = dzl_heap_peek (self->evict_heap, gpointer);
if (item->evict_at <= now)
{
dzl_heap_extract (self->evict_heap, NULL);
dzl_task_cache_evict_full (self, item->key, FALSE);
continue;
}
break;
}
return G_SOURCE_CONTINUE;
}
static void
dzl_task_cache_install_evict_source (DzlTaskCache *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, dzl_task_cache_do_eviction, self, NULL);
g_source_set_name (source, "DzlTaskCache Eviction");
g_source_set_priority (source, G_PRIORITY_LOW);
g_source_set_ready_time (source, -1);
evict_source = (EvictSource *)source;
evict_source->heap = dzl_heap_ref (self->evict_heap);
self->evict_source = source;
self->evict_source_id = g_source_attach (source, main_context);
}
static void
dzl_task_cache_constructed (GObject *object)
{
DzlTaskCache *self = (DzlTaskCache *)object;
G_OBJECT_CLASS (dzl_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 ("DzlTaskCache was configured improperly.");
return;
}
if (self->key_copy_func == NULL)
self->key_copy_func = dzl_task_cache_dummy_copy_func;
if (self->key_destroy_func == NULL)
self->key_destroy_func = dzl_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)
dzl_task_cache_install_evict_source (self);
}
static void
dzl_task_cache_dispose (GObject *object)
{
DzlTaskCache *self = (DzlTaskCache *)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, dzl_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");
}
if (self->queued != NULL)
g_clear_pointer (&self->queued, g_hash_table_unref);
if (self->in_flight != NULL)
g_clear_pointer (&self->in_flight, (GDestroyNotify)g_hash_table_unref);
if (self->populate_callback_data)
{
if (self->populate_callback_data_destroy)
self->populate_callback_data_destroy (self->populate_callback_data);
}
G_OBJECT_CLASS (dzl_task_cache_parent_class)->dispose (object);
}
static void
dzl_task_cache_finalize (GObject *object)
{
DzlTaskCache *self = (DzlTaskCache *)object;
g_clear_pointer (&self->name, g_free);
G_OBJECT_CLASS (dzl_task_cache_parent_class)->finalize (object);
}
static void
dzl_task_cache_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
DzlTaskCache *self = DZL_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
dzl_task_cache_class_init (DzlTaskCacheClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = dzl_task_cache_constructed;
object_class->dispose = dzl_task_cache_dispose;
object_class->finalize = dzl_task_cache_finalize;
object_class->set_property = dzl_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));
/**
* DzlTaskCache: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
dzl_task_cache_init (DzlTaskCache *self)
{
self->evict_heap = dzl_heap_new (sizeof (gpointer), cache_item_compare_evict_at);
}
/**
* dzl_task_cache_new: (skip)
*/
DzlTaskCache *
dzl_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,
DzlTaskCacheCallback 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 (DZL_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);
}
/**
* dzl_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 *
dzl_task_cache_get_values (DzlTaskCache *self)
{
GPtrArray *ar;
GHashTableIter iter;
gpointer value;
g_return_val_if_fail (DZL_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
dzl_task_cache_set_name (DzlTaskCache *self,
const gchar *name)
{
g_return_if_fail (DZL_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 ("[dzl_task_cache] %s", name);
g_source_set_name (self->evict_source, full_name);
}
}