[cairo] glyph caching bug

TOKUNAGA Hiroyuki tkng at xem.jp
Tue Dec 28 11:16:14 PST 2004


Hi,

On Mon, 27 Dec 2004 01:46:31 -0800
Keith Packard <keithp at keithp.com> wrote:

> I've noticed a bug somewhere in the cairo glyph caching code -- it's
> using  the wrong glyphs at times, and most often with large glyphs.
> 
> Here's an example nickle program which reproduces the bug in my 
> environment.  It spins "CAIRO" around at a scale of 100; at some 
> rotations, various letters are mistakenly replaced with other letters
> or  blanks.  Very disconcerting; you'll note that the code takes care
> to  double buffer the results and should be completely smooth.
> 
> I haven't tried to debug this very far; it looks hard to find to me...

I could reproduced the bug. This seems because there's no cache lock
implementation. Sometimes created Glyph was destroyed before used, since
_cairo_cache_lookup destroys entries randomly.

Attached patch is a tentative implementation of cache locking. While a
cache is locked, lookup function doesn't call destroy_entry function. I
could confirm that the bug doesn't occur if this patch was applied. But
cache will consume much memory than restricted by cache->max_memory
untill unlock function is called. It may be a problem.


Regards,

-- 
TOKUNAGA Hiroyuki
tkng at xem.jp
http://kodou.net/
-------------- next part --------------
Index: cairo_cache.c
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo_cache.c,v
retrieving revision 1.5
diff -u -r1.5 cairo_cache.c
--- cairo_cache.c	20 Dec 2004 17:43:59 -0000	1.5
+++ cairo_cache.c	28 Dec 2004 19:04:34 -0000
@@ -121,7 +121,8 @@
     assert (cache->entries != NULL);
     assert (cache->backend != NULL);
     assert (cache->arrangement != NULL);
-    assert (cache->used_memory <= cache->max_memory);
+    if(cache->locked == 0)
+       assert (cache->used_memory <= cache->max_memory);
     assert (cache->live_entries <= cache->arrangement->size);   
 }
 #else
@@ -409,16 +410,17 @@
     /* Store the hash value in case the backend forgot. */
     new_entry->hashcode = cache->backend->hash (cache, key);
 
-    /* Make some entries die if we're under memory pressure. */
-    while (cache->live_entries > 0 &&
-	   ((cache->max_memory - cache->used_memory) < new_entry->memory)) {
-	idx = _random_live_entry (cache);
-	assert (idx < cache->arrangement->size);
-	_entry_destroy (cache, idx);
+    if(cache->locked == 0) {
+       /* Make some entries die if we're under memory pressure. */
+       while (cache->locked == 0 && cache->live_entries > 0 &&
+	      ((cache->max_memory - cache->used_memory) < new_entry->memory)) {
+	 idx = _random_live_entry (cache);
+	 assert (idx < cache->arrangement->size);
+	 _entry_destroy (cache, idx);
+       }
+       assert(cache->max_memory >= (cache->used_memory + new_entry->memory));
     }
-    
-    assert(cache->max_memory >= (cache->used_memory + new_entry->memory));
-    
+   
     /* Make room in the table for a new slot. */
     status = _resize_cache (cache, cache->live_entries + 1);
     if (status != CAIRO_STATUS_SUCCESS) {
@@ -440,6 +442,35 @@
     return status;
 }
 
+/*
+ *  _cairo_cache_lookup may call destroy_entry function. This will be a problem
+ *  if you want to use multiple entries at the same time. (i.e. a created
+ *  entry may be destroyed before used.)
+ *  If this function is called, _cairo_cache_lookup doesn't call destroy_entry
+ *  function. You must call this function before multiple calls of
+ *  _cairo_cache_lookup.
+ */
+void
+_cairo_cache_lock (cairo_cache_t *cache)
+{
+    cache->locked = 1;
+}
+
+/* You must call _cairo_cache_unlock at the end of the function which uses
+   _cairo_cache_lock. */
+void
+_cairo_cache_unlock (cairo_cache_t *cache)
+{
+    cache->locked = 0;
+    /* Make some entries die if we're under memory pressure. */
+    while (cache->live_entries > 0 &&
+	   ((cache->max_memory - cache->used_memory) < 0)) {
+	  unsigned long idx = _random_live_entry (cache);
+	  assert (idx < cache->arrangement->size);
+	  _entry_destroy (cache, idx);
+    }
+}
+
 unsigned long
 _cairo_hash_string (const char *c)
 {
Index: cairo_xlib_surface.c
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo_xlib_surface.c,v
retrieving revision 1.31
diff -u -r1.31 cairo_xlib_surface.c
--- cairo_xlib_surface.c	21 Dec 2004 21:14:46 -0000	1.31
+++ cairo_xlib_surface.c	28 Dec 2004 19:04:35 -0000
@@ -958,14 +958,22 @@
 
 static void
 _lock_xlib_glyphset_caches (void)
-{
-    /* FIXME: implement locking */
+{ 
+    cairo_cache_t *cache;
+    if(_xlib_glyphset_caches != NULL) {
+      cache = &(_xlib_glyphset_caches->base);
+      _cairo_cache_lock (cache);
+    }
 }
 
 static void
 _unlock_xlib_glyphset_caches (void)
 {
-    /* FIXME: implement locking */
+    cairo_cache_t *cache;
+    if(_xlib_glyphset_caches != NULL) {
+      cache = &(_xlib_glyphset_caches->base);
+      _cairo_cache_unlock (cache);
+    }
 }
 
 static glyphset_cache_t *
Index: cairoint.h
===================================================================
RCS file: /cvs/cairo/cairo/src/cairoint.h,v
retrieving revision 1.78
diff -u -r1.78 cairoint.h
--- cairoint.h	21 Dec 2004 21:22:44 -0000	1.78
+++ cairoint.h	28 Dec 2004 19:04:35 -0000
@@ -317,6 +317,7 @@
     unsigned long max_memory;
     unsigned long used_memory;
     unsigned long live_entries;
+    unsigned int  locked;
 
 #ifdef CAIRO_MEASURE_CACHE_PERFORMANCE
     unsigned long hits;
@@ -341,6 +342,12 @@
 		     void *key,
 		     void **entry_return);
 
+cairo_private void
+_cairo_cache_lock (cairo_cache_t *cache);
+
+cairo_private void
+_cairo_cache_unlock (cairo_cache_t *cache);
+
 cairo_private unsigned long
 _cairo_hash_string (const char *c);
 


More information about the cairo mailing list