mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-24 01:39:33 +00:00
609 lines
15 KiB
C
609 lines
15 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <private-lib-core.h>
|
|
#include "private-lib-misc-cache-ttl.h"
|
|
|
|
#if defined(write)
|
|
#undef write
|
|
#endif
|
|
|
|
static void
|
|
update_sul(lws_cache_ttl_lru_t_heap_t *cache);
|
|
|
|
static int
|
|
lws_cache_heap_invalidate(struct lws_cache_ttl_lru *_c, const char *key);
|
|
|
|
static int
|
|
sort_expiry(const lws_dll2_t *a, const lws_dll2_t *b)
|
|
{
|
|
const lws_cache_ttl_item_heap_t
|
|
*c = lws_container_of(a, lws_cache_ttl_item_heap_t, list_expiry),
|
|
*d = lws_container_of(b, lws_cache_ttl_item_heap_t, list_expiry);
|
|
|
|
if (c->expiry > d->expiry)
|
|
return 1;
|
|
if (c->expiry < d->expiry)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
_lws_cache_heap_item_destroy(lws_cache_ttl_lru_t_heap_t *cache,
|
|
lws_cache_ttl_item_heap_t *item)
|
|
{
|
|
lwsl_cache("%s: %s (%s)\n", __func__, cache->cache.info.name,
|
|
(const char *)&item[1] + item->size);
|
|
|
|
lws_dll2_remove(&item->list_expiry);
|
|
lws_dll2_remove(&item->list_lru);
|
|
|
|
cache->cache.current_footprint -= item->size;
|
|
|
|
update_sul(cache);
|
|
|
|
if (cache->cache.info.cb)
|
|
cache->cache.info.cb((void *)((uint8_t *)&item[1]), item->size);
|
|
|
|
lws_free(item);
|
|
}
|
|
|
|
static void
|
|
lws_cache_heap_item_destroy(lws_cache_ttl_lru_t_heap_t *cache,
|
|
lws_cache_ttl_item_heap_t *item, int parent_too)
|
|
{
|
|
struct lws_cache_ttl_lru *backing = &cache->cache;
|
|
const char *tag = ((const char *)&item[1]) + item->size;
|
|
|
|
/*
|
|
* We're destroying a normal item?
|
|
*/
|
|
|
|
if (*tag == META_ITEM_LEADING)
|
|
/* no, nothing to check here then */
|
|
goto post;
|
|
|
|
if (backing->info.parent)
|
|
backing = backing->info.parent;
|
|
|
|
/*
|
|
* We need to check any cached meta-results from lookups that
|
|
* include this normal item, and if any, invalidate the meta-results
|
|
* since they have to be recalculated before being used again.
|
|
*/
|
|
|
|
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
|
|
cache->items_lru.head) {
|
|
lws_cache_ttl_item_heap_t *i = lws_container_of(d,
|
|
lws_cache_ttl_item_heap_t,
|
|
list_lru);
|
|
const char *iname = ((const char *)&item[1]) + item->size;
|
|
uint8_t *pay = (uint8_t *)&item[1], *end = pay + item->size;
|
|
|
|
if (*iname == META_ITEM_LEADING) {
|
|
size_t taglen = strlen(iname);
|
|
|
|
/*
|
|
* If the item about to be destroyed makes an
|
|
* appearance on the meta results list, we must kill
|
|
* the meta result item to force recalc next time
|
|
*/
|
|
|
|
while (pay < end) {
|
|
uint32_t tlen = lws_ser_ru32be(pay + 4);
|
|
|
|
if (tlen == taglen &&
|
|
!strcmp((const char *)pay + 8, iname)) {
|
|
#if defined(_DEBUG)
|
|
/*
|
|
* Sanity check that the item tag is
|
|
* really a match for that meta results
|
|
* item
|
|
*/
|
|
|
|
assert (!backing->info.ops->tag_match(
|
|
backing, iname + 1, tag, 1));
|
|
#endif
|
|
_lws_cache_heap_item_destroy(cache, i);
|
|
break;
|
|
}
|
|
pay += 8 + tlen + 1;
|
|
}
|
|
|
|
#if defined(_DEBUG)
|
|
/*
|
|
* Sanity check that the item tag really isn't a match
|
|
* for that meta results item
|
|
*/
|
|
|
|
assert (backing->info.ops->tag_match(backing, iname + 1,
|
|
tag, 1));
|
|
#endif
|
|
}
|
|
|
|
} lws_end_foreach_dll_safe(d, d1);
|
|
|
|
post:
|
|
_lws_cache_heap_item_destroy(cache, item);
|
|
}
|
|
|
|
static void
|
|
lws_cache_item_evict_lru(lws_cache_ttl_lru_t_heap_t *cache)
|
|
{
|
|
lws_cache_ttl_item_heap_t *ei;
|
|
|
|
if (!cache->items_lru.head)
|
|
return;
|
|
|
|
ei = lws_container_of(cache->items_lru.head,
|
|
lws_cache_ttl_item_heap_t, list_lru);
|
|
|
|
lws_cache_heap_item_destroy(cache, ei, 0);
|
|
}
|
|
|
|
/*
|
|
* We need to weed out expired entries in the backing file
|
|
*/
|
|
|
|
static void
|
|
expiry_cb(lws_sorted_usec_list_t *sul)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache = lws_container_of(sul,
|
|
lws_cache_ttl_lru_t_heap_t, cache.sul);
|
|
lws_usec_t now = lws_now_usecs();
|
|
|
|
lwsl_cache("%s: %s\n", __func__, cache->cache.info.name);
|
|
|
|
while (cache->items_expiry.head) {
|
|
lws_cache_ttl_item_heap_t *item;
|
|
|
|
item = lws_container_of(cache->items_expiry.head,
|
|
lws_cache_ttl_item_heap_t, list_expiry);
|
|
|
|
if (item->expiry > now)
|
|
return;
|
|
|
|
lws_cache_heap_item_destroy(cache, item, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Let's figure out what the earliest next expiry is
|
|
*/
|
|
|
|
static int
|
|
earliest_expiry(lws_cache_ttl_lru_t_heap_t *cache, lws_usec_t *pearliest)
|
|
{
|
|
lws_cache_ttl_item_heap_t *item;
|
|
|
|
if (!cache->items_expiry.head)
|
|
return 1;
|
|
|
|
item = lws_container_of(cache->items_expiry.head,
|
|
lws_cache_ttl_item_heap_t, list_expiry);
|
|
|
|
*pearliest = item->expiry;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
update_sul(lws_cache_ttl_lru_t_heap_t *cache)
|
|
{
|
|
lws_usec_t earliest;
|
|
|
|
/* weed out any newly-expired */
|
|
expiry_cb(&cache->cache.sul);
|
|
|
|
/* figure out the next soonest expiring item */
|
|
if (earliest_expiry(cache, &earliest)) {
|
|
lws_sul_cancel(&cache->cache.sul);
|
|
return;
|
|
}
|
|
|
|
lwsl_debug("%s: setting exp %llu\n", __func__,
|
|
(unsigned long long)earliest);
|
|
|
|
if (earliest)
|
|
lws_cache_schedule(&cache->cache, expiry_cb, earliest);
|
|
}
|
|
|
|
static lws_cache_ttl_item_heap_t *
|
|
lws_cache_heap_specific(lws_cache_ttl_lru_t_heap_t *cache,
|
|
const char *specific_key)
|
|
{
|
|
lws_start_foreach_dll(struct lws_dll2 *, d, cache->items_lru.head) {
|
|
lws_cache_ttl_item_heap_t *item = lws_container_of(d,
|
|
lws_cache_ttl_item_heap_t,
|
|
list_lru);
|
|
const char *iname = ((const char *)&item[1]) + item->size;
|
|
|
|
if (!strcmp(specific_key, iname))
|
|
return item;
|
|
|
|
} lws_end_foreach_dll(d);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
lws_cache_heap_tag_match(struct lws_cache_ttl_lru *cache, const char *wc,
|
|
const char *tag, char lookup_rules)
|
|
{
|
|
return lws_strcmp_wildcard(wc, strlen(wc), tag, strlen(tag));
|
|
}
|
|
|
|
static int
|
|
lws_cache_heap_lookup(struct lws_cache_ttl_lru *_c, const char *wildcard_key,
|
|
lws_dll2_owner_t *results_owner)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)_c;
|
|
size_t sklen = strlen(wildcard_key);
|
|
|
|
lws_start_foreach_dll(struct lws_dll2 *, d, cache->items_lru.head) {
|
|
lws_cache_ttl_item_heap_t *item = lws_container_of(d,
|
|
lws_cache_ttl_item_heap_t,
|
|
list_lru);
|
|
const char *iname = ((const char *)&item[1]) + item->size;
|
|
|
|
if (!lws_strcmp_wildcard(wildcard_key, sklen, iname,
|
|
strlen(iname))) {
|
|
size_t ilen = strlen(iname);
|
|
lws_cache_match_t *m;
|
|
char hit = 0;
|
|
|
|
/*
|
|
* It musn't already be on the list from an earlier
|
|
* cache level
|
|
*/
|
|
|
|
lws_start_foreach_dll(struct lws_dll2 *, e,
|
|
results_owner->head) {
|
|
lws_cache_match_t *i = lws_container_of(e,
|
|
lws_cache_match_t, list);
|
|
if (i->tag_size == ilen &&
|
|
!strcmp(iname, ((const char *)&i[1]))) {
|
|
hit = 1;
|
|
break;
|
|
}
|
|
} lws_end_foreach_dll(e);
|
|
|
|
if (!hit) {
|
|
|
|
/*
|
|
* it's unique, instantiate a record for it
|
|
*/
|
|
|
|
m = lws_fi(&_c->info.cx->fic,
|
|
"cache_lookup_oom") ? NULL :
|
|
lws_malloc(sizeof(*m) + ilen + 1,
|
|
__func__);
|
|
if (!m) {
|
|
lws_cache_clear_matches(results_owner);
|
|
return 1;
|
|
}
|
|
|
|
memset(&m->list, 0, sizeof(m->list));
|
|
m->tag_size = ilen;
|
|
memcpy(&m[1], iname, ilen + 1);
|
|
|
|
lws_dll2_add_tail(&m->list, results_owner);
|
|
}
|
|
}
|
|
|
|
} lws_end_foreach_dll(d);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_cache_heap_write(struct lws_cache_ttl_lru *_c, const char *specific_key,
|
|
const uint8_t *source, size_t size, lws_usec_t expiry,
|
|
void **ppvoid)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)_c;
|
|
struct lws_cache_ttl_lru *backing = _c;
|
|
lws_cache_ttl_item_heap_t *item, *ei;
|
|
size_t kl = strlen(specific_key);
|
|
char *p;
|
|
|
|
lwsl_cache("%s: %s: len %d\n", __func__, _c->info.name, (int)size);
|
|
|
|
/*
|
|
* Is this new tag going to invalidate any existing cached meta-results?
|
|
*
|
|
* If so, let's destroy any of those first to recover the heap
|
|
*/
|
|
|
|
if (backing->info.parent)
|
|
backing = backing->info.parent;
|
|
|
|
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
|
|
cache->items_lru.head) {
|
|
lws_cache_ttl_item_heap_t *i = lws_container_of(d,
|
|
lws_cache_ttl_item_heap_t,
|
|
list_lru);
|
|
const char *iname = ((const char *)&i[1]) + i->size;
|
|
|
|
if (*iname == META_ITEM_LEADING) {
|
|
|
|
/*
|
|
* If the item about to be added would match any cached
|
|
* results from before it was added, we have to
|
|
* invalidate them. To check this, we have to use the
|
|
* matching rules at the backing store level
|
|
*/
|
|
|
|
if (!strcmp(iname + 1, specific_key))
|
|
_lws_cache_heap_item_destroy(cache, i);
|
|
}
|
|
|
|
} lws_end_foreach_dll_safe(d, d1);
|
|
|
|
|
|
/*
|
|
* Keep us under the limit if possible... note this will always allow
|
|
* caching a single large item even if it is above the limits
|
|
*/
|
|
|
|
while ((cache->cache.info.max_footprint &&
|
|
cache->cache.current_footprint + size >
|
|
cache->cache.info.max_footprint) ||
|
|
(cache->cache.info.max_items &&
|
|
cache->items_lru.count + 1 > cache->cache.info.max_items))
|
|
lws_cache_item_evict_lru(cache);
|
|
|
|
/* remove any existing entry of the same key */
|
|
|
|
lws_cache_heap_invalidate(&cache->cache, specific_key);
|
|
|
|
item = lws_fi(&_c->info.cx->fic, "cache_write_oom") ? NULL :
|
|
lws_malloc(sizeof(*item) + kl + 1u + size, __func__);
|
|
if (!item)
|
|
return 1;
|
|
|
|
cache->cache.current_footprint += item->size;
|
|
|
|
/* only need to zero down our item object */
|
|
memset(item, 0, sizeof(*item));
|
|
|
|
p = (char *)&item[1];
|
|
if (ppvoid)
|
|
*ppvoid = p;
|
|
|
|
/* copy the payload into place */
|
|
if (source)
|
|
memcpy(p, source, size);
|
|
|
|
/* copy the key string into place, with terminating NUL */
|
|
memcpy(p + size, specific_key, kl + 1);
|
|
|
|
item->expiry = expiry;
|
|
item->key_len = kl;
|
|
item->size = size;
|
|
|
|
if (expiry) {
|
|
/* adding to expiry is optional, on nonzero expiry */
|
|
lws_dll2_add_sorted(&item->list_expiry, &cache->items_expiry,
|
|
sort_expiry);
|
|
ei = lws_container_of(cache->items_expiry.head,
|
|
lws_cache_ttl_item_heap_t, list_expiry);
|
|
lwsl_debug("%s: setting exp %llu\n", __func__,
|
|
(unsigned long long)ei->expiry);
|
|
lws_cache_schedule(&cache->cache, expiry_cb, ei->expiry);
|
|
}
|
|
|
|
/* always add outselves to head of lru list */
|
|
lws_dll2_add_head(&item->list_lru, &cache->items_lru);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_cache_heap_get(struct lws_cache_ttl_lru *_c, const char *specific_key,
|
|
const void **pdata, size_t *psize)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)_c;
|
|
lws_cache_ttl_item_heap_t *item;
|
|
|
|
item = lws_cache_heap_specific(cache, specific_key);
|
|
if (!item)
|
|
return 1;
|
|
|
|
/* we are using it, move it to lru head */
|
|
lws_dll2_remove(&item->list_lru);
|
|
lws_dll2_add_head(&item->list_lru, &cache->items_lru);
|
|
|
|
if (pdata) {
|
|
*pdata = (const void *)&item[1];
|
|
*psize = item->size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_cache_heap_invalidate(struct lws_cache_ttl_lru *_c, const char *specific_key)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)_c;
|
|
struct lws_cache_ttl_lru *backing = _c;
|
|
lws_cache_ttl_item_heap_t *item;
|
|
const void *user;
|
|
size_t size;
|
|
|
|
if (lws_cache_heap_get(_c, specific_key, &user, &size))
|
|
return 0;
|
|
|
|
if (backing->info.parent)
|
|
backing = backing->info.parent;
|
|
|
|
item = (lws_cache_ttl_item_heap_t *)(((uint8_t *)user) - sizeof(*item));
|
|
|
|
/*
|
|
* We must invalidate any cached results that would have included this
|
|
*/
|
|
|
|
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
|
|
cache->items_lru.head) {
|
|
lws_cache_ttl_item_heap_t *i = lws_container_of(d,
|
|
lws_cache_ttl_item_heap_t,
|
|
list_lru);
|
|
const char *iname = ((const char *)&i[1]) + i->size;
|
|
|
|
if (*iname == META_ITEM_LEADING) {
|
|
|
|
/*
|
|
* If the item about to be added would match any cached
|
|
* results from before it was added, we have to
|
|
* invalidate them. To check this, we have to use the
|
|
* matching rules at the backing store level
|
|
*/
|
|
|
|
if (!backing->info.ops->tag_match(backing, iname + 1,
|
|
specific_key, 1))
|
|
_lws_cache_heap_item_destroy(cache, i);
|
|
}
|
|
|
|
} lws_end_foreach_dll_safe(d, d1);
|
|
|
|
lws_cache_heap_item_destroy(cache, item, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct lws_cache_ttl_lru *
|
|
lws_cache_heap_create(const struct lws_cache_creation_info *info)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache;
|
|
|
|
assert(info->cx);
|
|
assert(info->name);
|
|
|
|
cache = lws_fi(&info->cx->fic, "cache_createfail") ? NULL :
|
|
lws_zalloc(sizeof(*cache), __func__);
|
|
if (!cache)
|
|
return NULL;
|
|
|
|
cache->cache.info = *info;
|
|
if (info->parent)
|
|
info->parent->child = &cache->cache;
|
|
|
|
// lwsl_cache("%s: create %s\n", __func__, info->name);
|
|
|
|
return (struct lws_cache_ttl_lru *)cache;
|
|
}
|
|
|
|
static int
|
|
destroy_dll(struct lws_dll2 *d, void *user)
|
|
{
|
|
lws_cache_ttl_lru_t *_c = (struct lws_cache_ttl_lru *)user;
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)_c;
|
|
lws_cache_ttl_item_heap_t *item =
|
|
lws_container_of(d, lws_cache_ttl_item_heap_t, list_lru);
|
|
|
|
lws_cache_heap_item_destroy(cache, item, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lws_cache_heap_expunge(struct lws_cache_ttl_lru *_c)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)_c;
|
|
|
|
lws_dll2_foreach_safe(&cache->items_lru, cache, destroy_dll);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
lws_cache_heap_destroy(struct lws_cache_ttl_lru **_cache)
|
|
{
|
|
lws_cache_ttl_lru_t *c = *_cache;
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)c;
|
|
|
|
if (!cache)
|
|
return;
|
|
|
|
lws_sul_cancel(&c->sul);
|
|
|
|
lws_dll2_foreach_safe(&cache->items_lru, cache, destroy_dll);
|
|
|
|
lws_free_set_NULL(*_cache);
|
|
}
|
|
|
|
#if defined(_DEBUG)
|
|
static int
|
|
dump_dll(struct lws_dll2 *d, void *user)
|
|
{
|
|
lws_cache_ttl_item_heap_t *item =
|
|
lws_container_of(d, lws_cache_ttl_item_heap_t, list_lru);
|
|
|
|
lwsl_cache(" %s: size %d, exp %llu\n",
|
|
(const char *)&item[1] + item->size,
|
|
(int)item->size, (unsigned long long)item->expiry);
|
|
|
|
lwsl_hexdump_cache((const char *)&item[1], item->size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
lws_cache_heap_debug_dump(struct lws_cache_ttl_lru *_c)
|
|
{
|
|
lws_cache_ttl_lru_t_heap_t *cache = (lws_cache_ttl_lru_t_heap_t *)_c;
|
|
#if !defined(LWS_WITH_NO_LOGS)
|
|
lws_cache_ttl_item_heap_t *item = NULL;
|
|
|
|
lws_dll2_t *d = cache->items_expiry.head;
|
|
|
|
if (d)
|
|
item = lws_container_of(d, lws_cache_ttl_item_heap_t,
|
|
list_expiry);
|
|
|
|
lwsl_cache("%s: %s: items %d, earliest %llu\n", __func__,
|
|
cache->cache.info.name, (int)cache->items_lru.count,
|
|
item ? (unsigned long long)item->expiry : 0ull);
|
|
#endif
|
|
|
|
lws_dll2_foreach_safe(&cache->items_lru, cache, dump_dll);
|
|
}
|
|
#endif
|
|
|
|
const struct lws_cache_ops lws_cache_ops_heap = {
|
|
.create = lws_cache_heap_create,
|
|
.destroy = lws_cache_heap_destroy,
|
|
.expunge = lws_cache_heap_expunge,
|
|
|
|
.write = lws_cache_heap_write,
|
|
.tag_match = lws_cache_heap_tag_match,
|
|
.lookup = lws_cache_heap_lookup,
|
|
.invalidate = lws_cache_heap_invalidate,
|
|
.get = lws_cache_heap_get,
|
|
#if defined(_DEBUG)
|
|
.debug_dump = lws_cache_heap_debug_dump,
|
|
#endif
|
|
};
|