summaryrefslogtreecommitdiff
path: root/draw
diff options
context:
space:
mode:
authorRobin Watts <robin.watts@artifex.com>2012-02-08 11:13:46 +0000
committerRobin Watts <robin.watts@artifex.com>2012-02-08 20:07:47 +0000
commitc07d0087384a45db43b6efd2f6808b31d2e60c59 (patch)
tree3685be21c279af4c99c85be65ba387893429f82e /draw
parent3e4cd0765ca1080d2b23c83076cc248310b5b2a2 (diff)
downloadmupdf-c07d0087384a45db43b6efd2f6808b31d2e60c59.tar.xz
Lock reworking.
This is a significant change to the use of locks in MuPDF. Previously, the user had the option of passing us lock/unlock functions for a single mutex as part of the allocation struct. Now we remove these entries from the allocation struct, and make a separate 'locks' struct. This enables people to use fz_alloc_default with locking. If multithreaded operation is required, then the user is required to create FZ_LOCK_MAX mutexes, which will be locked or unlocked by MuPDF calling the lock/unlock functions within the new fz_locks_context structure passed in at context creation. These mutexes are not required to be recursive (they may be, but MuPDF should never call them in this way). MuPDF avoids deadlocks by imposing a locking ordering on itself; a thread will never take lock n, if it already holds any lock i for which 0 <= i <= n. Currently, there are 4 locks used within MuPDF. Lock 0: The alloc lock; taken around all calls to user supplied (or default) allocation functions. Also taken around all accesses to the refs field of storable items. Lock 1: The store lock; taken whenever the store data structures (specifically the linked list pointers) are accessed. Lock 2: The file lock; taken whenever a thread is accessing the raw file. We use the debugging macros to insist that this is held whenever we do a file based seek or read. We also insist that this is never held when we resolve an indirect reference, as this can have the effect of moving the file pointer. Lock 3: The glyphcache lock; taken whenever a thread calls freetype, or accesses the glyphcache data structures. This introduces some complexities w.r.t type3 fonts. Locking can be hugely problematic, so to ease our minds as to the correctness of this code, we introduce some debugging macros. These compile away to nothing unless FITZ_DEBUG_LOCKING is defined. fz_assert_lock_held(ctx, lock) checks that we hold lock. fz_assert_lock_not_held(ctx, lock) checks that we do not hold lock. In addition fz_lock_debug_lock and fz_lock_debug_unlock are used on every fz_lock/fz_unlock to check the validity of the operation we are performing - in particular it checks that we do/do not already hold the lock we are trying to take/drop, and that by taking this lock we are not violating our defined locking order. The RESOLVE macro (used throughout the code to check whether we need to resolve an indirect reference) calls fz_assert_lock_not_held to ensure that we aren't about to resolve an indirect reference (and hence move the stream pointer) when the file is locked. In order to implement the file locking properly, pdf_open_stream (and friends) now lock the file as a side effect (because they fz_seek to the start of the stream). The lock is automatically dropped on an fz_close of such streams. Previously, the glyph cache was created in a context when it was first required; this presents problems as it can be shared between several contexts or not, depending on whether it is created before the contexts are cloned. We now always create it at startup, so it is always shared. This means that we need reference counting for the glyph caches. Added here. In fz_render_glyph, we take the glyph cache lock, and check to see whether the glyph is in the cache. If it is, we bump the refcount, drop the lock and returned the cached character. If it is not, we need to render the character. For freetype based fonts we keep the lock throughout the rendering process, thus ensuring that freetype is only called in a single threaded manner. For type3 fonts, however, we need to invoke the interpreter again to render the glyph streams. This can require reentrance to this routine. We therefore drop the glyph cache lock, call the interpreter to render us our pixmap, and take the lock again. This dropping and retaking of the lock introduces a possible race condition; 2 threads may try to render the same character at the same time. We therefore modify our hash table insert routines to behave differently if it comes to insert an entry only to find that an entry with the same key is already there. We spot this case; if we have just rendered a type3 glyph and when we try to insert it into the cache discover that someone has beaten us to it, we just discard our entry and use the cached one. Hopefully this will seldom be a problem in practise; to solve it properly would require greater complexity (probably involving spotting that another thread is already working on the desired rendering, and sleeping on a semaphore until it completes).
Diffstat (limited to 'draw')
-rw-r--r--draw/draw_glyph.c87
1 files changed, 66 insertions, 21 deletions
diff --git a/draw/draw_glyph.c b/draw/draw_glyph.c
index 49f04fe8..00eaf460 100644
--- a/draw/draw_glyph.c
+++ b/draw/draw_glyph.c
@@ -8,6 +8,7 @@ typedef struct fz_glyph_key_s fz_glyph_key;
struct fz_glyph_cache_s
{
+ int refs;
fz_hash_table *hash;
int total;
};
@@ -37,10 +38,12 @@ fz_new_glyph_cache_context(fz_context *ctx)
fz_rethrow(ctx);
}
cache->total = 0;
+ cache->refs = 1;
ctx->glyph_cache = cache;
}
+/* The glyph cache lock is always held when this function is called. */
static void
fz_evict_glyph_cache(fz_context *ctx)
{
@@ -70,10 +73,25 @@ fz_free_glyph_cache_context(fz_context *ctx)
if (!ctx->glyph_cache)
return;
- fz_evict_glyph_cache(ctx);
- fz_free_hash(ctx, ctx->glyph_cache->hash);
- fz_free(ctx, ctx->glyph_cache);
- ctx->glyph_cache = NULL;
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
+ ctx->glyph_cache->refs--;
+ if (ctx->glyph_cache->refs == 0)
+ {
+ fz_evict_glyph_cache(ctx);
+ fz_free_hash(ctx, ctx->glyph_cache->hash);
+ fz_free(ctx, ctx->glyph_cache);
+ ctx->glyph_cache = NULL;
+ }
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+}
+
+fz_glyph_cache *
+fz_glyph_cache_keep(fz_context *ctx)
+{
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
+ ctx->glyph_cache->refs++;
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ return ctx->glyph_cache;
}
fz_pixmap *
@@ -92,8 +110,6 @@ fz_render_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix ctm, fz_color
fz_pixmap *val;
float size = fz_matrix_expansion(ctm);
- if (!ctx->glyph_cache)
- fz_new_glyph_cache_context(ctx);
cache = ctx->glyph_cache;
if (size > MAX_FONT_SIZE)
@@ -113,25 +129,49 @@ fz_render_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix ctm, fz_color
key.e = (ctm.e - floorf(ctm.e)) * 256;
key.f = (ctm.f - floorf(ctm.f)) * 256;
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
val = fz_hash_find(ctx, cache->hash, &key);
if (val)
- return fz_keep_pixmap(ctx, val);
+ {
+ fz_keep_pixmap(ctx, val);
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ return val;
+ }
ctm.e = floorf(ctm.e) + key.e / 256.0f;
ctm.f = floorf(ctm.f) + key.f / 256.0f;
- if (font->ft_face)
- {
- val = fz_render_ft_glyph(ctx, font, gid, ctm);
- }
- else if (font->t3procs)
+ fz_try(ctx)
{
- val = fz_render_t3_glyph(ctx, font, gid, ctm, model);
+ if (font->ft_face)
+ {
+ val = fz_render_ft_glyph(ctx, font, gid, ctm);
+ }
+ else if (font->t3procs)
+ {
+ /* We drop the glyphcache here, and execute the t3
+ * glyph code. The danger here is that some other
+ * thread will come along, and want the same glyph
+ * too. If it does, we may both end up rendering
+ * pixmaps. We cope with this later on, by ensuring
+ * that only one gets inserted into the cache. If
+ * we insert ours to find one already there, we
+ * abandon ours, and use the one there already.
+ */
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ val = fz_render_t3_glyph(ctx, font, gid, ctm, model);
+ fz_lock(ctx, FZ_LOCK_GLYPHCACHE);
+ }
+ else
+ {
+ fz_warn(ctx, "assert: uninitialized font structure");
+ val = NULL;
+ }
}
- else
+ fz_catch(ctx)
{
- fz_warn(ctx, "assert: uninitialized font structure");
- return NULL;
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ fz_rethrow(ctx);
}
if (val)
@@ -142,8 +182,14 @@ fz_render_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix ctm, fz_color
fz_evict_glyph_cache(ctx);
fz_try(ctx)
{
- fz_hash_insert(ctx, cache->hash, &key, val);
- fz_keep_font(ctx, key.font);
+ fz_pixmap *pix = fz_hash_insert(ctx, cache->hash, &key, val);
+ if (pix)
+ {
+ fz_drop_pixmap(ctx, val);
+ val = pix;
+ }
+ else
+ fz_keep_font(ctx, key.font);
val = fz_keep_pixmap(ctx, val);
}
fz_catch(ctx)
@@ -151,10 +197,9 @@ fz_render_glyph(fz_context *ctx, fz_font *font, int gid, fz_matrix ctm, fz_color
fz_warn(ctx, "Failed to encache glyph - continuing");
}
cache->total += val->w * val->h;
- return val;
}
- return val;
}
- return NULL;
+ fz_unlock(ctx, FZ_LOCK_GLYPHCACHE);
+ return val;
}