From 18b15bfc5ae50a909621c7ea3e00e6f8ff7b4808 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Wed, 14 Jan 2004 05:04:04 +0000 Subject: A time-based thread-safe in-memory cache thing. Called em_cache 'cause 2004-01-13 Not Zed * e-msgport.c (em_cache*): A time-based thread-safe in-memory cache thing. Called em_cache 'cause there's an e_cache in gal. svn path=/trunk/; revision=24213 --- e-util/ChangeLog | 5 ++ e-util/e-msgport.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++ e-util/e-msgport.h | 23 ++++++ 3 files changed, 237 insertions(+) diff --git a/e-util/ChangeLog b/e-util/ChangeLog index e4a25d11f5..0ed9ae50a8 100644 --- a/e-util/ChangeLog +++ b/e-util/ChangeLog @@ -1,3 +1,8 @@ +2004-01-13 Not Zed + + * e-msgport.c (em_cache*): A time-based thread-safe in-memory + cache thing. Called em_cache 'cause there's an e_cache in gal. + 2004-01-05 Not Zed * e-memory.c (e_mempool_destroy): Fix from Zan Lynx diff --git a/e-util/e-msgport.c b/e-util/e-msgport.c index 36ea6cfe8d..f360a3f157 100644 --- a/e-util/e-msgport.c +++ b/e-util/e-msgport.c @@ -43,6 +43,7 @@ #define m(x) /* msgport debug */ #define t(x) /* thread debug */ +#define c(x) /* cache debug */ void e_dlist_init(EDList *v) { @@ -125,6 +126,214 @@ int e_dlist_length(EDList *l) return count; } +struct _EMCache { + GMutex *lock; + GHashTable *key_table; + EDList lru_list; + size_t node_size; + int node_count; + time_t timeout; + GFreeFunc node_free; +}; + +/** + * em_cache_new: + * @timeout: + * @nodesize: + * @nodefree: + * + * Setup a new timeout cache. @nodesize is the size of nodes in the + * cache, and @nodefree will be called to free YOUR content. + * + * Return value: + **/ +EMCache * +em_cache_new(time_t timeout, size_t nodesize, GFreeFunc nodefree) +{ + struct _EMCache *emc; + + emc = g_malloc0(sizeof(*emc)); + emc->node_size = nodesize; + emc->key_table = g_hash_table_new(g_str_hash, g_str_equal); + emc->node_free = nodefree; + e_dlist_init(&emc->lru_list); + emc->lock = g_mutex_new(); + emc->timeout = timeout; + + return emc; +} + +/** + * em_cache_destroy: + * @emc: + * + * destroy the cache, duh. + **/ +void +em_cache_destroy(EMCache *emc) +{ + em_cache_clear(emc); + g_mutex_free(emc->lock); + g_free(emc); +} + +/** + * em_cache_lookup: + * @emc: + * @key: + * + * Lookup a cache node. once you're finished with it, you need to + * unref it. + * + * Return value: + **/ +EMCacheNode * +em_cache_lookup(EMCache *emc, const char *key) +{ + EMCacheNode *n; + + g_mutex_lock(emc->lock); + n = g_hash_table_lookup(emc->key_table, key); + if (n) { + e_dlist_remove((EDListNode *)n); + e_dlist_addhead(&emc->lru_list, (EDListNode *)n); + n->stamp = time(0); + n->ref_count++; + } + g_mutex_unlock(emc->lock); + + c(printf("looking up '%s' %s\n", key, n?"found":"not found")); + + return n; +} + +/** + * em_cache_node_new: + * @emc: + * @key: + * + * Create a new key'd cache node. The node will not be added to the + * cache until you insert it. + * + * Return value: + **/ +EMCacheNode * +em_cache_node_new(EMCache *emc, const char *key) +{ + EMCacheNode *n; + + /* this could use memchunks, but its probably overkill */ + n = g_malloc0(emc->node_size); + n->key = g_strdup(key); + + return n; +} + +/** + * em_cache_node_unref: + * @emc: + * @n: + * + * unref a cache node, you can only unref nodes which have been looked + * up. + **/ +void +em_cache_node_unref(EMCache *emc, EMCacheNode *n) +{ + g_mutex_lock(emc->lock); + g_assert(n->ref_count > 0); + n->ref_count--; + g_mutex_unlock(emc->lock); +} + +/** + * em_cache_add: + * @emc: + * @n: + * + * Add a cache node to the cache, once added the memory is owned by + * the cache. If there are conflicts and the old node is still in + * use, then the new node is not added, otherwise it is added and any + * nodes older than the expire time are flushed. + **/ +void +em_cache_add(EMCache *emc, EMCacheNode *n) +{ + EMCacheNode *old, *prev; + EDList old_nodes; + + e_dlist_init(&old_nodes); + + g_mutex_lock(emc->lock); + old = g_hash_table_lookup(emc->key_table, n->key); + if (old != NULL) { + if (old->ref_count == 0) { + g_hash_table_remove(emc->key_table, old->key); + e_dlist_remove((EDListNode *)old); + e_dlist_addtail(&old_nodes, (EDListNode *)old); + goto insert; + } else { + e_dlist_addtail(&old_nodes, (EDListNode *)n); + } + } else { + time_t now; + insert: + now = time(0); + g_hash_table_insert(emc->key_table, n->key, n); + e_dlist_addhead(&emc->lru_list, (EDListNode *)n); + n->stamp = now; + emc->node_count++; + + c(printf("inserting node %s\n", n->key)); + + old = (EMCacheNode *)emc->lru_list.tailpred; + prev = old->prev; + while (prev && old->stamp < now - emc->timeout) { + if (old->ref_count == 0) { + c(printf("expiring node %s\n", old->key)); + g_hash_table_remove(emc->key_table, old->key); + e_dlist_remove((EDListNode *)old); + e_dlist_addtail(&old_nodes, (EDListNode *)old); + } + old = prev; + prev = prev->prev; + } + } + + g_mutex_unlock(emc->lock); + + while ((old = (EMCacheNode *)e_dlist_remhead(&old_nodes))) { + emc->node_free(old); + g_free(old->key); + g_free(old); + } +} + +/** + * em_cache_clear: + * @emc: + * + * clear the cache. just for api completeness. + **/ +void +em_cache_clear(EMCache *emc) +{ + EMCacheNode *node; + EDList old_nodes; + + e_dlist_init(&old_nodes); + g_mutex_lock(emc->lock); + while ((node = (EMCacheNode *)e_dlist_remhead(&emc->lru_list))) + e_dlist_addtail(&old_nodes, (EDListNode *)node); + g_mutex_unlock(emc->lock); + + while ((node = (EMCacheNode *)e_dlist_remhead(&old_nodes))) { + emc->node_free(node); + g_free(node->key); + g_free(node); + } +} + struct _EMsgPort { EDList queue; int condwait; /* how many waiting in condwait */ diff --git a/e-util/e-msgport.h b/e-util/e-msgport.h index 8d4e0c20f7..6347564c0f 100644 --- a/e-util/e-msgport.h +++ b/e-util/e-msgport.h @@ -2,6 +2,9 @@ #ifndef _E_MSGPORT_H #define _E_MSGPORT_H +#include +#include + /* double-linked list yeah another one, deal */ typedef struct _EDListNode { struct _EDListNode *next; @@ -25,6 +28,26 @@ EDListNode *e_dlist_remtail(EDList *l); int e_dlist_empty(EDList *l); int e_dlist_length(EDList *l); +/* a time-based cache */ +typedef struct _EMCache EMCache; +typedef struct _EMCacheNode EMCacheNode; + +/* subclass this for your data nodes, EMCache is opaque */ +struct _EMCacheNode { + struct _EMCacheNode *next, *prev; + char *key; + int ref_count; + time_t stamp; +}; + +EMCache *em_cache_new(time_t timeout, size_t nodesize, GFreeFunc nodefree); +void em_cache_destroy(EMCache *emc); +EMCacheNode *em_cache_lookup(EMCache *emc, const char *key); +EMCacheNode *em_cache_node_new(EMCache *emc, const char *key); +void em_cache_node_unref(EMCache *emc, EMCacheNode *n); +void em_cache_add(EMCache *emc, EMCacheNode *n); +void em_cache_clear(EMCache *emc); + /* message ports - a simple inter-thread 'ipc' primitive */ /* opaque handle */ typedef struct _EMsgPort EMsgPort; -- cgit v1.2.3