| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119 |
- // Copyright (C) 2004-2025 Artifex Software, Inc.
- //
- // This file is part of MuPDF.
- //
- // MuPDF is free software: you can redistribute it and/or modify it under the
- // terms of the GNU Affero General Public License as published by the Free
- // Software Foundation, either version 3 of the License, or (at your option)
- // any later version.
- //
- // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
- // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
- // details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
- //
- // Alternative licensing terms are available from the licensor.
- // For commercial licensing, see <https://www.artifex.com/> or contact
- // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
- // CA 94129, USA, for further information.
- #include "mupdf/fitz.h"
- #include <assert.h>
- #include <limits.h>
- #include <stdio.h>
- #include <string.h>
- typedef struct fz_item
- {
- void *key;
- fz_storable *val;
- size_t size;
- struct fz_item *next;
- struct fz_item *prev;
- fz_store *store;
- const fz_store_type *type;
- } fz_item;
- /* Every entry in fz_store is protected by the alloc lock */
- struct fz_store
- {
- 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. */
- size_t max;
- size_t size;
- int defer_reap_count;
- int needs_reaping;
- int scavenging;
- };
- void
- fz_new_store_context(fz_context *ctx, size_t 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, NULL);
- }
- 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;
- store->defer_reap_count = 0;
- store->needs_reaping = 0;
- ctx->store = store;
- }
- void *
- fz_keep_storable(fz_context *ctx, const fz_storable *sc)
- {
- /* Explicitly drop const to allow us to use const
- * sanely throughout the code. */
- fz_storable *s = (fz_storable *)sc;
- return fz_keep_imp(ctx, s, &s->refs);
- }
- void *fz_keep_key_storable(fz_context *ctx, const fz_key_storable *sc)
- {
- return fz_keep_storable(ctx, &sc->storable);
- }
- /*
- Entered with FZ_LOCK_ALLOC held.
- Drops FZ_LOCK_ALLOC.
- */
- static void
- do_reap(fz_context *ctx)
- {
- fz_store *store = ctx->store;
- fz_item *item, *prev, *remove;
- if (store == NULL)
- {
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- return;
- }
- fz_assert_lock_held(ctx, FZ_LOCK_ALLOC);
- ctx->store->needs_reaping = 0;
- FZ_LOG_DUMP_STORE(ctx, "Before reaping store:\n");
- /* Reap the items */
- remove = NULL;
- for (item = store->tail; item; item = prev)
- {
- prev = item->prev;
- if (item->type->needs_reap == NULL || item->type->needs_reap(ctx, item->key) == 0)
- continue;
- /* We have to drop it */
- 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;
- /* Remove from the hash table */
- if (item->type->make_hash_key)
- {
- fz_store_hash hash = { NULL };
- hash.drop = item->val->drop;
- if (item->type->make_hash_key(ctx, &hash, item->key))
- fz_hash_remove(ctx, store->hash, &hash);
- }
- /* Store whether to drop this value or not in 'prev' */
- if (item->val->refs > 0)
- (void)Memento_dropRef(item->val);
- item->prev = (item->val->refs > 0 && --item->val->refs == 0) ? item : NULL;
- /* Store it in our removal chain - just singly linked */
- item->next = remove;
- remove = item;
- }
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- /* Now drop the remove chain */
- for (item = remove; item != NULL; item = remove)
- {
- remove = item->next;
- /* Drop a reference to the value (freeing if required) */
- if (item->prev)
- item->val->drop(ctx, item->val);
- /* Always drops the key and drop the item */
- item->type->drop_key(ctx, item->key);
- fz_free(ctx, item);
- }
- FZ_LOG_DUMP_STORE(ctx, "After reaping store:\n");
- }
- void fz_drop_key_storable(fz_context *ctx, const fz_key_storable *sc)
- {
- /* Explicitly drop const to allow us to use const
- * sanely throughout the code. */
- fz_key_storable *s = (fz_key_storable *)sc;
- int drop;
- int unlock = 1;
- if (s == NULL)
- return;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- assert(s->storable.refs != 0);
- if (s->storable.refs > 0)
- {
- (void)Memento_dropRef(s);
- drop = --s->storable.refs == 0;
- if (!drop && s->storable.refs == s->store_key_refs)
- {
- if (ctx->store->defer_reap_count > 0)
- {
- ctx->store->needs_reaping = 1;
- }
- else
- {
- do_reap(ctx);
- unlock = 0;
- }
- }
- }
- else
- drop = 0;
- if (unlock)
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- /*
- 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.
- */
- if (drop)
- s->storable.drop(ctx, &s->storable);
- }
- void *fz_keep_key_storable_key(fz_context *ctx, const fz_key_storable *sc)
- {
- /* Explicitly drop const to allow us to use const
- * sanely throughout the code. */
- fz_key_storable *s = (fz_key_storable *)sc;
- if (s == NULL)
- return NULL;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- if (s->storable.refs > 0)
- {
- (void)Memento_takeRef(s);
- ++s->storable.refs;
- ++s->store_key_refs;
- }
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- return s;
- }
- void fz_drop_key_storable_key(fz_context *ctx, const fz_key_storable *sc)
- {
- /* Explicitly drop const to allow us to use const
- * sanely throughout the code. */
- fz_key_storable *s = (fz_key_storable *)sc;
- int drop;
- if (s == NULL)
- return;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- assert(s->store_key_refs > 0 && s->storable.refs >= s->store_key_refs);
- (void)Memento_dropRef(s);
- drop = --s->storable.refs == 0;
- --s->store_key_refs;
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- /*
- 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.
- */
- if (drop)
- s->storable.drop(ctx, &s->storable);
- }
- 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) */
- if (item->val->refs > 0)
- (void)Memento_dropRef(item->val);
- 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.drop = item->val->drop;
- if (item->type->make_hash_key(ctx, &hash, item->key))
- fz_hash_remove(ctx, store->hash, &hash);
- }
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- if (drop)
- item->val->drop(ctx, item->val);
- /* Always drops the key and drop the item */
- item->type->drop_key(ctx, item->key);
- fz_free(ctx, item);
- fz_lock(ctx, FZ_LOCK_ALLOC);
- }
- static size_t
- ensure_space(fz_context *ctx, size_t tofree)
- {
- fz_item *item, *prev;
- size_t count;
- fz_store *store = ctx->store;
- fz_item *to_be_freed = NULL;
- 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;
- }
- /* Now move all the items to be freed onto 'to_be_freed' */
- count = 0;
- for (item = store->tail; item; item = prev)
- {
- prev = item->prev;
- if (item->val->refs != 1)
- continue;
- 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;
- /* Remove from the hash table */
- if (item->type->make_hash_key)
- {
- fz_store_hash hash = { NULL };
- hash.drop = item->val->drop;
- if (item->type->make_hash_key(ctx, &hash, item->key))
- fz_hash_remove(ctx, store->hash, &hash);
- }
- /* Link into to_be_freed */
- item->next = to_be_freed;
- to_be_freed = item;
- count += item->size;
- if (count >= tofree)
- break;
- }
- /* Now we can safely drop the lock and free our pending items. These
- * have all been removed from both the store list, and the hash table,
- * so they can't be 'found' by anyone else in the meantime. */
- while (to_be_freed)
- {
- int drop;
- item = to_be_freed;
- to_be_freed = to_be_freed->next;
- /* Drop a reference to the value (freeing if required) */
- if (item->val->refs > 0)
- (void)Memento_dropRef(item->val);
- drop = (item->val->refs > 0 && --item->val->refs == 0);
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- if (drop)
- item->val->drop(ctx, item->val);
- /* Always drops the key and drop the item */
- item->type->drop_key(ctx, item->key);
- fz_free(ctx, item);
- fz_lock(ctx, FZ_LOCK_ALLOC);
- }
- 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_, size_t itemsize, const fz_store_type *type)
- {
- fz_item *item = NULL;
- size_t size;
- fz_storable *val = (fz_storable *)val_;
- fz_store *store = ctx->store;
- fz_store_hash hash = { NULL };
- int use_hash = 0;
- if (!store)
- 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. */
- item = Memento_label(fz_malloc_no_throw(ctx, sizeof (fz_item)), "fz_item");
- if (!item)
- return NULL;
- memset(item, 0, sizeof (fz_item));
- if (type->make_hash_key)
- {
- hash.drop = val->drop;
- use_hash = type->make_hash_key(ctx, &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 = NULL;
- fz_try(ctx)
- {
- /* May drop and retake the lock */
- existing = fz_hash_insert(ctx, store->hash, &hash, item);
- }
- 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. */
- fz_warn(ctx, "found duplicate %s in the store", type->name);
- touch(store, existing);
- if (existing->val->refs > 0)
- {
- (void)Memento_takeRef(existing->val);
- 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)
- {
- (void)Memento_takeRef(val);
- val->refs++;
- }
- /* If we haven't got an infinite store, check for space within it */
- if (store->max != FZ_STORE_UNLIMITED)
- {
- /* FIXME: Overflow? */
- size = store->size + itemsize;
- if (size > store->max)
- {
- FZ_LOG_STORE(ctx, "Store size exceeded: item=%zu, size=%zu, max=%zu\n",
- itemsize, store->size, store->max);
- while (size > store->max)
- {
- size_t saved;
- /* First, do any outstanding reaping, even if defer_reap_count > 0 */
- if (store->needs_reaping)
- {
- do_reap(ctx); /* Drops alloc lock */
- fz_lock(ctx, FZ_LOCK_ALLOC);
- }
- size = store->size + itemsize;
- if (size <= store->max)
- break;
- /* ensure_space may drop, then retake the lock */
- saved = ensure_space(ctx, size - store->max);
- size -= saved;
- if (saved == 0)
- {
- /* Failed to free any space. */
- /* We used to 'unstore' it here, but that's wrong.
- * If we've already spent the memory to malloc it
- * then not putting it in the store just means that
- * a resource used multiple times will just be malloced
- * again. Better to put it in the store, have the
- * store account for it, and for it to potentially be reused.
- * When the caller drops the reference to it, it can then
- * be dropped from the store on the next attempt to store
- * anything else. */
- break;
- }
- }
- FZ_LOG_DUMP_STORE(ctx, "After eviction:\n");
- }
- }
- 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_drop_fn *drop, void *key, const 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.drop = drop;
- use_hash = type->make_hash_key(ctx, &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->drop == drop && !type->cmp_key(ctx, 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)
- {
- (void)Memento_takeRef(item->val);
- 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_drop_fn *drop, void *key, const fz_store_type *type)
- {
- fz_item *item;
- fz_store *store = ctx->store;
- int dodrop;
- fz_store_hash hash = { NULL };
- int use_hash = 0;
- if (type->make_hash_key)
- {
- hash.drop = drop;
- use_hash = type->make_hash_key(ctx, &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->drop == drop && !type->cmp_key(ctx, 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;
- }
- if (item->val->refs > 0)
- (void)Memento_dropRef(item->val);
- dodrop = (item->val->refs > 0 && --item->val->refs == 0);
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- if (dodrop)
- item->val->drop(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;
- return fz_keep_imp(ctx, ctx->store, &ctx->store->refs);
- }
- void
- fz_drop_store_context(fz_context *ctx)
- {
- if (!ctx)
- return;
- if (fz_drop_imp(ctx, ctx->store, &ctx->store->refs))
- {
- fz_empty_store(ctx);
- fz_drop_hash_table(ctx, ctx->store->hash);
- fz_free(ctx, ctx->store);
- ctx->store = NULL;
- }
- }
- static void
- fz_debug_store_item(fz_context *ctx, void *state, void *key_, int keylen, void *item_)
- {
- unsigned char *key = key_;
- fz_item *item = item_;
- int i;
- char buf[256];
- fz_output *out = (fz_output *)state;
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- item->type->format_key(ctx, buf, sizeof buf, item->key);
- fz_lock(ctx, FZ_LOCK_ALLOC);
- fz_write_printf(ctx, out, "STORE\thash[");
- for (i=0; i < keylen; ++i)
- fz_write_printf(ctx, out,"%02x", key[i]);
- fz_write_printf(ctx, out, "][refs=%d][size=%d] key=%s val=%p\n", item->val->refs, (int)item->size, buf, (void *)item->val);
- }
- static void
- fz_debug_store_locked(fz_context *ctx, fz_output *out)
- {
- fz_item *item, *next;
- char buf[256];
- fz_store *store = ctx->store;
- size_t list_total = 0;
- fz_write_printf(ctx, out, "STORE\t-- resource store contents --\n");
- for (item = store->head; item; item = next)
- {
- next = item->next;
- if (next)
- {
- (void)Memento_takeRef(next->val);
- next->val->refs++;
- }
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- item->type->format_key(ctx, buf, sizeof buf, item->key);
- fz_lock(ctx, FZ_LOCK_ALLOC);
- fz_write_printf(ctx, out, "STORE\tstore[*][refs=%d][size=%d] key=%s val=%p\n",
- item->val->refs, (int)item->size, buf, (void *)item->val);
- list_total += item->size;
- if (next)
- {
- (void)Memento_dropRef(next->val);
- next->val->refs--;
- }
- }
- fz_write_printf(ctx, out, "STORE\t-- resource store hash contents --\n");
- fz_hash_for_each(ctx, store->hash, out, fz_debug_store_item);
- fz_write_printf(ctx, out, "STORE\t-- end --\n");
- fz_write_printf(ctx, out, "STORE\tmax=%zu, size=%zu, actual size=%zu\n", store->max, store->size, list_total);
- }
- void
- fz_debug_store(fz_context *ctx, fz_output *out)
- {
- fz_lock(ctx, FZ_LOCK_ALLOC);
- fz_debug_store_locked(ctx, out);
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- }
- /*
- Consider if we have blocks of the following sizes in the store, from oldest
- to newest:
- A 32
- B 64
- C 128
- D 256
- Further suppose we need to free 97 bytes. Naively freeing blocks until we have
- freed enough would mean we'd free A, B and C, when we could have freed just C.
- We are forced into an n^2 algorithm by the need to drop the lock as part of the
- eviction, so we might as well embrace it and go for a solution that properly
- drops just C.
- The algorithm used is to scan the list of blocks from oldest to newest, counting
- how many bytes we can free in the blocks we pass. We stop this scan when we have
- found enough blocks. We then free the largest block. This releases the lock
- momentarily, which means we have to start the scan process all over again, so
- we repeat. This guarantees we only evict a minimum of blocks, but does mean we
- scan more blocks than we'd ideally like.
- */
- static int
- scavenge(fz_context *ctx, size_t tofree)
- {
- fz_store *store = ctx->store;
- size_t freed = 0;
- fz_item *item;
- if (store->scavenging)
- return 0;
- store->scavenging = 1;
- do
- {
- /* Count through a suffix of objects in the store until
- * we find enough to give us what we need to evict. */
- size_t suffix_size = 0;
- fz_item *largest = NULL;
- for (item = store->tail; item; item = item->prev)
- {
- if (item->val->refs == 1 && (item->val->droppable == NULL || item->val->droppable(ctx, item->val)))
- {
- /* This one is evictable */
- suffix_size += item->size;
- if (largest == NULL || item->size > largest->size)
- largest = item;
- if (suffix_size >= tofree - freed)
- break;
- }
- }
- /* If there are no evictable blocks, we can't find anything to free. */
- if (largest == NULL)
- break;
- /* Free largest. */
- if (freed == 0) {
- FZ_LOG_DUMP_STORE(ctx, "Before scavenge:\n");
- }
- freed += largest->size;
- evict(ctx, largest); /* Drops then retakes lock */
- }
- while (freed < tofree);
- if (freed != 0) {
- FZ_LOG_DUMP_STORE(ctx, "After scavenge:\n");
- }
- store->scavenging = 0;
- /* Success is managing to evict any blocks */
- return freed != 0;
- }
- void
- fz_drop_storable(fz_context *ctx, const fz_storable *sc)
- {
- /* Explicitly drop const to allow us to use const
- * sanely throughout the code. */
- fz_storable *s = (fz_storable *)sc;
- int num;
- if (s == NULL)
- return;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- /* Drop the ref, and leave num as being the number of
- * refs left (-1 meaning, "statically allocated"). */
- if (s->refs > 0)
- {
- (void)Memento_dropIntRef(s);
- num = --s->refs;
- }
- else
- num = -1;
- /* If we have just 1 ref left, it's possible that
- * this ref is held by the store. If the store is
- * oversized, we ought to throw any such references
- * away to try to bring the store down to a "legal"
- * size. Run a scavenge to check for this case. */
- if (ctx->store->max != FZ_STORE_UNLIMITED)
- if (num == 1 && ctx->store->size > ctx->store->max)
- scavenge(ctx, ctx->store->size - ctx->store->max);
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- /* If we have no references to an object left, 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.
- */
- if (num == 0)
- s->drop(ctx, s);
- }
- int fz_store_scavenge_external(fz_context *ctx, size_t size, int *phase)
- {
- int ret;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- ret = fz_store_scavenge(ctx, size, phase);
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- return ret;
- }
- int fz_store_scavenge(fz_context *ctx, size_t size, int *phase)
- {
- fz_store *store;
- size_t max;
- store = ctx->store;
- if (store == NULL)
- return 0;
- #ifdef DEBUG_SCAVENGING
- fz_write_printf(ctx, fz_stdout(ctx), "Scavenging: store=%zu size=%zu phase=%d\n", store->size, size, *phase);
- fz_debug_store_locked(ctx, fz_stdout(ctx));
- Memento_stats();
- #endif
- do
- {
- size_t 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 > SIZE_MAX - store->size)
- tofree = SIZE_MAX - max;
- else if (size + store->size > max)
- continue;
- else
- tofree = size + store->size - max;
- if (scavenge(ctx, tofree))
- {
- #ifdef DEBUG_SCAVENGING
- fz_write_printf(ctx, fz_stdout(ctx), "scavenged: store=%zu\n", store->size);
- fz_debug_store(ctx, fz_stdout(ctx));
- Memento_stats();
- #endif
- return 1;
- }
- }
- while (max > 0);
- #ifdef DEBUG_SCAVENGING
- fz_write_printf(ctx, fz_stdout(ctx), "scavenging failed\n");
- fz_debug_store(ctx, fz_stdout(ctx));
- Memento_listBlocks();
- #endif
- return 0;
- }
- int
- fz_shrink_store(fz_context *ctx, unsigned int percent)
- {
- int success;
- fz_store *store;
- size_t new_size;
- if (percent >= 100)
- return 1;
- store = ctx->store;
- if (store == NULL)
- return 0;
- #ifdef DEBUG_SCAVENGING
- fz_write_printf(ctx, fz_stdout(ctx), "fz_shrink_store: %zu\n", store->size/(1024*1024));
- #endif
- fz_lock(ctx, FZ_LOCK_ALLOC);
- new_size = (size_t)(((uint64_t)store->size * percent) / 100);
- if (store->size > new_size)
- scavenge(ctx, store->size - new_size);
- success = (store->size <= new_size) ? 1 : 0;
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- #ifdef DEBUG_SCAVENGING
- fz_write_printf(ctx, fz_stdout(ctx), "fz_shrink_store after: %zu\n", store->size/(1024*1024));
- #endif
- return success;
- }
- void fz_filter_store(fz_context *ctx, fz_store_filter_fn *fn, void *arg, const fz_store_type *type)
- {
- fz_store *store;
- fz_item *item, *prev, *remove;
- store = ctx->store;
- if (store == NULL)
- return;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- /* Filter the items */
- remove = NULL;
- for (item = store->tail; item; item = prev)
- {
- prev = item->prev;
- if (item->type != type)
- continue;
- if (fn(ctx, arg, item->key) == 0)
- continue;
- /* We have to drop it */
- 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;
- /* Remove from the hash table */
- if (item->type->make_hash_key)
- {
- fz_store_hash hash = { NULL };
- hash.drop = item->val->drop;
- if (item->type->make_hash_key(ctx, &hash, item->key))
- fz_hash_remove(ctx, store->hash, &hash);
- }
- /* Store whether to drop this value or not in 'prev' */
- if (item->val->refs > 0)
- (void)Memento_dropRef(item->val);
- item->prev = (item->val->refs > 0 && --item->val->refs == 0) ? item : NULL;
- /* Store it in our removal chain - just singly linked */
- item->next = remove;
- remove = item;
- }
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- /* Now drop the remove chain */
- for (item = remove; item != NULL; item = remove)
- {
- remove = item->next;
- /* Drop a reference to the value (freeing if required) */
- if (item->prev) /* See above for our abuse of prev here */
- item->val->drop(ctx, item->val);
- /* Always drops the key and drop the item */
- item->type->drop_key(ctx, item->key);
- fz_free(ctx, item);
- }
- }
- void fz_defer_reap_start(fz_context *ctx)
- {
- if (ctx->store == NULL)
- return;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- ctx->store->defer_reap_count++;
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- }
- void fz_defer_reap_end(fz_context *ctx)
- {
- int reap;
- if (ctx->store == NULL)
- return;
- fz_lock(ctx, FZ_LOCK_ALLOC);
- --ctx->store->defer_reap_count;
- reap = ctx->store->defer_reap_count == 0 && ctx->store->needs_reaping;
- if (reap)
- do_reap(ctx); /* Drops FZ_LOCK_ALLOC */
- else
- fz_unlock(ctx, FZ_LOCK_ALLOC);
- }
- #ifdef ENABLE_STORE_LOGGING
- void fz_log_dump_store(fz_context *ctx, const char *fmt, ...)
- {
- fz_output *out;
- va_list args;
- va_start(args, fmt);
- out = fz_new_log_for_module(ctx, "STORE");
- fz_write_vprintf(ctx, out, fmt, args);
- va_end(args);
- fz_debug_store(ctx, out);
- fz_write_printf(ctx, out, "STORE\tEND\n");
- fz_close_output(ctx, out);
- fz_drop_output(ctx, out);
- }
- #endif
|