#524908 libcairo-ocaml-dev: memory leak in Cairo_bigarray

Package:
libcairo-ocaml-dev
Source:
cairo-ocaml
Description:
OCaml bindings for Cairo
Submitter:
Oto Havle
Date:
2011-08-05 18:54:06 UTC
Severity:
normal
#524908#5
Date:
2009-04-20 18:46:03 UTC
From:
To:
OCaml programs which create Cairo surfaces via Cairo_bigarray leak memory.
Following test program reproduces the bug. The program allocates many 100Mb arrays, but
explicit garbage collector invocation should keep the total memory usage low.


$ cat bug.ml
#use "topfind"
#require "bigarray,cairo"

open Bigarray

let test () =
   let u = Array2.create int8_unsigned c_layout 10000 10000 in
   ignore (Cairo_bigarray.of_bigarr_8 u)

let () =
   for k = 1 to 100 do Gc.compact (); test () done

$ ocaml bug.ml
Out of memory during evaluation.


It seems that the OCaml runtime does not free memory associated with the bigarray "u".
Curiously, following program works as expected. Note the last line of function "test", which
prevents deallocation of the bigarray in preceding GC run.


$ cat nobug.ml
#use "topfind"
#require "bigarray,cairo"

open Bigarray

let test () =
   let u = Array2.create int8_unsigned c_layout 10000 10000 in
   ignore (Cairo_bigarray.of_bigarr_8 u);
   Gc.compact ();
   ignore u

let () =
   for k = 1 to 100 do Gc.compact (); test () done

$ ocaml nobug.ml
(no output)

#524908#14
Date:
2011-08-02 18:07:09 UTC
From:
To:
tags 524908 + moreinfo
thanks

I cannot reproduce this with version 20090223-5 (current testing), nor
with 1:1.2.0-1 (current unstable). Is this bug still relevant?


Cheers,

#524908#21
Date:
2011-08-02 19:52:42 UTC
From:
To:
Hi,

   Thank you for investigating. I've just tried to run the old test code and I can still
reproduce the bug. I use Debian Stable, package versions

  libcairo-ocaml-dev        20090223-3+b2
  ocaml-findlib             1.2.5+debian-1+b1
  ocaml-nox                 3.11.2-2

I do not have debian testing installed, so I did not test version 20090223-5.

   I've run the test programs bug.ml and nobug.ml (mentioned in the original bug report)
under strace.

$ strace ocaml bug.ml 2> bug.strace
$ strace ocaml nobug.ml 2> nobug.strace

Please look at the mmap/munmap calls at the very end of the strace logs. Clearly, bug.ml
allocates until exhaustion of the address space. On the other hand, nobug.ml correctly
releases the memory. Note that I have 32-bit system.

Could you please run similar tests? If you test on 64-bit system and cannot reproduce the
bug, you can try bigger array size / iteration count.

Best wishes,

   Oto Havle.

Stéphane Glondu wrote:

#524908#26
Date:
2011-08-03 06:22:41 UTC
From:
To:
tags 524908 - moreinfo
tags 524908 + confirmed
found 524908 1:1.2.0-2
thanks

Le 02/08/2011 21:52, Oto Havle a écrit :

I didn't take this into account, and I can indeed reproduce the bug in
an up-to-date i386 sid chroot, with version 1:1.2.0-2.


Cheers,

#524908#37
Date:
2011-08-03 17:16:58 UTC
From:
To:
tags 524908 + help upstream
thanks

Le 03/08/2011 08:22, Stéphane Glondu a écrit :

By looking a bit deeper, I can observe that bug.ml fails on both
bytecode and native, but nobug.ml fails only on native (and succeeds on
bytecode as originally stated in the bug report).

Looking even further at the failing bytecode case, after instrumenting
the code (cairo-ocaml and ocaml GC code), I can see that cairo_surface_t
finalizer is always called when expected.
ml_cairo_image_surface_create_for_data does register a global root, but
caml_{register/remove}_global_root calls are always paired. I tried to
remove the global root registration, and the problem disappears.

By the way, not all the recommendations from the section "Living in
harmony with the garbage collector" of the manual are respected: there
are many missing CAMLparam*, CAMLlocal* and CAMLreturn* invocations. I
tried to add some, but was unsuccessful in solving the problem.

#524908#44
Date:
2011-08-04 14:52:20 UTC
From:
To:
Stéphane Glondu wrote:
 > I can see that cairo_surface_t finalizer is always called when expected.
 > ml_cairo_image_surface_create_for_data does register a global root, but
 > caml_{register/remove}_global_root calls are always paired.

   The bug scenario in fact involves two custom blocks - cairo_surface_t and bigarray.
The cairo_surface_t finalizer (ml_final_cairo_surface_t) is called correctly, but the
bigarray finalizer (caml_ba_finalize) is not called at all. The bigarray value somehow
disappears from the OCaml heap. The corresponding bigarray data on the C heap is not reclaimed.

   I've found a thread from caml-list archives which suggests that calling
caml_{register/remove}_global_root from custom block finalizers might not be safe:

http://www.mail-archive.com/caml-list@yquem.inria.fr/msg03746.html

   I've tried to relate the bug to the OCaml GC. My current idea is as follows:

The custom block

- is always allocated in the major heap
- is reclaimed and finalizer is called in sweep_slice() in major_gc.c
- is also reclaimed in caml_compact_heap() in compact.c - and finalizer is not called!

compact.c:
     310           /* No pointers to the header and no infix header:
     311              the object was free. */
     312           *p = Make_header (Wosize_ehd (q), Tag_ehd (q), Caml_blue);
     313           p += Whsize_ehd (q);

   The custom block is manipulated in this part of the GC code (confirmed by inserting a tag
check here, and running bug.ml through the modified ocamlrun). Moreover, the bug does not
appear if Gc.compact is replaced by Gc.full_major.

   Maybe someone who really understands OCaml GC code can confirm it?

#524908#49
Date:
2011-08-05 18:49:23 UTC
From:
To:
No, this is not the case, quoting Xavier Leroy from that same thread: