11 /* CTABLE *ctable_create(limit, create, delete, context)
13 /* void *(*create)(const char *key, void *context);
14 /* void (*delete)(void *value, void *context);
17 /* const void *ctable_locate(cache, key)
21 /* void ctable_free(cache)
24 /* void ctable_walk(cache, action)
26 /* void (*action)(const char *key, const void *value);
28 /* This module maintains multiple caches. Cache items are purged
29 /* automatically when the number of items exceeds a configurable
30 /* limit. Caches never shrink. Each cache entry consists of a
31 /* string-valued lookup key and a generic data pointer value.
33 /* ctable_create() creates a cache with the specified size limit, and
34 /* returns a pointer to the result. The create and delete arguments
35 /* specify pointers to call-back functions that create a value, given
36 /* a key, and delete a given value, respectively. The context argument
37 /* is passed on to the call-back routines.
39 /* ctable_locate() looks up or generates the value that corresponds to
40 /* the specified key, and returns that value.
42 /* ctable_free() destroys the specified cache, including its contents.
44 /* ctable_walk() iterates over all elements in the cache, and invokes
45 /* the action function for each cache element with the corresponding
46 /* key and value as arguments. This function is useful mainly for
47 /* cache performance debugging.
49 /* Fatal errors: out of memory. Panic: interface violation.
53 /* The Secure Mailer license must be distributed with this software.
56 /* IBM T.J. Watson Research
58 /* Yorktown Heights, NY 10598, USA
67 /* Utility library. */
76 * Cache entries are kept in most-recently used order. We use a hash table
77 * to quickly locate cache entries.
79 #define CTABLE_ENTRY struct ctable_entry
82 RING ring
; /* MRU linkage */
83 const char *key
; /* lookup key */
84 void *value
; /* corresponding value */
87 #define RING_TO_CTABLE_ENTRY(ring_ptr) \
88 RING_TO_APPL(ring_ptr, CTABLE_ENTRY, ring)
89 #define RING_PTR_OF(x) (&((x)->ring))
92 HTABLE
*table
; /* table with key, ctable_entry pairs */
93 unsigned limit
; /* max nr of entries */
94 unsigned used
; /* current nr of entries */
95 CTABLE_CREATE_FN create
; /* constructor */
96 CTABLE_DELETE_FN
delete; /* destructor */
97 RING ring
; /* MRU linkage */
98 void *context
; /* application context */
101 #define CTABLE_MIN_SIZE 5
103 /* ctable_create - create empty cache */
105 CTABLE
*ctable_create(int limit
, CTABLE_CREATE_FN create
,
106 CTABLE_DELETE_FN
delete, void *context
)
108 CTABLE
*cache
= (CTABLE
*) mymalloc(sizeof(CTABLE
));
109 const char *myname
= "ctable_create";
112 msg_panic("%s: bad cache limit: %d", myname
, limit
);
114 cache
->table
= htable_create(limit
);
115 cache
->limit
= (limit
< CTABLE_MIN_SIZE
? CTABLE_MIN_SIZE
: limit
);
117 cache
->create
= create
;
118 cache
->delete = delete;
119 ring_init(RING_PTR_OF(cache
));
120 cache
->context
= context
;
124 /* ctable_locate - look up or create cache item */
126 const void *ctable_locate(CTABLE
*cache
, const char *key
)
128 const char *myname
= "ctable_locate";
132 * If the entry is not in the cache, make sure there is room for a new
133 * entry and install it at the front of the MRU chain. Otherwise, move
134 * the entry to the front of the MRU chain if it is not already there.
135 * All this means that the cache never shrinks.
137 if ((entry
= (CTABLE_ENTRY
*) htable_find(cache
->table
, key
)) == 0) {
138 if (cache
->used
>= cache
->limit
) {
139 entry
= RING_TO_CTABLE_ENTRY(ring_pred(RING_PTR_OF(cache
)));
141 msg_info("%s: purge entry key %s", myname
, entry
->key
);
142 ring_detach(RING_PTR_OF(entry
));
143 cache
->delete(entry
->value
, cache
->context
);
144 htable_delete(cache
->table
, entry
->key
, (void (*) (char *)) 0);
146 entry
= (CTABLE_ENTRY
*) mymalloc(sizeof(CTABLE_ENTRY
));
149 entry
->value
= cache
->create(key
, cache
->context
);
150 entry
->key
= htable_enter(cache
->table
, key
, (char *) entry
)->key
;
151 ring_append(RING_PTR_OF(cache
), RING_PTR_OF(entry
));
153 msg_info("%s: install entry key %s", myname
, entry
->key
);
154 } else if (entry
== RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache
)))) {
156 msg_info("%s: leave existing entry key %s", myname
, entry
->key
);
158 ring_detach(RING_PTR_OF(entry
));
159 ring_append(RING_PTR_OF(cache
), RING_PTR_OF(entry
));
161 msg_info("%s: move existing entry key %s", myname
, entry
->key
);
163 return (entry
->value
);
166 static CTABLE
*ctable_free_cache
;
168 /* ctable_free_callback - callback function */
170 static void ctable_free_callback(char *ptr
)
172 CTABLE_ENTRY
*entry
= (CTABLE_ENTRY
*) ptr
;
174 ctable_free_cache
->delete(entry
->value
, ctable_free_cache
->context
);
175 myfree((char *) entry
);
178 /* ctable_free - destroy cache and contents */
180 void ctable_free(CTABLE
*cache
)
182 CTABLE
*saved_cache
= ctable_free_cache
;
185 * XXX the hash table does not pass application context so we have to
186 * store it in a global variable.
188 ctable_free_cache
= cache
;
189 htable_free(cache
->table
, ctable_free_callback
);
190 myfree((char *) cache
);
191 ctable_free_cache
= saved_cache
;
194 /* ctable_walk - iterate over all cache entries */
196 void ctable_walk(CTABLE
*cache
, void (*action
) (const char *, const void *))
198 RING
*entry
= RING_PTR_OF(cache
);
200 /* Walking down the MRU chain is less work than using ht_walk(). */
202 while ((entry
= ring_succ(entry
)) != RING_PTR_OF(cache
))
203 action((RING_TO_CTABLE_ENTRY(entry
)->key
),
204 (RING_TO_CTABLE_ENTRY(entry
)->value
));
210 * Proof-of-concept test program. Read keys from stdin, ask for values not
216 #include <vstring_vstream.h>
217 #include <msg_vstream.h>
219 #define STR(x) vstring_str(x)
221 static void *ask(const char *key
, void *context
)
223 VSTRING
*data_buf
= (VSTRING
*) context
;
225 vstream_printf("ask: %s = ", key
);
226 vstream_fflush(VSTREAM_OUT
);
227 if (vstring_get_nonl(data_buf
, VSTREAM_IN
) == VSTREAM_EOF
)
228 vstream_longjmp(VSTREAM_IN
, 1);
230 vstream_printf("%s\n", STR(data_buf
));
231 vstream_fflush(VSTREAM_OUT
);
233 return (mystrdup(STR(data_buf
)));
236 static void drop(void *data
, void *unused_context
)
241 int main(int unused_argc
, char **argv
)
248 msg_vstream_init(argv
[0], VSTREAM_ERR
);
249 key_buf
= vstring_alloc(100);
250 data_buf
= vstring_alloc(100);
251 cache
= ctable_create(1, ask
, drop
, (void *) data_buf
);
253 vstream_control(VSTREAM_IN
, VSTREAM_CTL_EXCEPT
, VSTREAM_CTL_END
);
255 if (vstream_setjmp(VSTREAM_IN
) == 0) {
257 vstream_printf("key = ");
258 vstream_fflush(VSTREAM_OUT
);
259 if (vstring_get_nonl(key_buf
, VSTREAM_IN
) == VSTREAM_EOF
)
260 vstream_longjmp(VSTREAM_IN
, 1);
262 vstream_printf("%s\n", STR(key_buf
));
263 vstream_fflush(VSTREAM_OUT
);
265 value
= ctable_locate(cache
, STR(key_buf
));
266 vstream_printf("result: %s\n", value
);
270 vstring_free(key_buf
);
271 vstring_free(data_buf
);