#include <assert.h>
#include "svn_pools.h"
#include "svn_private_config.h"
#include "cache.h"
#include "private/svn_mutex.h"
typedef struct inprocess_cache_t {
const char *id;
apr_hash_t *hash;
apr_ssize_t klen;
svn_cache__serialize_func_t serialize_func;
svn_cache__deserialize_func_t deserialize_func;
apr_uint64_t total_pages;
apr_uint64_t unallocated_pages;
apr_uint64_t items_per_page;
struct cache_page *sentinel;
struct cache_page *partial_page;
apr_uint64_t partial_page_number_filled;
apr_pool_t *cache_pool;
apr_size_t data_size;
svn_mutex__t *mutex;
} inprocess_cache_t;
struct cache_page {
struct cache_page *prev;
struct cache_page *next;
apr_pool_t *page_pool;
struct cache_entry *first_entry;
};
struct cache_entry {
const void *key;
void *value;
apr_size_t size;
struct cache_page *page;
struct cache_entry *next_entry;
};
static void
remove_page_from_list(struct cache_page *page)
{
page->prev->next = page->next;
page->next->prev = page->prev;
}
static void
insert_page(inprocess_cache_t *cache,
struct cache_page *page)
{
struct cache_page *pred = cache->sentinel;
page->prev = pred;
page->next = pred->next;
page->prev->next = page;
page->next->prev = page;
}
static svn_error_t *
move_page_to_front(inprocess_cache_t *cache,
struct cache_page *page)
{
SVN_ERR_ASSERT(page != cache->sentinel);
if (! page->next)
return SVN_NO_ERROR;
remove_page_from_list(page);
insert_page(cache, page);
return SVN_NO_ERROR;
}
static const void *
duplicate_key(inprocess_cache_t *cache,
const void *key,
apr_pool_t *pool)
{
if (cache->klen == APR_HASH_KEY_STRING)
return apr_pstrdup(pool, key);
else
return apr_pmemdup(pool, key, cache->klen);
}
static svn_error_t *
inprocess_cache_get_internal(char **buffer,
apr_size_t *size,
inprocess_cache_t *cache,
const void *key,
apr_pool_t *result_pool)
{
struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
if (entry)
{
SVN_ERR(move_page_to_front(cache, entry->page));
*buffer = apr_palloc(result_pool, entry->size);
if (entry->size)
memcpy(*buffer, entry->value, entry->size);
*size = entry->size;
}
else
{
*buffer = NULL;
*size = 0;
}
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_get(void **value_p,
svn_boolean_t *found,
void *cache_void,
const void *key,
apr_pool_t *result_pool)
{
inprocess_cache_t *cache = cache_void;
if (key)
{
char* buffer;
apr_size_t size;
SVN_MUTEX__WITH_LOCK(cache->mutex,
inprocess_cache_get_internal(&buffer,
&size,
cache,
key,
result_pool));
*found = (buffer != NULL);
if (!buffer || !size)
*value_p = NULL;
else
return cache->deserialize_func(value_p, buffer, size, result_pool);
}
else
{
*value_p = NULL;
*found = FALSE;
}
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_has_key_internal(svn_boolean_t *found,
inprocess_cache_t *cache,
const void *key,
apr_pool_t *scratch_pool)
{
*found = apr_hash_get(cache->hash, key, cache->klen) != NULL;
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_has_key(svn_boolean_t *found,
void *cache_void,
const void *key,
apr_pool_t *scratch_pool)
{
inprocess_cache_t *cache = cache_void;
if (key)
SVN_MUTEX__WITH_LOCK(cache->mutex,
inprocess_cache_has_key_internal(found,
cache,
key,
scratch_pool));
else
*found = FALSE;
return SVN_NO_ERROR;
}
static void
erase_page(inprocess_cache_t *cache,
struct cache_page *page)
{
struct cache_entry *e;
remove_page_from_list(page);
for (e = page->first_entry;
e;
e = e->next_entry)
{
cache->data_size -= e->size;
apr_hash_set(cache->hash, e->key, cache->klen, NULL);
}
svn_pool_clear(page->page_pool);
page->first_entry = NULL;
page->prev = NULL;
page->next = NULL;
cache->partial_page = page;
cache->partial_page_number_filled = 0;
}
static svn_error_t *
inprocess_cache_set_internal(inprocess_cache_t *cache,
const void *key,
void *value,
apr_pool_t *scratch_pool)
{
struct cache_entry *existing_entry;
existing_entry = apr_hash_get(cache->hash, key, cache->klen);
if (existing_entry && cache->items_per_page == 1)
{
struct cache_page *page = existing_entry->page;
SVN_ERR_ASSERT(page->next != NULL);
SVN_ERR_ASSERT(cache->partial_page == NULL);
erase_page(cache, page);
existing_entry = NULL;
}
if (existing_entry)
{
struct cache_page *page = existing_entry->page;
SVN_ERR(move_page_to_front(cache, page));
cache->data_size -= existing_entry->size;
if (value)
{
SVN_ERR(cache->serialize_func(&existing_entry->value,
&existing_entry->size,
value,
page->page_pool));
cache->data_size += existing_entry->size;
if (existing_entry->size == 0)
existing_entry->value = NULL;
}
else
{
existing_entry->value = NULL;
existing_entry->size = 0;
}
return SVN_NO_ERROR;
}
if (cache->partial_page == NULL && cache->unallocated_pages > 0)
{
cache->partial_page = apr_pcalloc(cache->cache_pool,
sizeof(*(cache->partial_page)));
cache->partial_page->page_pool = svn_pool_create(cache->cache_pool);
cache->partial_page_number_filled = 0;
(cache->unallocated_pages)--;
}
if (cache->partial_page == NULL)
{
struct cache_page *oldest_page = cache->sentinel->prev;
SVN_ERR_ASSERT(oldest_page != cache->sentinel);
erase_page(cache, oldest_page);
}
SVN_ERR_ASSERT(cache->partial_page != NULL);
{
struct cache_page *page = cache->partial_page;
struct cache_entry *new_entry = apr_pcalloc(page->page_pool,
sizeof(*new_entry));
new_entry->key = duplicate_key(cache, key, page->page_pool);
if (value)
{
SVN_ERR(cache->serialize_func(&new_entry->value,
&new_entry->size,
value,
page->page_pool));
cache->data_size += new_entry->size;
if (new_entry->size == 0)
new_entry->value = NULL;
}
else
{
new_entry->value = NULL;
new_entry->size = 0;
}
new_entry->page = page;
new_entry->next_entry = page->first_entry;
page->first_entry = new_entry;
apr_hash_set(cache->hash, new_entry->key, cache->klen, new_entry);
(cache->partial_page_number_filled)++;
if (cache->partial_page_number_filled >= cache->items_per_page)
{
insert_page(cache, page);
cache->partial_page = NULL;
}
}
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_set(void *cache_void,
const void *key,
void *value,
apr_pool_t *scratch_pool)
{
inprocess_cache_t *cache = cache_void;
if (key)
SVN_MUTEX__WITH_LOCK(cache->mutex,
inprocess_cache_set_internal(cache,
key,
value,
scratch_pool));
return SVN_NO_ERROR;
}
struct cache_iter_baton {
svn_iter_apr_hash_cb_t user_cb;
void *user_baton;
};
static svn_error_t *
iter_cb(void *baton,
const void *key,
apr_ssize_t klen,
void *val,
apr_pool_t *pool)
{
struct cache_iter_baton *b = baton;
struct cache_entry *entry = val;
return (b->user_cb)(b->user_baton, key, klen, entry->value, pool);
}
static svn_error_t *
inprocess_cache_iter(svn_boolean_t *completed,
void *cache_void,
svn_iter_apr_hash_cb_t user_cb,
void *user_baton,
apr_pool_t *scratch_pool)
{
inprocess_cache_t *cache = cache_void;
struct cache_iter_baton b;
b.user_cb = user_cb;
b.user_baton = user_baton;
SVN_MUTEX__WITH_LOCK(cache->mutex,
svn_iter_apr_hash(completed, cache->hash,
iter_cb, &b, scratch_pool));
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_get_partial_internal(void **value_p,
svn_boolean_t *found,
inprocess_cache_t *cache,
const void *key,
svn_cache__partial_getter_func_t func,
void *baton,
apr_pool_t *result_pool)
{
struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
if (! entry)
{
*found = FALSE;
return SVN_NO_ERROR;
}
SVN_ERR(move_page_to_front(cache, entry->page));
*found = TRUE;
return func(value_p, entry->value, entry->size, baton, result_pool);
}
static svn_error_t *
inprocess_cache_get_partial(void **value_p,
svn_boolean_t *found,
void *cache_void,
const void *key,
svn_cache__partial_getter_func_t func,
void *baton,
apr_pool_t *result_pool)
{
inprocess_cache_t *cache = cache_void;
if (key)
SVN_MUTEX__WITH_LOCK(cache->mutex,
inprocess_cache_get_partial_internal(value_p,
found,
cache,
key,
func,
baton,
result_pool));
else
*found = FALSE;
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_set_partial_internal(inprocess_cache_t *cache,
const void *key,
svn_cache__partial_setter_func_t func,
void *baton,
apr_pool_t *scratch_pool)
{
struct cache_entry *entry = apr_hash_get(cache->hash, key, cache->klen);
if (entry)
{
SVN_ERR(move_page_to_front(cache, entry->page));
cache->data_size -= entry->size;
SVN_ERR(func(&entry->value,
&entry->size,
baton,
entry->page->page_pool));
cache->data_size += entry->size;
}
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_set_partial(void *cache_void,
const void *key,
svn_cache__partial_setter_func_t func,
void *baton,
apr_pool_t *scratch_pool)
{
inprocess_cache_t *cache = cache_void;
if (key)
SVN_MUTEX__WITH_LOCK(cache->mutex,
inprocess_cache_set_partial_internal(cache,
key,
func,
baton,
scratch_pool));
return SVN_NO_ERROR;
}
static svn_boolean_t
inprocess_cache_is_cachable(void *cache_void, apr_size_t size)
{
inprocess_cache_t *cache = cache_void;
return size < SVN_ALLOCATOR_RECOMMENDED_MAX_FREE / cache->items_per_page;
}
static svn_error_t *
inprocess_cache_get_info_internal(inprocess_cache_t *cache,
svn_cache__info_t *info,
apr_pool_t *result_pool)
{
info->id = apr_pstrdup(result_pool, cache->id);
info->used_entries = apr_hash_count(cache->hash);
info->total_entries = cache->items_per_page * cache->total_pages;
info->used_size = cache->data_size;
info->data_size = cache->data_size;
info->total_size = cache->data_size
+ cache->items_per_page * sizeof(struct cache_page)
+ info->used_entries * sizeof(struct cache_entry);
return SVN_NO_ERROR;
}
static svn_error_t *
inprocess_cache_get_info(void *cache_void,
svn_cache__info_t *info,
svn_boolean_t reset,
apr_pool_t *result_pool)
{
inprocess_cache_t *cache = cache_void;
SVN_MUTEX__WITH_LOCK(cache->mutex,
inprocess_cache_get_info_internal(cache,
info,
result_pool));
return SVN_NO_ERROR;
}
static svn_cache__vtable_t inprocess_cache_vtable = {
inprocess_cache_get,
inprocess_cache_has_key,
inprocess_cache_set,
inprocess_cache_iter,
inprocess_cache_is_cachable,
inprocess_cache_get_partial,
inprocess_cache_set_partial,
inprocess_cache_get_info
};
svn_error_t *
svn_cache__create_inprocess(svn_cache__t **cache_p,
svn_cache__serialize_func_t serialize,
svn_cache__deserialize_func_t deserialize,
apr_ssize_t klen,
apr_int64_t pages,
apr_int64_t items_per_page,
svn_boolean_t thread_safe,
const char *id,
apr_pool_t *pool)
{
svn_cache__t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
inprocess_cache_t *cache = apr_pcalloc(pool, sizeof(*cache));
cache->id = apr_pstrdup(pool, id);
SVN_ERR_ASSERT(klen == APR_HASH_KEY_STRING || klen >= 1);
cache->hash = apr_hash_make(pool);
cache->klen = klen;
cache->serialize_func = serialize;
cache->deserialize_func = deserialize;
SVN_ERR_ASSERT(pages >= 1);
cache->total_pages = pages;
cache->unallocated_pages = pages;
SVN_ERR_ASSERT(items_per_page >= 1);
cache->items_per_page = items_per_page;
cache->sentinel = apr_pcalloc(pool, sizeof(*(cache->sentinel)));
cache->sentinel->prev = cache->sentinel;
cache->sentinel->next = cache->sentinel;
SVN_ERR(svn_mutex__init(&cache->mutex, thread_safe, pool));
cache->cache_pool = pool;
wrapper->vtable = &inprocess_cache_vtable;
wrapper->cache_internal = cache;
wrapper->pretend_empty = !!getenv("SVN_X_DOES_NOT_MARK_THE_SPOT");
*cache_p = wrapper;
return SVN_NO_ERROR;
}