diff options
Diffstat (limited to 'fitz/res_store.c')
-rw-r--r-- | fitz/res_store.c | 638 |
1 files changed, 0 insertions, 638 deletions
diff --git a/fitz/res_store.c b/fitz/res_store.c deleted file mode 100644 index 609f10dc..00000000 --- a/fitz/res_store.c +++ /dev/null @@ -1,638 +0,0 @@ -#include "mupdf/fitz.h" - -typedef struct fz_item_s fz_item; - -struct fz_item_s -{ - void *key; - fz_storable *val; - unsigned int size; - fz_item *next; - fz_item *prev; - fz_store *store; - fz_store_type *type; -}; - -struct fz_store_s -{ - int refs; - - /* Every item in the store is kept in a doubly linked list, ordered - * by usage (so LRU entries are at the end). */ - fz_item *head; - fz_item *tail; - - /* We have a hash table that allows to quickly find a subset of the - * entries (those whose keys are indirect objects). */ - fz_hash_table *hash; - - /* We keep track of the size of the store, and keep it below max. */ - unsigned int max; - unsigned int size; -}; - -void -fz_new_store_context(fz_context *ctx, unsigned int max) -{ - fz_store *store; - store = fz_malloc_struct(ctx, fz_store); - fz_try(ctx) - { - store->hash = fz_new_hash_table(ctx, 4096, sizeof(fz_store_hash), FZ_LOCK_ALLOC); - } - fz_catch(ctx) - { - fz_free(ctx, store); - fz_rethrow(ctx); - } - store->refs = 1; - store->head = NULL; - store->tail = NULL; - store->size = 0; - store->max = max; - ctx->store = store; -} - -void * -fz_keep_storable(fz_context *ctx, fz_storable *s) -{ - if (s == NULL) - return NULL; - fz_lock(ctx, FZ_LOCK_ALLOC); - if (s->refs > 0) - s->refs++; - fz_unlock(ctx, FZ_LOCK_ALLOC); - return s; -} - -void -fz_drop_storable(fz_context *ctx, fz_storable *s) -{ - int do_free = 0; - - if (s == NULL) - return; - fz_lock(ctx, FZ_LOCK_ALLOC); - if (s->refs < 0) - { - /* It's a static object. Dropping does nothing. */ - } - else if (--s->refs == 0) - { - /* If we are dropping the last reference to an object, then - * it cannot possibly be in the store (as the store always - * keeps a ref to everything in it, and doesn't drop via - * this method. So we can simply drop the storable object - * itself without any operations on the fz_store. */ - do_free = 1; - } - fz_unlock(ctx, FZ_LOCK_ALLOC); - if (do_free) - s->free(ctx, s); -} - -static void -evict(fz_context *ctx, fz_item *item) -{ - fz_store *store = ctx->store; - int drop; - - store->size -= item->size; - /* Unlink from the linked list */ - if (item->next) - item->next->prev = item->prev; - else - store->tail = item->prev; - if (item->prev) - item->prev->next = item->next; - else - store->head = item->next; - /* Drop a reference to the value (freeing if required) */ - drop = (item->val->refs > 0 && --item->val->refs == 0); - /* Remove from the hash table */ - if (item->type->make_hash_key) - { - fz_store_hash hash = { NULL }; - hash.free = item->val->free; - if (item->type->make_hash_key(&hash, item->key)) - fz_hash_remove(ctx, store->hash, &hash); - } - fz_unlock(ctx, FZ_LOCK_ALLOC); - if (drop) - item->val->free(ctx, item->val); - /* Always drops the key and free the item */ - item->type->drop_key(ctx, item->key); - fz_free(ctx, item); - fz_lock(ctx, FZ_LOCK_ALLOC); -} - -static int -ensure_space(fz_context *ctx, unsigned int tofree) -{ - fz_item *item, *prev; - unsigned int count; - fz_store *store = ctx->store; - - fz_assert_lock_held(ctx, FZ_LOCK_ALLOC); - - /* First check that we *can* free tofree; if not, we'd rather not - * cache this. */ - count = 0; - for (item = store->tail; item; item = item->prev) - { - if (item->val->refs == 1) - { - count += item->size; - if (count >= tofree) - break; - } - } - - /* If we ran out of items to search, then we can never free enough */ - if (item == NULL) - { - return 0; - } - - /* Actually free the items */ - count = 0; - for (item = store->tail; item; item = prev) - { - prev = item->prev; - if (item->val->refs == 1) - { - /* Free this item. Evict has to drop the lock to - * manage that, which could cause prev to be removed - * in the meantime. To avoid that we bump its reference - * count here. This may cause another simultaneous - * evict process to fail to make enough space as prev is - * pinned - but that will only happen if we're near to - * the limit anyway, and it will only cause something to - * not be cached. */ - count += item->size; - if (prev) - prev->val->refs++; - evict(ctx, item); /* Drops then retakes lock */ - /* So the store has 1 reference to prev, as do we, so - * no other evict process can have thrown prev away in - * the meantime. So we are safe to just decrement its - * reference count here. */ - if (prev) - --prev->val->refs; - - if (count >= tofree) - return count; - } - } - - return count; -} - -static void -touch(fz_store *store, fz_item *item) -{ - if (item->next != item) - { - /* Already in the list - unlink it */ - if (item->next) - item->next->prev = item->prev; - else - store->tail = item->prev; - if (item->prev) - item->prev->next = item->next; - else - store->head = item->next; - } - /* Now relink it at the start of the LRU chain */ - item->next = store->head; - if (item->next) - item->next->prev = item; - else - store->tail = item; - store->head = item; - item->prev = NULL; -} - -void * -fz_store_item(fz_context *ctx, void *key, void *val_, unsigned int itemsize, fz_store_type *type) -{ - fz_item *item = NULL; - unsigned int size; - fz_storable *val = (fz_storable *)val_; - fz_store *store = ctx->store; - fz_store_hash hash = { NULL }; - int use_hash = 0; - unsigned pos; - - if (!store) - return NULL; - - fz_var(item); - - if (store->max != FZ_STORE_UNLIMITED && store->max < itemsize) - { - /* Our item would take up more room than we can ever - * possibly have in the store. Just give up now. */ - return NULL; - } - - /* If we fail for any reason, we swallow the exception and continue. - * All that the above program will see is that we failed to store - * the item. */ - fz_try(ctx) - { - item = fz_malloc_struct(ctx, fz_item); - } - fz_catch(ctx) - { - return NULL; - } - - if (type->make_hash_key) - { - hash.free = val->free; - use_hash = type->make_hash_key(&hash, key); - } - - type->keep_key(ctx, key); - fz_lock(ctx, FZ_LOCK_ALLOC); - - /* Fill out the item. To start with, we always set item->next == item - * and item->prev == item. This is so that we can spot items that have - * been put into the hash table without having made it into the linked - * list yet. */ - item->key = key; - item->val = val; - item->size = itemsize; - item->next = item; - item->prev = item; - item->type = type; - - /* If we can index it fast, put it into the hash table. This serves - * to check whether we have one there already. */ - if (use_hash) - { - fz_item *existing; - - fz_try(ctx) - { - /* May drop and retake the lock */ - existing = fz_hash_insert_with_pos(ctx, store->hash, &hash, item, &pos); - } - fz_catch(ctx) - { - /* Any error here means that item never made it into the - * hash - so no one else can have a reference. */ - fz_unlock(ctx, FZ_LOCK_ALLOC); - fz_free(ctx, item); - type->drop_key(ctx, key); - return NULL; - } - if (existing) - { - /* There was one there already! Take a new reference - * to the existing one, and drop our current one. */ - touch(store, existing); - if (existing->val->refs > 0) - existing->val->refs++; - fz_unlock(ctx, FZ_LOCK_ALLOC); - fz_free(ctx, item); - type->drop_key(ctx, key); - return existing->val; - } - } - /* Now bump the ref */ - if (val->refs > 0) - val->refs++; - /* If we haven't got an infinite store, check for space within it */ - if (store->max != FZ_STORE_UNLIMITED) - { - size = store->size + itemsize; - while (size > store->max) - { - /* ensure_space may drop, then retake the lock */ - int saved = ensure_space(ctx, size - store->max); - if (saved == 0) - { - /* Failed to free any space. */ - /* If we are using the hash table, then we've already - * inserted item - remove it. */ - if (use_hash) - { - /* If someone else has already picked up a reference - * to item, then we cannot remove it. Leave it in the - * store, and we'll live with being over budget. We - * know this is the case, if it's in the linked list. */ - if (item->next != item) - break; - fz_hash_remove_fast(ctx, store->hash, &hash, pos); - } - fz_unlock(ctx, FZ_LOCK_ALLOC); - fz_free(ctx, item); - type->drop_key(ctx, key); - if (val->refs > 0) - val->refs--; - return NULL; - } - size -= saved; - } - } - store->size += itemsize; - - /* Regardless of whether it's indexed, it goes into the linked list */ - touch(store, item); - fz_unlock(ctx, FZ_LOCK_ALLOC); - - return NULL; -} - -void * -fz_find_item(fz_context *ctx, fz_store_free_fn *free, void *key, fz_store_type *type) -{ - fz_item *item; - fz_store *store = ctx->store; - fz_store_hash hash = { NULL }; - int use_hash = 0; - - if (!store) - return NULL; - - if (!key) - return NULL; - - if (type->make_hash_key) - { - hash.free = free; - use_hash = type->make_hash_key(&hash, key); - } - - fz_lock(ctx, FZ_LOCK_ALLOC); - if (use_hash) - { - /* We can find objects keyed on indirected objects quickly */ - item = fz_hash_find(ctx, store->hash, &hash); - } - else - { - /* Others we have to hunt for slowly */ - for (item = store->head; item; item = item->next) - { - if (item->val->free == free && !type->cmp_key(item->key, key)) - break; - } - } - if (item) - { - /* LRU the block. This also serves to ensure that any item - * picked up from the hash before it has made it into the - * linked list does not get whipped out again due to the - * store being full. */ - touch(store, item); - /* And bump the refcount before returning */ - if (item->val->refs > 0) - item->val->refs++; - fz_unlock(ctx, FZ_LOCK_ALLOC); - return (void *)item->val; - } - fz_unlock(ctx, FZ_LOCK_ALLOC); - - return NULL; -} - -void -fz_remove_item(fz_context *ctx, fz_store_free_fn *free, void *key, fz_store_type *type) -{ - fz_item *item; - fz_store *store = ctx->store; - int drop; - fz_store_hash hash = { NULL }; - int use_hash = 0; - - if (type->make_hash_key) - { - hash.free = free; - use_hash = type->make_hash_key(&hash, key); - } - - fz_lock(ctx, FZ_LOCK_ALLOC); - if (use_hash) - { - /* We can find objects keyed on indirect objects quickly */ - item = fz_hash_find(ctx, store->hash, &hash); - if (item) - fz_hash_remove(ctx, store->hash, &hash); - } - else - { - /* Others we have to hunt for slowly */ - for (item = store->head; item; item = item->next) - if (item->val->free == free && !type->cmp_key(item->key, key)) - break; - } - if (item) - { - /* Momentarily things can be in the hash table without being - * in the list. Don't attempt to unlink these. We indicate - * such items by setting item->next == item. */ - if (item->next != item) - { - if (item->next) - item->next->prev = item->prev; - else - store->tail = item->prev; - if (item->prev) - item->prev->next = item->next; - else - store->head = item->next; - } - drop = (item->val->refs > 0 && --item->val->refs == 0); - fz_unlock(ctx, FZ_LOCK_ALLOC); - if (drop) - item->val->free(ctx, item->val); - type->drop_key(ctx, item->key); - fz_free(ctx, item); - } - else - fz_unlock(ctx, FZ_LOCK_ALLOC); -} - -void -fz_empty_store(fz_context *ctx) -{ - fz_store *store = ctx->store; - - if (store == NULL) - return; - - fz_lock(ctx, FZ_LOCK_ALLOC); - /* Run through all the items in the store */ - while (store->head) - { - evict(ctx, store->head); /* Drops then retakes lock */ - } - fz_unlock(ctx, FZ_LOCK_ALLOC); -} - -fz_store * -fz_keep_store_context(fz_context *ctx) -{ - if (ctx == NULL || ctx->store == NULL) - return NULL; - fz_lock(ctx, FZ_LOCK_ALLOC); - ctx->store->refs++; - fz_unlock(ctx, FZ_LOCK_ALLOC); - return ctx->store; -} - -void -fz_drop_store_context(fz_context *ctx) -{ - int refs; - if (ctx == NULL || ctx->store == NULL) - return; - fz_lock(ctx, FZ_LOCK_ALLOC); - refs = --ctx->store->refs; - fz_unlock(ctx, FZ_LOCK_ALLOC); - if (refs != 0) - return; - - fz_empty_store(ctx); - fz_free_hash(ctx, ctx->store->hash); - fz_free(ctx, ctx->store); - ctx->store = NULL; -} - -#ifndef NDEBUG -static void -print_item(FILE *out, void *item_) -{ - fz_item *item = (fz_item *)item_; - fprintf(out, " val=%p item=%p\n", item->val, item); - fflush(out); -} - -void -fz_print_store_locked(fz_context *ctx, FILE *out) -{ - fz_item *item, *next; - fz_store *store = ctx->store; - - fprintf(out, "-- resource store contents --\n"); - fflush(out); - - for (item = store->head; item; item = next) - { - next = item->next; - if (next) - next->val->refs++; - fprintf(out, "store[*][refs=%d][size=%d] ", item->val->refs, item->size); - fz_unlock(ctx, FZ_LOCK_ALLOC); - item->type->debug(out, item->key); - fprintf(out, " = %p\n", item->val); - fflush(out); - fz_lock(ctx, FZ_LOCK_ALLOC); - if (next) - next->val->refs--; - } - fprintf(out, "-- resource store hash contents --\n"); - fz_print_hash_details(ctx, out, store->hash, print_item); - fprintf(out, "-- end --\n"); - fflush(out); -} - -void -fz_print_store(fz_context *ctx, FILE *out) -{ - fz_lock(ctx, FZ_LOCK_ALLOC); - fz_print_store_locked(ctx, out); - fz_unlock(ctx, FZ_LOCK_ALLOC); -} -#endif - -/* This is now an n^2 algorithm - not ideal, but it'll only be bad if we are - * actually managing to scavenge lots of blocks back. */ -static int -scavenge(fz_context *ctx, unsigned int tofree) -{ - fz_store *store = ctx->store; - unsigned int count = 0; - fz_item *item, *prev; - - /* Free the items */ - for (item = store->tail; item; item = prev) - { - prev = item->prev; - if (item->val->refs == 1) - { - /* Free this item */ - count += item->size; - evict(ctx, item); /* Drops then retakes lock */ - - if (count >= tofree) - break; - - /* Have to restart search again, as prev may no longer - * be valid due to release of lock in evict. */ - prev = store->tail; - } - } - /* Success is managing to evict any blocks */ - return count != 0; -} - -int fz_store_scavenge(fz_context *ctx, unsigned int size, int *phase) -{ - fz_store *store; - unsigned int max; - - if (ctx == NULL) - return 0; - store = ctx->store; - if (store == NULL) - return 0; - -#ifdef DEBUG_SCAVENGING - printf("Scavenging: store=%d size=%d phase=%d\n", store->size, size, *phase); - fz_print_store_locked(ctx, stderr); - Memento_stats(); -#endif - do - { - unsigned int tofree; - - /* Calculate 'max' as the maximum size of the store for this phase */ - if (*phase >= 16) - max = 0; - else if (store->max != FZ_STORE_UNLIMITED) - max = store->max / 16 * (16 - *phase); - else - max = store->size / (16 - *phase) * (15 - *phase); - (*phase)++; - - /* Slightly baroque calculations to avoid overflow */ - if (size > UINT_MAX - store->size) - tofree = UINT_MAX - max; - else if (size + store->size > max) - continue; - else - tofree = size + store->size - max; - - if (scavenge(ctx, tofree)) - { -#ifdef DEBUG_SCAVENGING - printf("scavenged: store=%d\n", store->size); - fz_print_store(ctx, stderr); - Memento_stats(); -#endif - return 1; - } - } - while (max > 0); - -#ifdef DEBUG_SCAVENGING - printf("scavenging failed\n"); - fz_print_store(ctx, stderr); - Memento_listBlocks(); -#endif - return 0; -} |