Cairo problem: memory leak?

BlitzMax Forums/Brucey's Modules/Cairo problem: memory leak?

Rozek(Posted 2010) [#1]
Hello!

I am using Cairo within a multi-threaded environment (although it's guaranteed that all drawing happens within the main thread only!) - and facing a severe memory leak (approx 20MB per second!)

First I thought, I might be using Cairo the wrong way (namely by creating a local TCairoImageSurface (from a fixed image) and then creating a local TCairo context (which is "destroy"ed later after having finished drawing into the image) whenever I need to draw new content - up to 30 times per second). So I started "caching" the TCairoImageSurface (the underlying image was cached anyway) but immediately ran into the following error message

Simulator.debug.mt(10231,0xa000d000) malloc: *** error for object 0x22e6cd0: incorrect checksum for freed object - object was probably modified after being freed, break at szone_error to debug
Simulator.debug.mt(10231,0xa000d000) malloc: *** set a breakpoint in szone_error to debug

Does Cairo.destroy() free the ImageSurface internally?

When I create a new Surface every time, everthing works fine (well, except the horrible memory loss)

I am using Cairo under Mac OS X 10.4 on a Mac mini Intel

Thanks in advance for any response!


Rozek(Posted 2010) [#2]
Well,

I just cached the result of TCairo.create(Curface) as well - and used a Cairo.createContext()/destroy() pair whenever I have to draw something.

Now it no longer crashes - but the horrible memory leak still persists!


Rozek(Posted 2010) [#3]
Hello again!

I just checked that it is NOT a problem of BlitzMAX's internal GC. The following little test
superstrict 

  function ThreadFunction:Object (Data:Object)
    debuglog("Subthread:  started")
      local i:int
      for i = 1 to 1000000 
        ThreadSubroutine()
      next
    debuglog("Subthread:  stopping")
  end function

  function ThreadSubroutine()
    local Text:String = "", i:int
    for i = 1 to 1000
      Text = Text + " ";
    next
  end function

debuglog("MainThread: spawning Subthread")
local Subthread:TThread = createThread(ThreadFunction,null)

debuglog("MainThread: waiting for Subthread to finish")
  waitThread(Subthread)
debuglog("MainThread: finishing")
runs fine without wasting more and more memory


Rozek(Posted 2010) [#4]
Hmmm,

Cairo seems to be *principally* wasting memory: whenever I cache some more objects (e.g. ImageSurfaces, FontFaces etc.) I get a lower leaking rate...

And there must be even more: sometimes I don't see any output from Cairo - but when I recompile the (unmodified!) code and run again, I might get output again!

Prior to caching, I always got a result. With caching, I sometimes get nothing - debugger output shows, that all Cairo items have been properly created, though.

I'll check my code again (to see if I can find a workaround) but I'm afraid that Cairo needs some more testing...


Brucey(Posted 2010) [#5]
Hi!
Do you have a small example I can test here?


Rozek(Posted 2010) [#6]
Hello Brucey!

The program itself is quite large already - but let me see if I can get an excerpt. Just give me some time


Rozek(Posted 2010) [#7]
Brucey,

try this one already (although not multi-threaded) - it's just a little "stress test" using one of your examples:

SuperStrict

Import BaH.Cairo

Graphics 640,480,0

SetBlend ALPHABLEND

SetClsColor 255,255,255

repeat
  Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(256,256))

  Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(256.0,256.0)
  cairo.SetMatrix(normalizeMat)

  Local xc:Double = 0.5
  Local yc:Double = 0.5
  Local radius:Double = 0.4
  Local angle1:Double = 45.0 
  Local angle2:Double = 180.0         

  cairo.SetLineWidth(0.05)
  cairo.Arc(xc, yc, radius, angle1, angle2)
  cairo.Stroke()

' draw helping lines 
  cairo.SetSourceRGBA(1,0.2,0.2,0.6)
  cairo.Arc(xc, yc, 0.05, 0, 360)
  cairo.Fill()
  cairo.SetLineWidth(0.03)
  cairo.Arc(xc, yc, radius, angle1, angle1)
  cairo.LineTo(xc, yc)
  cairo.Arc(xc, yc, radius, angle2, angle2)
  cairo.LineTo(xc, yc)
  cairo.Stroke()

  cairo.Destroy()

  Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap())

  Cls
  DrawImage image,MouseX() - 128,MouseY() - 128
  Flip
Until KeyHit(key_escape)

End
On my machine, this little application ended up with hundreds of MBs (then the activity display stopped showing and my machine was almost locked)


Rozek(Posted 2010) [#8]
With an additional
delay(33)
in the loop (which gives you s.th. around 30fps) I end up with approx 17MB loss/sec, after the internal GC managed to collect dead memory 3 times (and then gave up!?)


Rozek(Posted 2010) [#9]
Hey,

with an explicit GCCollect() in the loop (I'm counting til 30, then doing a GCCollect) I managed to keep the memory consumption low!

Let me try that in my actual application!
[result]
Unfortunately, that didn't help - I have the impression, that the memory loss rate was slightly reduced, but I still loose approx. 14MB/sec (with all caching switched off again)
[/result]


Rozek(Posted 2010) [#10]
Brucey,

something strange is going on, when trying the following example
SuperStrict

Import BaH.Cairo

type TDummy
  field Dummy:int[] = new int[1000]
end type

  global ThreadSync:TMutex = createMutex()
  global ThreadLocker:TSemaphore = createSemaphore(0)

  function ThreadFunction:Object (Data:Object)
    debuglog("Subthread:  started")
 
    repeat
      waitSemaphore(ThreadLocker)

      lockMutex(ThreadSync) ' emulates modification of graphics tree within subthread
        local i:int
        for i = 1 to 1000
          local Dummy:TDummy = new TDummy
        next
      unlockMutex(ThreadSync)
    forever
  end function

debuglog("MainThread: spawning Subthread")
local Subthread:TThread = createThread(ThreadFunction,null)

Graphics 640,480,0

SetBlend ALPHABLEND

SetClsColor 255,255,255

local i:int = 0
repeat
  postSemaphore(ThreadLocker)

  i = i+1
  if (i = 30) then
    gccollect()
    i = 0
  end if

lockMutex(ThreadSync) ' emulates displaying the graphics tree within main thread
  Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(256,256))

  Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(256.0,256.0)
  cairo.SetMatrix(normalizeMat)

  Local xc:Double = 0.5
  Local yc:Double = 0.5
  Local radius:Double = 0.4
  Local angle1:Double = 45.0 
  Local angle2:Double = 180.0         

  cairo.SetLineWidth(0.05)
  cairo.Arc(xc, yc, radius, angle1, angle2)
  cairo.Stroke()

' draw helping lines 
  cairo.SetSourceRGBA(1,0.2,0.2,0.6)
  cairo.Arc(xc, yc, 0.05, 0, 360)
  cairo.Fill()
  cairo.SetLineWidth(0.03)
  cairo.Arc(xc, yc, radius, angle1, angle1)
  cairo.LineTo(xc, yc)
  cairo.Arc(xc, yc, radius, angle2, angle2)
  cairo.LineTo(xc, yc)
  cairo.Stroke()

  cairo.Destroy()

  Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap())

  Cls
  DrawImage image,MouseX() - 128,MouseY() - 128
  Flip
unlockMutex(ThreadSync)

  delay(33)
Until KeyHit(key_escape)

End
the app once immediately(!) crashed while creating instances of TDummy (with plenty of memory left)

I get the impression as if the problem would be related to the BlitzMAX GC - and Cairo just the package where it becomes appearant! Unfortunately, I need Cairo...and Threads...and the GC...


Rozek(Posted 2010) [#11]
Brucey, I may have got it!

Try
Cairo.selectFontFace("arial", 0, 0)
- I'm afraid, that's a major memory leak! just add it to the main thread of the abovementioned example before Cairo.destroy. You may even remove the dummy object creation loop of the second thread!

For your convenience: here is the full code (even without the second thread!)
SuperStrict

Import BaH.Cairo


Graphics 640,480,0

SetBlend ALPHABLEND

SetClsColor 255,255,255

local i:int = 0
repeat
  Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(256,256))

  Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(256.0,256.0)
  cairo.SetMatrix(normalizeMat)

  Local xc:Double = 0.5
  Local yc:Double = 0.5
  Local radius:Double = 0.4
  Local angle1:Double = 45.0 
  Local angle2:Double = 180.0         

  cairo.SetLineWidth(0.05)
  cairo.Arc(xc, yc, radius, angle1, angle2)
  cairo.Stroke()

' draw helping lines 
  cairo.SetSourceRGBA(1,0.2,0.2,0.6)
  cairo.Arc(xc, yc, 0.05, 0, 360)
  cairo.Fill()
  cairo.SetLineWidth(0.03)
  cairo.Arc(xc, yc, radius, angle1, angle1)
  cairo.LineTo(xc, yc)
  cairo.Arc(xc, yc, radius, angle2, angle2)
  cairo.LineTo(xc, yc)
  cairo.Stroke()

cairo.selectFontFace("arial", 0, 0) ' <<<<<<<<<<<<<<<<<<

  cairo.Destroy()

  Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap())

  Cls
  DrawImage image,MouseX() - 128,MouseY() - 128
  Flip

  delay(33)
Until KeyHit(key_escape)

End



Brucey(Posted 2010) [#12]
Your first example app seems to run fine here. (the single thread app).

It certainly isn't leaking - according to the leak tool. Sure, it goes a bit wild with memory, but as far as I can see, that's mostly TImage frame memory.

Do you really need to create/destroy contexts all the time?


Brucey(Posted 2010) [#13]
I've just tested your last one. It isn't leaking here.
Can you email me a compiled version of that last test please? (so that I can run it here).

are you using the latest SVN cairo with the latest BlitzMax?


Rozek(Posted 2010) [#14]
Hmmm,

caching the FontFace helped getting rid of the nasty memory leak - but it seems as if I then also have to cache the Cairo context and the associated Surface, right? If I try to recreate the Cairo context during the next display phase, the application crashes.

I tried to recreate the Cairo context because I still face the problem that - from time to time - I do not get any display at all (with caching - I did not have this kind of problem without caching). In such a case, I have to restart my application and sometimes (not always) I actually get the desired output)

Do you have any idea?


Rozek(Posted 2010) [#15]
Brucey,

sure - with or without debugging enabled?

I'm running BlitzMAX 1.38 with MaxGUI 1.37 under MacOS X 10.4 on a Mac mini Intel

My Cairo version should be quite new (< 6 weeks or so) but let me check that first...yes, it's cairo_1_23_src.zip


Rozek(Posted 2010) [#16]
Brucey,

Email has been sent!

Example is also leaking under Leopard...

but...HEY...when COMPILED under Leopard (same BlitzMX/MaxGUI/Cairo version!) everything runs fine! Strange: even the GCC/C++ versions reported by BlitzMAX are the same!

Do you have any idea? Static libs?


Rozek(Posted 2010) [#17]
Hmm,

something weird is going on (under Leopard): I just rebuilt BlitzMAX/MaxGUI/Cairo and then recompiled the last example - and now it is leaking as well, not continuously (memory consumption goes down from time to time) but still quite heavily.

Now I'm completely puzzled...

[edit]oh - it seems that the BlitzMAX/MaxGUI/Cairo modules depend on the Multi-Threading settings: under Tiger, Multi-Threading was set, under Leopard not. After switching to Multi-Threading under Leopard, I had to rebuild all modules and - afterwards - the example was leaking again

I have to stop here for the moment, but will continue as soon as possible[/edit]


Brucey(Posted 2010) [#18]
Your binary does indeed leak on my machine.
And looking at the call-stack for the "live" blocks in RAM, it's related to FT...
  12 untitled2.debug.mt FT_New_Face
  11 untitled2.debug.mt FT_Open_Face
  10 untitled2.debug.mt open_face
   9 untitled2.debug.mt tt_face_init
   8 untitled2.debug.mt sfnt_load_face
   7 untitled2.debug.mt tt_face_get_name
   6 untitled2.debug.mt tt_name_entry_ascii_from_utf16
   5 untitled2.debug.mt ft_mem_realloc
   4 untitled2.debug.mt ft_mem_qrealloc
   3 untitled2.debug.mt ft_mem_alloc
   2 untitled2.debug.mt ft_mem_qalloc
   1 untitled2.debug.mt ft_alloc
   0 libSystem.B.dylib malloc


The version I have here is Cairo 1.9.4. (which is the dev version). I've not checked it in yet though - as I was working on getting the GL support built in (no, it's not as fast as the image stuff).
Perhaps 1.9.4 works better than 1.8.8 in this regard.


Brucey(Posted 2010) [#19]
None of my tests are leaking.

...except that I couldn't get your mt example working because it crashes out when it tries to free the gl texture.


Rozek(Posted 2010) [#20]
Brucey,

thanks for your effort - the problem with freeing the GL textures can be solved (that's another MT-related problem) see at the end of http://www.blitzmax.com/Community/posts.php?topic=89912 (another one of my lengthy Blog-like threads)

I will now try and rebuild all my BlitzMAX modules without MT and see if the last example still leaks

If not, then we are facing even more trouble when using MT (deep sigh)
[edit]Meanwhile, I did the rebuild and the example STILL leaks - thus, it does NOT depend on multithreading or not![/edit]


Rozek(Posted 2010) [#21]
Brucey,

can you send me your *recommended* version of Cairo and the Cairo wrapper (if it differs from mine)

Thanks in advance!


Rozek(Posted 2010) [#22]
Ok,

with caching (of Surface, Context and FontFaces) my "real" application does not seem to suffer from memory losses any more (physical and virtual memory consumption keep stable) - even without "aggressive" GC (i.e. explicit GCcollect invocations). Now, however, I am facing the problem, that I only get some output in approx. 1 of 5 program invocations.

Do you have any idea why?

[edit]I found the reason: my Cairo surface is based on a "dynamic image" but I did not (yet) embrace my drawing code by lockImage and unlockImage. With image locking, I always get a display - but I also get a memory leak again...(damn)[/edit]