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