/src/h2o/lib/common/cache.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <assert.h> |
23 | | #include <pthread.h> |
24 | | #include "khash.h" |
25 | | #include "h2o/cache.h" |
26 | | #include "h2o/linklist.h" |
27 | | #include "h2o/memory.h" |
28 | | #include "h2o/string_.h" |
29 | | |
30 | | static h2o_cache_hashcode_t get_keyhash(h2o_cache_ref_t *ref); |
31 | | static int is_equal(h2o_cache_ref_t *x, h2o_cache_ref_t *y); |
32 | | |
33 | | KHASH_INIT(cache, h2o_cache_ref_t *, char, 0, get_keyhash, is_equal) |
34 | | |
35 | | struct st_h2o_cache_t { |
36 | | int flags; |
37 | | khash_t(cache) * table; |
38 | | size_t size; |
39 | | size_t capacity; |
40 | | h2o_linklist_t lru; |
41 | | h2o_linklist_t age; |
42 | | uint64_t duration; |
43 | | void (*destroy_cb)(h2o_iovec_t value); |
44 | | pthread_mutex_t mutex; /* only used if (flags & H2O_CACHE_FLAG_MULTITHREADED) != 0 */ |
45 | | }; |
46 | | |
47 | | static h2o_cache_hashcode_t get_keyhash(h2o_cache_ref_t *ref) |
48 | 0 | { |
49 | 0 | return ref->keyhash; |
50 | 0 | } |
51 | | |
52 | | static int is_equal(h2o_cache_ref_t *x, h2o_cache_ref_t *y) |
53 | 0 | { |
54 | 0 | return x->key.len == y->key.len && memcmp(x->key.base, y->key.base, x->key.len) == 0; |
55 | 0 | } |
56 | | |
57 | | static void lock_cache(h2o_cache_t *cache) |
58 | 0 | { |
59 | 0 | if ((cache->flags & H2O_CACHE_FLAG_MULTITHREADED) != 0) |
60 | 0 | pthread_mutex_lock(&cache->mutex); |
61 | 0 | } |
62 | | |
63 | | static void unlock_cache(h2o_cache_t *cache) |
64 | 0 | { |
65 | 0 | if ((cache->flags & H2O_CACHE_FLAG_MULTITHREADED) != 0) |
66 | 0 | pthread_mutex_unlock(&cache->mutex); |
67 | 0 | } |
68 | | |
69 | | static void erase_ref(h2o_cache_t *cache, khiter_t iter, int reuse) |
70 | 0 | { |
71 | 0 | h2o_cache_ref_t *ref = kh_key(cache->table, iter); |
72 | |
|
73 | 0 | if (!reuse) |
74 | 0 | kh_del(cache, cache->table, iter); |
75 | 0 | h2o_linklist_unlink(&ref->_lru_link); |
76 | 0 | h2o_linklist_unlink(&ref->_age_link); |
77 | 0 | cache->size -= ref->value.len; |
78 | |
|
79 | 0 | h2o_cache_release(cache, ref); |
80 | 0 | } |
81 | | |
82 | | static int64_t get_timeleft(h2o_cache_t *cache, h2o_cache_ref_t *ref, uint64_t now) |
83 | 0 | { |
84 | 0 | return (int64_t)(ref->at + cache->duration) - now; |
85 | 0 | } |
86 | | |
87 | | static void purge(h2o_cache_t *cache, uint64_t now) |
88 | 0 | { |
89 | | /* by cache size */ |
90 | 0 | while (cache->capacity < cache->size) { |
91 | 0 | h2o_cache_ref_t *last; |
92 | 0 | assert(!h2o_linklist_is_empty(&cache->lru)); |
93 | 0 | last = H2O_STRUCT_FROM_MEMBER(h2o_cache_ref_t, _lru_link, cache->lru.next); |
94 | 0 | erase_ref(cache, kh_get(cache, cache->table, last), 0); |
95 | 0 | } |
96 | | /* by TTL */ |
97 | 0 | while (!h2o_linklist_is_empty(&cache->age)) { |
98 | 0 | h2o_cache_ref_t *oldest = H2O_STRUCT_FROM_MEMBER(h2o_cache_ref_t, _age_link, cache->age.next); |
99 | 0 | if (get_timeleft(cache, oldest, now) >= 0) |
100 | 0 | break; |
101 | 0 | erase_ref(cache, kh_get(cache, cache->table, oldest), 0); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | h2o_cache_hashcode_t h2o_cache_calchash(const char *s, size_t l) |
106 | 0 | { |
107 | 0 | h2o_cache_hashcode_t h = 0; |
108 | 0 | for (; l != 0; --l) |
109 | 0 | h = (h << 5) - h + ((unsigned char *)s)[l - 1]; |
110 | 0 | return h; |
111 | 0 | } |
112 | | |
113 | | h2o_cache_t *h2o_cache_create(int flags, size_t capacity, uint64_t duration, void (*destroy_cb)(h2o_iovec_t value)) |
114 | 0 | { |
115 | 0 | h2o_cache_t *cache = h2o_mem_alloc(sizeof(*cache)); |
116 | |
|
117 | 0 | cache->flags = flags; |
118 | 0 | cache->table = kh_init(cache); |
119 | 0 | cache->size = 0; |
120 | 0 | cache->capacity = capacity; |
121 | 0 | h2o_linklist_init_anchor(&cache->lru); |
122 | 0 | h2o_linklist_init_anchor(&cache->age); |
123 | 0 | cache->duration = duration; |
124 | 0 | cache->destroy_cb = destroy_cb; |
125 | 0 | if ((cache->flags & H2O_CACHE_FLAG_MULTITHREADED) != 0) |
126 | 0 | pthread_mutex_init(&cache->mutex, NULL); |
127 | |
|
128 | 0 | return cache; |
129 | 0 | } |
130 | | |
131 | | void h2o_cache_destroy(h2o_cache_t *cache) |
132 | 0 | { |
133 | 0 | h2o_cache_clear(cache); |
134 | 0 | kh_destroy(cache, cache->table); |
135 | 0 | if ((cache->flags & H2O_CACHE_FLAG_MULTITHREADED) != 0) |
136 | 0 | pthread_mutex_destroy(&cache->mutex); |
137 | 0 | free(cache); |
138 | 0 | } |
139 | | |
140 | | void h2o_cache_clear(h2o_cache_t *cache) |
141 | 0 | { |
142 | 0 | lock_cache(cache); |
143 | |
|
144 | 0 | while (!h2o_linklist_is_empty(&cache->lru)) { |
145 | 0 | h2o_cache_ref_t *ref = H2O_STRUCT_FROM_MEMBER(h2o_cache_ref_t, _lru_link, cache->lru.next); |
146 | 0 | erase_ref(cache, kh_get(cache, cache->table, ref), 0); |
147 | 0 | } |
148 | 0 | assert(h2o_linklist_is_linked(&cache->age)); |
149 | 0 | assert(kh_size(cache->table) == 0); |
150 | 0 | assert(cache->size == 0); |
151 | | |
152 | 0 | unlock_cache(cache); |
153 | 0 | } |
154 | | |
155 | | h2o_cache_ref_t *h2o_cache_fetch(h2o_cache_t *cache, uint64_t now, h2o_iovec_t key, h2o_cache_hashcode_t keyhash) |
156 | 0 | { |
157 | 0 | h2o_cache_ref_t search_key, *ref; |
158 | 0 | khiter_t iter; |
159 | 0 | int64_t timeleft; |
160 | |
|
161 | 0 | if (keyhash == 0) |
162 | 0 | keyhash = h2o_cache_calchash(key.base, key.len); |
163 | 0 | search_key.key = key; |
164 | 0 | search_key.keyhash = keyhash; |
165 | |
|
166 | 0 | lock_cache(cache); |
167 | |
|
168 | 0 | purge(cache, now); |
169 | |
|
170 | 0 | if ((iter = kh_get(cache, cache->table, &search_key)) == kh_end(cache->table)) |
171 | 0 | goto NotFound; |
172 | | |
173 | | /* found */ |
174 | 0 | ref = kh_key(cache->table, iter); |
175 | 0 | timeleft = get_timeleft(cache, ref, now); |
176 | 0 | if (timeleft < 0) |
177 | 0 | goto NotFound; |
178 | 0 | if ((cache->flags & H2O_CACHE_FLAG_EARLY_UPDATE) != 0 && timeleft < 10 && !ref->_requested_early_update) { |
179 | 0 | ref->_requested_early_update = 1; |
180 | 0 | goto NotFound; |
181 | 0 | } |
182 | | /* move the entry to the top of LRU */ |
183 | 0 | h2o_linklist_unlink(&ref->_lru_link); |
184 | 0 | h2o_linklist_insert(&cache->lru, &ref->_lru_link); |
185 | 0 | __sync_fetch_and_add(&ref->_refcnt, 1); |
186 | | |
187 | | /* unlock and return the found entry */ |
188 | 0 | unlock_cache(cache); |
189 | 0 | return ref; |
190 | | |
191 | 0 | NotFound: |
192 | 0 | unlock_cache(cache); |
193 | 0 | return NULL; |
194 | 0 | } |
195 | | |
196 | | void h2o_cache_release(h2o_cache_t *cache, h2o_cache_ref_t *ref) |
197 | 0 | { |
198 | 0 | if (__sync_fetch_and_sub(&ref->_refcnt, 1) == 1) { |
199 | 0 | assert(!h2o_linklist_is_linked(&ref->_lru_link)); |
200 | 0 | assert(!h2o_linklist_is_linked(&ref->_age_link)); |
201 | 0 | if (cache->destroy_cb != NULL) |
202 | 0 | cache->destroy_cb(ref->value); |
203 | 0 | free(ref->key.base); |
204 | 0 | free(ref); |
205 | 0 | } |
206 | 0 | } |
207 | | |
208 | | int h2o_cache_set(h2o_cache_t *cache, uint64_t now, h2o_iovec_t key, h2o_cache_hashcode_t keyhash, h2o_iovec_t value) |
209 | 0 | { |
210 | 0 | h2o_cache_ref_t *newref; |
211 | 0 | khiter_t iter; |
212 | 0 | int existed; |
213 | |
|
214 | 0 | if (keyhash == 0) |
215 | 0 | keyhash = h2o_cache_calchash(key.base, key.len); |
216 | | |
217 | | /* create newref */ |
218 | 0 | newref = h2o_mem_alloc(sizeof(*newref)); |
219 | 0 | *newref = (h2o_cache_ref_t){h2o_strdup(NULL, key.base, key.len), keyhash, now, value, 0, {NULL}, {NULL}, 1}; |
220 | |
|
221 | 0 | lock_cache(cache); |
222 | | |
223 | | /* set or replace the named value */ |
224 | 0 | iter = kh_get(cache, cache->table, newref); |
225 | 0 | if (iter != kh_end(cache->table)) { |
226 | 0 | erase_ref(cache, iter, 1); |
227 | 0 | kh_key(cache->table, iter) = newref; |
228 | 0 | existed = 1; |
229 | 0 | } else { |
230 | 0 | int unused; |
231 | 0 | kh_put(cache, cache->table, newref, &unused); |
232 | 0 | existed = 0; |
233 | 0 | } |
234 | 0 | h2o_linklist_insert(&cache->lru, &newref->_lru_link); |
235 | 0 | h2o_linklist_insert(&cache->age, &newref->_age_link); |
236 | 0 | cache->size += newref->value.len; |
237 | |
|
238 | 0 | purge(cache, now); |
239 | |
|
240 | 0 | unlock_cache(cache); |
241 | |
|
242 | 0 | return existed; |
243 | 0 | } |
244 | | |
245 | | void h2o_cache_delete(h2o_cache_t *cache, uint64_t now, h2o_iovec_t key, h2o_cache_hashcode_t keyhash) |
246 | 0 | { |
247 | 0 | h2o_cache_ref_t search_key; |
248 | 0 | khiter_t iter; |
249 | |
|
250 | 0 | if (keyhash == 0) |
251 | 0 | keyhash = h2o_cache_calchash(key.base, key.len); |
252 | 0 | search_key.key = key; |
253 | 0 | search_key.keyhash = keyhash; |
254 | |
|
255 | 0 | lock_cache(cache); |
256 | |
|
257 | 0 | purge(cache, now); |
258 | |
|
259 | 0 | if ((iter = kh_get(cache, cache->table, &search_key)) != kh_end(cache->table)) |
260 | 0 | erase_ref(cache, iter, 0); |
261 | |
|
262 | 0 | unlock_cache(cache); |
263 | 0 | } |
264 | | |
265 | | size_t h2o_cache_get_capacity(h2o_cache_t *cache) |
266 | 0 | { |
267 | 0 | return cache->capacity; |
268 | 0 | } |
269 | | |
270 | | uint64_t h2o_cache_get_duration(h2o_cache_t *cache) |
271 | 0 | { |
272 | 0 | return cache->duration; |
273 | 0 | } |