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