1 /*  $Id: cache.c 8709 2009-11-06 21:47:11Z iulius $
2 **
3 **  Message-ID to storage token cache.
4 **
5 **  Written by Alex Kiernan (alex.kiernan@thus.net).
6 **
7 **  Implementation of a message-ID to storage token cache which can be
8 **  built during (X)OVER/(X)HDR/XPAT/NEWNEWS.  If we hit in the cache when
9 **  retrieving articles, the (relatively) expensive cost of a trip
10 **  through the history database is saved.
11 */
12 
13 #include "config.h"
14 #include "clibrary.h"
15 
16 #include "inn/innconf.h"
17 #include "inn/tst.h"
18 #include "inn/list.h"
19 #include "inn/libinn.h"
20 #include "inn/storage.h"
21 
22 #include "cache.h"
23 
24 /*
25 **  Pointer to the message-ID to storage token ternary search tree.
26 */
27 static struct tst *msgidcache;
28 
29 /*
30 **  Count of message-IDs in the cache so that someone doing GROUP,
31 **  (X)OVER, GROUP, (X)OVER, etc. for example doesn't blow up with
32 **  out of memory.
33 */
34 static unsigned long msgcachecount;
35 
36 struct cache_entry {
37     struct node node;
38     HASH hash;
39     TOKEN token;
40 };
41 
42 static struct list unused, used;
43 
44 /*
45 **  Add a translation from HASH, h, to TOKEN, t, to the message-ID
46 **  cache.
47 */
48 void
cache_add(const HASH h,const TOKEN t)49 cache_add(const HASH h, const TOKEN t)
50 {
51     if (innconf->msgidcachesize != 0) {
52 	struct cache_entry *entry, *old;
53 	const unsigned char *p;
54         void *exist;
55 
56 	if (!msgidcache) {
57 	    msgidcache = tst_init((innconf->msgidcachesize + 9) / 10);
58 	    list_new(&unused);
59 	    list_new(&used);
60 	}
61 
62 	entry = xmalloc(sizeof *entry);
63 	entry->hash = h;
64 	entry->token = t;
65 	p = (unsigned char *) HashToText(h);
66 	if (tst_insert(msgidcache, p, entry, 0, &exist) == TST_DUPLICATE_KEY) {
67 	    free(entry);
68             old = exist;
69 	    list_remove(&old->node);
70 	    list_addtail(&unused, &old->node);
71 	} else {
72 	    list_addtail(&unused, &entry->node);
73 	    ++msgcachecount;
74 	}
75 	if (msgcachecount >= innconf->msgidcachesize) {
76 	    /* Need to throw away a node. */
77 	    entry = (struct cache_entry *)list_remhead(&used);
78 	    if (entry == NULL)
79 		entry = (struct cache_entry *)list_remhead(&unused);
80 	    if (entry != NULL) {
81 		tst_delete(msgidcache,
82 			   (unsigned char *) HashToText(entry->hash));
83 		free(entry);
84 	    }
85 	}
86     }
87 }
88 
89 
90 /*
91 **  Lookup (and remove if found) a message-ID to TOKEN mapping.  If this
92 **  is a final lookup (ARTICLE, BODY, (X)HDR, XPAT), we remove it if we
93 **  find it since this matches the observed behaviour of most clients, but
94 **  cache it just in case we can reuse it if they issue multiple
95 **  commands against the same message-ID (e.g. HEAD, STAT).
96 */
97 TOKEN
cache_get(const HASH h,bool final)98 cache_get(const HASH h, bool final)
99 {
100     static HASH last_hash;
101     static TOKEN last_token;
102     static const TOKEN empty_token = { TOKEN_EMPTY, 0, "" };
103 
104     if (HashCompare(&h, &last_hash) == 0 && !HashEmpty(last_hash))
105 	return last_token;
106 
107     if (msgidcache) {
108 	struct cache_entry *entry;
109 
110 	entry = tst_search(msgidcache, (unsigned char *) HashToText(h));
111 	if (entry != NULL) {
112 	    list_remove(&entry->node);
113 	    if (!final)
114 		list_addtail(&unused, &entry->node);
115 	    else
116 		list_addtail(&used, &entry->node);
117 	    last_hash = entry->hash;
118 	    last_token = entry->token;
119 	    return last_token;
120 	}
121     }
122     return empty_token;
123 }
124