mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-12-02 00:01:37 +01:00
Commit 7810ad7 ("BUG/MAJOR: lru: fix unconditional call to free due to
unexpected semi-colon") was not enough, it happens that the free() is
not performed at the right place because if the evicted node is recycled,
we must also release its data before it gets overwritten.
No backport is needed.
279 lines
7.3 KiB
C
279 lines
7.3 KiB
C
/*
|
|
* Copyright (C) 2015 Willy Tarreau <w@1wt.eu>
|
|
*
|
|
* 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 <import/lru.h>
|
|
|
|
/* Minimal list manipulation macros for lru64_list */
|
|
#define LIST_ADD(lh, el) ({ (el)->n = (lh)->n; (el)->n->p = (lh)->n = (el); (el)->p = (lh); })
|
|
#define LIST_DEL(el) ({ (el)->n->p = (el)->p; (el)->p->n = (el)->n; })
|
|
|
|
|
|
/* Lookup key <key> in LRU cache <lru> for use with domain <domain> whose data's
|
|
* current version is <revision>. It differs from lru64_get as it does not
|
|
* create missing keys. The function returns NULL if an error or a cache miss
|
|
* occurs. */
|
|
struct lru64 *lru64_lookup(unsigned long long key, struct lru64_head *lru,
|
|
void *domain, unsigned long long revision)
|
|
{
|
|
struct eb64_node *node;
|
|
struct lru64 *elem;
|
|
|
|
if (!lru->spare) {
|
|
if (!lru->cache_size)
|
|
return NULL;
|
|
lru->spare = malloc(sizeof(*lru->spare));
|
|
if (!lru->spare)
|
|
return NULL;
|
|
lru->spare->domain = NULL;
|
|
}
|
|
|
|
node = __eb64_lookup(&lru->keys, key);
|
|
elem = container_of(node, typeof(*elem), node);
|
|
if (elem) {
|
|
/* Existing entry found, check validity then move it at the
|
|
* head of the LRU list.
|
|
*/
|
|
if (elem->domain == domain && elem->revision == revision) {
|
|
LIST_DEL(&elem->lru);
|
|
LIST_ADD(&lru->list, &elem->lru);
|
|
return elem;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Get key <key> from LRU cache <lru> for use with domain <domain> whose data's
|
|
* current revision is <revision>. If the key doesn't exist it's first created
|
|
* with ->domain = NULL. The caller detects this situation by checking ->domain
|
|
* and must perform the operation to be cached then call lru64_commit() to
|
|
* complete the operation. A lock (mutex or spinlock) may be added around the
|
|
* function to permit use in a multi-threaded environment. The function may
|
|
* return NULL upon memory allocation failure.
|
|
*/
|
|
struct lru64 *lru64_get(unsigned long long key, struct lru64_head *lru,
|
|
void *domain, unsigned long long revision)
|
|
{
|
|
struct eb64_node *node;
|
|
struct lru64 *elem;
|
|
|
|
if (!lru->spare) {
|
|
if (!lru->cache_size)
|
|
return NULL;
|
|
lru->spare = malloc(sizeof(*lru->spare));
|
|
if (!lru->spare)
|
|
return NULL;
|
|
lru->spare->domain = NULL;
|
|
}
|
|
|
|
/* Lookup or insert */
|
|
lru->spare->node.key = key;
|
|
node = __eb64_insert(&lru->keys, &lru->spare->node);
|
|
elem = container_of(node, typeof(*elem), node);
|
|
|
|
if (elem != lru->spare) {
|
|
/* Existing entry found, check validity then move it at the
|
|
* head of the LRU list.
|
|
*/
|
|
if (elem->domain == domain && elem->revision == revision) {
|
|
LIST_DEL(&elem->lru);
|
|
LIST_ADD(&lru->list, &elem->lru);
|
|
return elem;
|
|
}
|
|
|
|
if (!elem->domain)
|
|
return NULL; // currently locked
|
|
|
|
/* recycle this entry */
|
|
LIST_DEL(&elem->lru);
|
|
}
|
|
else {
|
|
/* New entry inserted, initialize and move to the head of the
|
|
* LRU list, and lock it until commit.
|
|
*/
|
|
lru->cache_usage++;
|
|
lru->spare = NULL; // used, need a new one next time
|
|
}
|
|
|
|
elem->domain = NULL;
|
|
LIST_ADD(&lru->list, &elem->lru);
|
|
|
|
if (lru->cache_usage > lru->cache_size) {
|
|
/* try to kill oldest entry */
|
|
struct lru64 *old;
|
|
|
|
old = container_of(lru->list.p, typeof(*old), lru);
|
|
if (old->domain) {
|
|
/* not locked */
|
|
LIST_DEL(&old->lru);
|
|
__eb64_delete(&old->node);
|
|
if (old->data && old->free)
|
|
old->free(old->data);
|
|
if (!lru->spare)
|
|
lru->spare = old;
|
|
else {
|
|
free(old);
|
|
}
|
|
lru->cache_usage--;
|
|
}
|
|
}
|
|
return elem;
|
|
}
|
|
|
|
/* Commit element <elem> with data <data>, domain <domain> and revision
|
|
* <revision>. <elem> is checked for NULL so that it's possible to call it
|
|
* with the result from a call to lru64_get(). The caller might lock it using a
|
|
* spinlock or mutex shared with the one around lru64_get().
|
|
*/
|
|
void lru64_commit(struct lru64 *elem, void *data, void *domain,
|
|
unsigned long long revision, void (*free)(void *))
|
|
{
|
|
if (!elem)
|
|
return;
|
|
|
|
elem->data = data;
|
|
elem->revision = revision;
|
|
elem->domain = domain;
|
|
elem->free = free;
|
|
}
|
|
|
|
/* Create a new LRU cache of <size> entries. Returns the new cache or NULL in
|
|
* case of allocation failure.
|
|
*/
|
|
struct lru64_head *lru64_new(int size)
|
|
{
|
|
struct lru64_head *lru;
|
|
|
|
lru = malloc(sizeof(*lru));
|
|
if (lru) {
|
|
lru->list.p = lru->list.n = &lru->list;
|
|
lru->keys = EB_ROOT_UNIQUE;
|
|
lru->spare = NULL;
|
|
lru->cache_size = size;
|
|
lru->cache_usage = 0;
|
|
}
|
|
return lru;
|
|
}
|
|
|
|
/* Tries to destroy the LRU cache <lru>. Returns the number of locked entries
|
|
* that prevent it from being destroyed, or zero meaning everything was done.
|
|
*/
|
|
int lru64_destroy(struct lru64_head *lru)
|
|
{
|
|
struct lru64 *elem, *next;
|
|
|
|
if (!lru)
|
|
return 0;
|
|
|
|
elem = container_of(lru->list.p, typeof(*elem), lru);
|
|
while (&elem->lru != &lru->list) {
|
|
next = container_of(elem->lru.p, typeof(*next), lru);
|
|
if (elem->domain) {
|
|
/* not locked */
|
|
LIST_DEL(&elem->lru);
|
|
eb64_delete(&elem->node);
|
|
if (elem->data && elem->free)
|
|
elem->free(elem->data);
|
|
free(elem);
|
|
lru->cache_usage--;
|
|
lru->cache_size--;
|
|
}
|
|
elem = next;
|
|
}
|
|
|
|
if (lru->cache_usage)
|
|
return lru->cache_usage;
|
|
|
|
free(lru);
|
|
return 0;
|
|
}
|
|
|
|
/* The code below is just for validation and performance testing. It's an
|
|
* example of a function taking some time to return results that could be
|
|
* cached.
|
|
*/
|
|
#ifdef STANDALONE
|
|
|
|
#include <stdio.h>
|
|
|
|
static unsigned int misses;
|
|
|
|
static unsigned long long sum(unsigned long long x)
|
|
{
|
|
#ifndef TEST_LRU_FAST_OPERATION
|
|
if (x < 1)
|
|
return 0;
|
|
return x + sum(x * 99 / 100 - 1);
|
|
#else
|
|
return (x << 16) - (x << 8) - 1;
|
|
#endif
|
|
}
|
|
|
|
static long get_value(struct lru64_head *lru, long a)
|
|
{
|
|
struct lru64 *item;
|
|
|
|
if (lru) {
|
|
item = lru64_get(a, lru, lru, 0);
|
|
if (item && item->domain)
|
|
return (long)item->data;
|
|
}
|
|
misses++;
|
|
/* do the painful work here */
|
|
a = sum(a);
|
|
if (item)
|
|
lru64_commit(item, (void *)a, lru, 0);
|
|
return a;
|
|
}
|
|
|
|
/* pass #of loops in argv[1] and set argv[2] to something to use the LRU */
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct lru64_head *lru = NULL;
|
|
long long ret;
|
|
int total, loops;
|
|
|
|
if (argc < 2) {
|
|
printf("Need a number of rounds and optionally an LRU cache size (0..65536)\n");
|
|
exit(1);
|
|
}
|
|
|
|
total = atoi(argv[1]);
|
|
|
|
if (argc > 2) /* cache size */
|
|
lru = lru64_new(atoi(argv[2]));
|
|
|
|
ret = 0;
|
|
for (loops = 0; loops < total; loops++) {
|
|
ret += get_value(lru, rand() & 65535);
|
|
}
|
|
/* just for accuracy control */
|
|
printf("ret=%llx, hits=%d, misses=%d (%d %% hits)\n", ret, total-misses, misses, (int)((float)(total-misses) * 100.0 / total));
|
|
|
|
while (lru64_destroy(lru));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|