Coverage Report

Created: 2025-07-18 06:40

/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
}