change the persistant data store to owning the allocations

This commit is contained in:
Vincent Sanders 2014-11-22 23:56:13 +00:00
parent 335ba082fd
commit 8b810ee4a1
4 changed files with 264 additions and 138 deletions

View File

@ -31,8 +31,6 @@ enum backing_store_flags {
BACKING_STORE_NONE = 0,
/** data is metadata */
BACKING_STORE_META = 1,
/** backing store will handle allocation. */
BACKING_STORE_ALLOC = 2
};
/** low level cache backing store operation table
@ -61,12 +59,13 @@ struct gui_llcache_table {
* Place an object in the backing store.
*
* The object is placed in the persistent store and may be
* retrieved. If the BACKING_STORE_ALLOC flag is used the
* backing store will take a reference to the passed data,
* subsequently the caller should explicitly release the
* allocation using the release method and not free the data
* itself. An additional effect of this is that the persistent
* storage may not have been completely written on return.
* retrieved with the fetch method.
* The backing store will take a reference to the
* passed data, subsequently the caller should explicitly
* release the allocation using the release method and not
* free the data itself.
* The caller may not assume that the persistent storage has
* been completely written on return.
*
* @param[in] url The url is used as the unique primary key for the data.
* @param[in] flags The flags to control how the obejct is stored.
@ -80,9 +79,13 @@ struct gui_llcache_table {
/**
* Retrive an object from the backing store.
*
* If the BACKING_STORE_ALLOC flag is set the returned memory
* is managed by the backing store and should be freed by
* calling the release method.
* The caller may provide a buffer in \a data and a buffer
* length in \a datalen. Alternatively the backing store will
* allocate its own buffer if \a data is NULL, this memory is
* managed by the backing store.
* The caller must assume nothing about the backing store
* allocated buffers and the storage and *must* be freed by
* calling the release method.
*
* @param[in] url The url is used as the unique primary key for the data.
* @param[in] flags The flags to control how the object is retrived.
@ -90,9 +93,18 @@ struct gui_llcache_table {
* @param[in,out] datalen The length of the \a data retrieved.
* @return NSERROR_OK on success or error code on faliure.
*/
nserror (*fetch)(struct nsurl *url, enum backing_store_flags *flags,
nserror (*fetch)(struct nsurl *url, enum backing_store_flags flags,
uint8_t **data, size_t *datalen);
/**
* release a previously fetched or stored memory object.
*
* @param url The url is used as the unique primary key to invalidate.
* @param[in] flags The flags to control how the object data is released.
* @return NSERROR_OK on success or error code on faliure.
*/
nserror (*release)(struct nsurl *url, enum backing_store_flags flags);
/**
* Invalidate a source object from the backing store.
*
@ -106,19 +118,6 @@ struct gui_llcache_table {
*/
nserror (*invalidate)(struct nsurl *url);
/**
* release a previously fetched or stored memory object.
*
* if the BACKING_STORE_ALLOC flag was used with the fetch or
* store operation for this url the returned storage is
* unreferenced. When the reference count drops to zero the
* storage is released.
*
* @param url The url is used as the unique primary key to invalidate.
* @param[in] flags The flags to control how the object data is released.
* @return NSERROR_OK on success or error code on faliure.
*/
nserror (*release)(struct nsurl *url, enum backing_store_flags flags);
};
extern struct gui_llcache_table* null_llcache_table;

View File

@ -56,7 +56,7 @@
#define DEFAULT_ENTRY_SIZE 16
/** Backing store file format version */
#define CONTROL_VERSION 110
#define CONTROL_VERSION 120
/** Number of milliseconds after a update before control data maintinance is performed */
#define CONTROL_MAINT_TIME 10000
@ -73,21 +73,6 @@
/** Filename of serialised entries */
#define ENTRIES_FNAME "entries"
/**
* flags that indicate what additional information is contained within
* an entry.
*/
enum store_entry_flags {
/** entry is not managing the allocation */
STORE_ENTRY_FLAG_NONE = 0,
/** entry allocation is on heap */
STORE_ENTRY_FLAG_HEAP = 1,
/** entry allocation is mmaped */
STORE_ENTRY_FLAG_MMAP = 2,
/** entry allocation is in small object pool */
STORE_ENTRY_FLAG_SMALL = 4,
};
/**
* The type used to store index values refering to store entries. Care
* must be taken with this type as it is used to build address to
@ -103,20 +88,70 @@ typedef uint16_t entry_index_t;
*/
typedef uint32_t entry_ident_t;
/**
* Entry extension index values.
*/
enum store_entry_elem_idx {
ENTRY_ELEM_DATA = 0, /**< entry element is data */
ENTRY_ELEM_META = 1, /**< entry element is metadata */
ENTRY_ELEM_COUNT = 2, /**< count of elements on an entry */
};
/**
* flags that indicate what additional information is contained within
* an entry element.
*/
enum store_entry_elem_flags {
/** store not managing any allocation on entry */
ENTRY_ELEM_FLAG_NONE = 0,
/** entry data allocation is on heap */
ENTRY_ELEM_FLAG_HEAP = 0x1,
/** entry data allocation is mmaped */
ENTRY_ELEM_FLAG_MMAP = 0x2,
/** entry data allocation is in small object pool */
ENTRY_ELEM_FLAG_SMALL = 0x4,
};
/**
* Backing store entry element.
*
* @note Order is important to avoid excessive structure packing overhead.
*/
struct store_entry_element {
uint32_t size; /**< size of entry element on disc */
union {
struct {
uint8_t* data; /**< data allocated on heap */
uint8_t ref; /**< reference count */
} heap;
struct {
uint8_t* data; /**< data is from an mmapping */
uint8_t ref; /**< reference count */
} map;
struct {
uint16_t block; /**< small object data block */
} small;
};
uint8_t flags; /* extension flags */
};
/**
* Backing store object index entry.
*
* @note Order is important to avoid structure packing overhead.
* An entry in the backing store contains two elements for the actual
* data and the metadata. The two elements are treated identically for
* storage lifetime but as a collective whole for expiration and
* indexing.
*
* @note Order is important to avoid excessive structure packing overhead.
*/
struct store_entry {
int64_t last_used; /**< unix time the entry was last used */
entry_ident_t ident; /**< entry identifier */
uint32_t data_alloc; /**< currently allocated size of data on disc */
uint32_t meta_alloc; /**< currently allocated size of metadata on disc */
uint16_t use_count; /**< number of times this entry has been accessed */
uint16_t flags; /**< entry flags (unused) */
uint16_t data_block; /**< small object data block entry (unused) */
uint16_t meta_block; /**< small object meta block entry (unused) */
/** Entry element (data or meta) specific information */
struct store_entry_element elem[ENTRY_ELEM_COUNT];
};
/**
@ -195,6 +230,18 @@ remove_store_entry(struct store_state *state,
return NSERROR_NOT_FOUND;
}
/* check if the entry has storage already allocated */
if (((state->entries[sei].elem[ENTRY_ELEM_DATA].flags &
(ENTRY_ELEM_FLAG_HEAP | ENTRY_ELEM_FLAG_MMAP)) != 0) ||
((state->entries[sei].elem[ENTRY_ELEM_META].flags &
(ENTRY_ELEM_FLAG_HEAP | ENTRY_ELEM_FLAG_MMAP)) != 0)) {
/* this entry cannot be removed as it has associated
* allocation.
*/
LOG(("attempt to remove entry with in use data"));
return NSERROR_PERMISSION;
}
/* sei is entry to be removed, we swap it to the end of the
* table so there are no gaps and the returned entry is held
* in storage with reasonable lifetime.
@ -204,8 +251,8 @@ remove_store_entry(struct store_state *state,
BS_ENTRY_INDEX(ident, state) = 0;
/* global allocation accounting */
state->total_alloc -= state->entries[sei].data_alloc;
state->total_alloc -= state->entries[sei].meta_alloc;
state->total_alloc -= state->entries[sei].elem[ENTRY_ELEM_DATA].size;
state->total_alloc -= state->entries[sei].elem[ENTRY_ELEM_META].size;
state->last_entry--;
@ -396,6 +443,26 @@ static int compar(const void *va, const void *vb)
const struct store_entry *a = &BS_ENTRY(*(entry_ident_t *)va, storestate);
const struct store_entry *b = &BS_ENTRY(*(entry_ident_t *)vb, storestate);
/* consider the allocation flags - if an entry has an
* allocation it is considered more valuble as it cannot be
* freed.
*/
if ((a->elem[ENTRY_ELEM_DATA].flags == ENTRY_ELEM_FLAG_NONE) &&
(b->elem[ENTRY_ELEM_DATA].flags != ENTRY_ELEM_FLAG_NONE)) {
return -1;
} else if ((a->elem[ENTRY_ELEM_DATA].flags != ENTRY_ELEM_FLAG_NONE) &&
(b->elem[ENTRY_ELEM_DATA].flags == ENTRY_ELEM_FLAG_NONE)) {
return 1;
}
if ((a->elem[ENTRY_ELEM_META].flags == ENTRY_ELEM_FLAG_NONE) &&
(b->elem[ENTRY_ELEM_META].flags != ENTRY_ELEM_FLAG_NONE)) {
return -1;
} else if ((a->elem[ENTRY_ELEM_META].flags != ENTRY_ELEM_FLAG_NONE) &&
(b->elem[ENTRY_ELEM_META].flags == ENTRY_ELEM_FLAG_NONE)) {
return 1;
}
if (a->use_count < b->use_count) {
return -1;
} else if (a->use_count > b->use_count) {
@ -463,8 +530,8 @@ static nserror store_evict(struct store_state *state)
removed = 0;
for (ent = 0; ent < ent_count; ent++) {
removed += BS_ENTRY(elist[ent], state).data_alloc;
removed += BS_ENTRY(elist[ent], state).meta_alloc;
removed += BS_ENTRY(elist[ent], state).elem[ENTRY_ELEM_DATA].size;
removed += BS_ENTRY(elist[ent], state).elem[ENTRY_ELEM_META].size;
ret = unlink_ident(state, elist[ent]);
if (ret != NSERROR_OK) {
@ -591,6 +658,7 @@ get_store_entry(struct store_state *state, nsurl *url, struct store_entry **bse)
sei = BS_ENTRY_INDEX(ident, state);
if (sei == 0) {
LOG(("Failed to find ident 0x%x in index", ident));
return NSERROR_NOT_FOUND;
}
@ -639,7 +707,7 @@ set_store_entry(struct store_state *state,
entry_index_t sei; /* store entry index */
struct store_entry *se;
nserror ret;
bool isrep; /* is the store repalcing an existing entry or not */
struct store_entry_element *elem;
LOG(("url:%s", nsurl_access(url)));
@ -654,52 +722,59 @@ set_store_entry(struct store_state *state,
/* use the url hash as the entry identifier */
ident = nsurl_hash(url);
/* get the entry index from the ident */
sei = BS_ENTRY_INDEX(ident, state);
/** @todo Should this deal with cache eviction? */
if (sei == 0) {
/* allocating the next available entry */
sei = state->last_entry;
state->last_entry++;
BS_ENTRY_INDEX(ident, state) = sei;
isrep = false;
} else {
/* updating or replacing existing entry */
/** @todo should we be checking the entry ident
* matches the url. Thats a collision in the address
* mapping right? and is it important?
*/
isrep = true;
/* clear the new entry */
memset(&state->entries[sei], 0, sizeof(struct store_entry));
}
/** @todo should we be checking the entry ident matches the
* url. Thats a collision in the address mapping right? and is
* it important?
*/
/* the entry */
se = &state->entries[sei];
/* the entry element */
if ((flags & BACKING_STORE_META) != 0) {
elem = &se->elem[ENTRY_ELEM_META];
} else {
elem = &se->elem[ENTRY_ELEM_DATA];
}
/* check if the element has storage already allocated */
if ((elem->flags & (ENTRY_ELEM_FLAG_HEAP | ENTRY_ELEM_FLAG_MMAP)) != 0) {
/* this entry cannot be removed as it has associated
* allocation.
*/
LOG(("attempt to remove entry with in use data"));
return NSERROR_PERMISSION;
}
/* set the common entry data */
se->ident = ident;
se->flags = STORE_ENTRY_FLAG_NONE;
se->use_count = 1;
se->last_used = time(NULL);
/* account for allocation */
if ((flags & BACKING_STORE_META) != 0) {
if (isrep) {
state->total_alloc -= se->meta_alloc;
} else {
se->data_alloc = 0;
}
se->meta_alloc = datalen;
} else {
if (isrep) {
state->total_alloc -= se->data_alloc;
} else {
se->meta_alloc = 0;
}
se->data_alloc = datalen;
}
state->total_alloc += datalen;
/* store the data in the element */
elem->flags |= ENTRY_ELEM_FLAG_HEAP;
elem->heap.data = data;
elem->heap.ref = 1;
/* account for size of entry element */
state->total_alloc -= elem->size;
elem->size = datalen;
state->total_alloc += elem->size;
/* ensure control maintinance scheduled. */
state->entries_dirty = true;
guit->browser->schedule(CONTROL_MAINT_TIME, control_maintinance, state);
*bse = se;
@ -793,8 +868,8 @@ build_entrymap(struct store_state *state)
BS_ENTRY_INDEX(state->entries[eloop].ident, state) = eloop;
/* account for the storage space */
state->total_alloc += state->entries[eloop].data_alloc +
state->entries[eloop].meta_alloc;
state->total_alloc += state->entries[eloop].elem[ENTRY_ELEM_DATA].size;
state->total_alloc += state->entries[eloop].elem[ENTRY_ELEM_META].size;
}
return NSERROR_OK;
@ -1113,7 +1188,7 @@ initialise(const struct llcache_store_parameters *parameters)
LOG(("FS backing store init successful"));
LOG(("path:%s limit:%d hyst:%d addr:%d entries:%d", newstate->path, newstate->limit, newstate->hysteresis, newstate->ident_bits, newstate->entry_bits));
LOG(("Using %d/%d", newstate->total_alloc, newstate->limit));
LOG(("Using %lld/%lld", newstate->total_alloc, newstate->limit));
return NSERROR_OK;
}
@ -1151,6 +1226,8 @@ finalise(void)
/**
* Place an object in the backing store.
*
* takes ownership of the heap block passed in.
*
* @param url The url is used as the unique primary key for the data.
* @param flags The flags to control how the object is stored.
* @param data The objects source data.
@ -1200,6 +1277,22 @@ store(nsurl *url,
return NSERROR_OK;
}
/**
* release any allocation for an entry
*/
static nserror entry_release_alloc(struct store_entry_element *elem)
{
if ((elem->flags & ENTRY_ELEM_FLAG_HEAP) != 0) {
elem->heap.ref--;
if (elem->heap.ref == 0) {
LOG(("freeing %p", elem->heap.data));
free(elem->heap.data);
elem->flags &= ~ENTRY_ELEM_FLAG_HEAP;
}
}
return NSERROR_OK;
}
/**
* Retrive an object from the backing store.
*
@ -1211,12 +1304,13 @@ store(nsurl *url,
*/
static nserror
fetch(nsurl *url,
enum backing_store_flags *flags,
enum backing_store_flags bsflags,
uint8_t **data_out,
size_t *datalen_out)
{
nserror ret;
struct store_entry *bse;
struct store_entry_element *elem;
uint8_t *data;
size_t datalen;
int fd;
@ -1237,36 +1331,53 @@ fetch(nsurl *url,
LOG(("retriving cache file for url:%s", nsurl_access(url)));
fd = store_open(storestate, bse->ident, *flags, O_RDONLY);
fd = store_open(storestate, bse->ident, bsflags, O_RDONLY);
if (fd < 0) {
LOG(("Open failed"));
/** @todo should this invalidate the entry? */
return NSERROR_NOT_FOUND;
}
/* the entry element */
if ((bsflags & BACKING_STORE_META) != 0) {
elem = &bse->elem[ENTRY_ELEM_META];
} else {
elem = &bse->elem[ENTRY_ELEM_DATA];
}
data = *data_out;
datalen = *datalen_out;
/** @todo should this check datalen is sufficient? */
/* need to deal with buffers */
if (data == NULL) {
if (datalen == 0) {
/* caller did not know the files length */
if (((*flags) & BACKING_STORE_META) != 0) {
datalen = bse->meta_alloc;
} else {
datalen = bse->data_alloc;
if ((elem->flags & ENTRY_ELEM_FLAG_HEAP) != 0) {
/* a heap allocation already exists. Return
* that allocation and bump our ref count.
*/
data = elem->heap.data;
elem->heap.ref++;
datalen = elem->size;
LOG(("Using existing heap allocation %p", elem->heap.data));
} else {
datalen = elem->size;
data = malloc(elem->size);
if (data == NULL) {
close(fd);
return NSERROR_NOMEM;
}
}
data = malloc(datalen);
if (data == NULL) {
close(fd);
return NSERROR_NOMEM;
/* store allocated buffer so track ownership */
elem->flags |= ENTRY_ELEM_FLAG_HEAP;
elem->heap.data = data;
elem->heap.ref = 1;
LOG(("Creating new heap allocation %p", elem->heap.data));
}
} else if (datalen == 0) {
/* caller provided a buffer but no length bad parameter */
return NSERROR_BAD_PARAMETER;
}
/** @todo should this check datalen is sufficient */
LOG(("Reading %d bytes into %p from file", datalen, data));
/** @todo this read should be an a loop */
@ -1275,7 +1386,7 @@ fetch(nsurl *url,
LOG(("read returned %d", rd));
close(fd);
if ((*data_out) == NULL) {
free(data);
entry_release_alloc(elem);
}
return NSERROR_NOT_FOUND;
}
@ -1291,6 +1402,41 @@ fetch(nsurl *url,
}
/**
* release a previously fetched or stored memory object.
*
* @param url The url is used as the unique primary key to invalidate.
* @param[in] flags The flags to control how the object data is released.
* @return NSERROR_OK on success or error code on faliure.
*/
static nserror release(nsurl *url, enum backing_store_flags bsflags)
{
nserror ret;
struct store_entry *bse;
struct store_entry_element *elem;
/* check backing store is initialised */
if (storestate == NULL) {
return NSERROR_INIT_FAILED;
}
ret = get_store_entry(storestate, url, &bse);
if (ret != NSERROR_OK) {
LOG(("entry not found"));
return ret;
}
/* the entry element */
if ((bsflags & BACKING_STORE_META) != 0) {
elem = &bse->elem[ENTRY_ELEM_META];
} else {
elem = &bse->elem[ENTRY_ELEM_DATA];
}
return entry_release_alloc(elem);
}
/**
* Invalidate a source object from the backing store.
*
@ -1314,23 +1460,6 @@ invalidate(nsurl *url)
}
/**
* release a previously fetched or stored memory object.
*
* if the BACKING_STORE_ALLOC flag was used with the fetch or
* store operation for this url the returned storage is
* unreferenced. When the reference count drops to zero the
* storage is released.
*
* @param url The url is used as the unique primary key to invalidate.
* @param[in] flags The flags to control how the object data is released.
* @return NSERROR_OK on success or error code on faliure.
*/
static nserror release(nsurl *url, enum backing_store_flags flags)
{
return NSERROR_NOT_FOUND;
}
static struct gui_llcache_table llcache_table = {
.initialise = initialise,
.finalise = finalise,

View File

@ -150,7 +150,6 @@ typedef struct {
/** Current status of objects data */
typedef enum {
LLCACHE_STATE_RAM = 0, /**< source data is stored in RAM only */
LLCACHE_STATE_MMAP, /**< source data is mmaped (implies on disc too) */
LLCACHE_STATE_DISC, /**< source data is stored on disc */
} llcache_store_state;
@ -1076,8 +1075,6 @@ llcache_object_remove_from_list(llcache_object *object, llcache_object **list)
*/
static nserror llcache_persist_retrieve(llcache_object *object)
{
enum backing_store_flags flags = BACKING_STORE_NONE;
/* ensure the source data is present if necessary */
if ((object->source_data != NULL) ||
(object->store_state != LLCACHE_STATE_DISC)) {
@ -1089,7 +1086,7 @@ static nserror llcache_persist_retrieve(llcache_object *object)
/* Source data for the object may be in the persiatant store */
return guit->llcache->fetch(object->url,
&flags,
BACKING_STORE_NONE,
&object->source_data,
&object->source_len);
}
@ -1246,13 +1243,12 @@ llcache_process_metadata(llcache_object *object)
int lnsize;
size_t num_headers;
size_t hloop;
enum backing_store_flags flags = BACKING_STORE_META;
LOG(("Retriving metadata"));
/* attempt to retrieve object metadata from the backing store */
res = guit->llcache->fetch(object->url,
&flags,
BACKING_STORE_META,
&metadata,
&metadatalen);
if (res != NSERROR_OK) {
@ -1273,14 +1269,14 @@ llcache_process_metadata(llcache_object *object)
res = nsurl_create(ln, &metadataurl);
if (res != NSERROR_OK) {
free(metadata);
guit->llcache->release(object->url, BACKING_STORE_META);
return res;
}
if (nsurl_compare(object->url, metadataurl, NSURL_COMPLETE) != true) {
/* backing store returned the wrong object for the
* request. This may occour if the backing store had
* a collision in its stoage method. We cope with this
* a collision in its storage method. We cope with this
* by simply skipping caching of this object.
*/
@ -1290,7 +1286,7 @@ llcache_process_metadata(llcache_object *object)
nsurl_unref(metadataurl);
free(metadata);
guit->llcache->release(object->url, BACKING_STORE_META);
return NSERROR_BAD_URL;
}
@ -1349,12 +1345,12 @@ llcache_process_metadata(llcache_object *object)
res = llcache_fetch_process_header(object, (uint8_t *)ln, lnsize);
if (res != NSERROR_OK) {
free(metadata);
guit->llcache->release(object->url, BACKING_STORE_META);
return res;
}
}
free(metadata);
guit->llcache->release(object->url, BACKING_STORE_META);
/* object stored in backing store */
object->store_state = LLCACHE_STATE_DISC;
@ -1363,7 +1359,8 @@ llcache_process_metadata(llcache_object *object)
format_error:
LOG(("metadata error on line %d\n", line));
free(metadata);
guit->llcache->release(object->url, BACKING_STORE_META);
return NSERROR_INVALID;
}
@ -1454,7 +1451,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
* pull from persistant store.
*/
if (newest == NULL) {
LLCACHE_LOG(("No viable object found in cache"));
LLCACHE_LOG(("No viable object found in llcache"));
error = llcache_object_new(url, &obj);
if (error != NSERROR_OK)
@ -1498,7 +1495,7 @@ llcache_object_retrieve_from_cache(nsurl *url,
/* retrival of source data from persistant store
* failed, destroy cache object and fall though to
* cache miss to re-retch
* cache miss to re-fetch
*/
LLCACHE_LOG(("Persistant retrival failed for %p", newest));
@ -2255,7 +2252,7 @@ write_backing_store(struct llcache_object *object, size_t *written_out)
BACKING_STORE_META,
metadata,
metadatasize);
free(metadata);
guit->llcache->release(object->url, BACKING_STORE_META);
if (ret != NSERROR_OK) {
/* There has been an error putting the metadata in the
* backing store. Ensure the data object is invalidated.
@ -2903,7 +2900,7 @@ void llcache_clean(bool purge)
(object->fetch.fetch == NULL) &&
(object->fetch.outstanding_query == false) &&
(remaining_lifetime <= 0)) {
/* object is stale */
/* object is stale */
LLCACHE_LOG(("discarding stale cacheable object with no users or pending fetches (%p) %s", object, nsurl_access(object->url)));
llcache_object_remove_from_list(object,
@ -2942,7 +2939,8 @@ void llcache_clean(bool purge)
(object->fetch.fetch == NULL) &&
(object->fetch.outstanding_query == false) &&
(object->store_state == LLCACHE_STATE_DISC)) {
free(object->source_data);
guit->llcache->release(object->url, BACKING_STORE_NONE);
object->source_data = NULL;
llcache_size -= object->source_len;
@ -2955,7 +2953,7 @@ void llcache_clean(bool purge)
/* Fresh cacheable objects with no users, no pending fetches
* and pushed to persistant store while the cache exceeds
* the configured size. Efectively just the object metadata.
* the configured size. Efectively just the llcache object metadata.
*/
for (object = llcache->cached_objects;
((limit < llcache_size) && (object != NULL));
@ -2983,7 +2981,7 @@ void llcache_clean(bool purge)
}
/* Fresh cacheable objects with no users or pending fetches
* while the cache exceeds the configured size. These are the
* while the cache exceeds the configured size. These are the
* most valuble objects as replacing them is a full network
* fetch
*/

View File

@ -45,7 +45,7 @@ static nserror store(nsurl *url,
}
static nserror fetch(nsurl *url,
enum backing_store_flags *flags,
enum backing_store_flags flags,
uint8_t **data_out,
size_t *datalen_out)
{