Memory leaks in cairo bindings



I am trying to track down a problem with leaking cairo_t context structs
when using cairo from python. I running under an up to date fedora 20 system.
(I suspect that other structs are leaking as well, but I have not deep dived
them yet).

The attached example program can be run under valgrind to show the leak.
The program takes a repeat count that you can use to tell the leak from the 
startup overhead.

        python gdk-cairo-leak.py 10

I think the bug is in the reference counting of the cairo_t struct.
After creation the ref_count is 2 that that means that it will never be freed 
as the python object will be deleted causing cairo_destroy to be called that 
will take the ref count down to 1 but not 0 required to free the storage.

Is there a mecahnism to mark the cairo_create and not needing an extra 
cairo_reference call?

I am happy to work up a patch if someone can give me some insight into how
the reference counting is supposed to work in the class of API.

The following GDB session should show the bug. I have determined the
cr->ref_count address and set a watch point. I have annotate the key
observations with *****.

(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /usr/bin/python gdk-cairo-leak-barry.py 1
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7fffe67e1700 (LWP 18064)]
cairo_create
Hardware watchpoint 12: *(int *)0x9d8c60

***** 0x9d8c60 is the cr->ref_count address

Old value = 0
New value = 1
_cairo_init (cr=cr entry=0x9d8c60, backend=backend entry=0x7fffea9758a0 
<_cairo_default_context_backend>) at cairo.c:247
247         cr->status = CAIRO_STATUS_SUCCESS;

***** cairo_create returns the cairo_t with a ref_count of 1.

(gdb) bt 10
#0  _cairo_init (cr=cr entry=0x9d8c60, backend=backend entry=0x7fffea9758a0 
<_cairo_default_context_backend>) at cairo.c:247
#1  0x00007fffea681d08 in _cairo_default_context_init (cr=cr entry=0x9d8c60, 
target=0x9d8640) at cairo-default-context.c:1445
#2  0x00007fffea681d9d in _cairo_default_context_create (target=<optimized 
out>) at cairo-default-context.c:1468
#3  0x00007fffec9be383 in gdk_cairo_create (window=0x9b32b0) at 
gdkwindow.c:3180
#4  0x00007fffef2b7d8c in ffi_call_unix64 () at ../src/x86/unix64.S:76
#5  0x00007fffef2b76bc in ffi_call (cif=cif entry=0x7fffffffc980, 
fn=fn entry=0x7fffec9be310 <gdk_cairo_create>, 
rvalue=rvalue entry=0x7fffffffc960, 
    avalue=avalue entry=0x7fffffffc8a0) at ../src/x86/ffi64.c:522
#6  0x00007ffff01ade49 in g_callable_info_invoke (info=info entry=0x9d55e0, 
function=0x7fffec9be310 <gdk_cairo_create>, in_args=in_args entry=0x9b6c50, 
    n_in_args=n_in_args entry=1, out_args=out_args entry=0x0, 
n_out_args=n_out_args entry=0, return_value=return_value entry=0x7fffffffcb58, 
    is_method=is_method entry=0, throws=0, error=error entry=0x7fffffffcb08) 
at girepository/gicallableinfo.c:680
#7  0x00007ffff01af199 in g_function_info_invoke (info=info entry=0x9d55e0, 
in_args=0x9b6c50, n_in_args=1, out_args=0x0, n_out_args=0, 
    return_value=return_value entry=0x7fffffffcb58, 
error=error entry=0x7fffffffcb08) at girepository/gifunctioninfo.c:274
#8  0x00007ffff03ebab7 in _invoke_callable (function_ptr=0x0, 
callable_info=0x9d55e0, cache=0x9b52a0, state=0x7fffffffcb10) at pygi-
invoke.c:64
#9  pygi_callable_info_invoke (info=<optimized out>, py_args=<optimized out>, 
kwargs=<optimized out>, cache=<optimized out>, function_ptr=<optimized out>, 
    user_data=<optimized out>) at pygi-invoke.c:652
(More stack frames follow...)
(gdb) c
Continuing.

Breakpoint 4, gdk_cairo_create (window=0x9b32b0) at gdkwindow.c:3182
3182      if (window->impl_window->paint_stack)
(gdb) c
Continuing.

Hardware watchpoint 12: *(int *)0x9d8c60

Old value = 1
New value = 2
cairo_reference (cr=cr entry=0x9d8c60) at cairo.c:279
279     }
(gdb) bt 10
#0  cairo_reference (cr=cr entry=0x9d8c60) at cairo.c:279
#1  0x00007fffe5ddebac in cairo_context_from_arg (interface_info=<optimized 
out>, data=0x9d8c60) at pygi-foreign-cairo.c:63
#2  0x00007ffff03f1d2c in _pygi_marshal_to_py_interface_struct_cache_adapter 
(state=<optimized out>, callable_cache=<optimized out>, 
    arg_cache=<optimized out>, arg=<optimized out>) at pygi-marshal-to-
py.c:752
#3  0x00007ffff03ebc1d in _invoke_marshal_out_args (cache=0x9b52a0, 
state=0x7fffffffcb10) at pygi-invoke.c:543
#4  pygi_callable_info_invoke (info=<optimized out>, py_args=<optimized out>, 
kwargs=<optimized out>, cache=<optimized out>, function_ptr=<optimized out>, 
    user_data=<optimized out>) at pygi-invoke.c:657
#5  0x00007ffff7a610d3 in PyObject_Call (func=func entry=<gi.FunctionInfo at 
remote 0x95dc38>, arg=arg entry=(<X11Window at remote 0x95b1e0>,), 
    kw=kw entry=0x0) at /usr/src/debug/Python-2.7.5/Objects/abstract.c:2529
#6  0x00007ffff7af537c in do_call (nk=<optimized out>, na=1, 
pp_stack=0x7fffffffcd00, func=<gi.FunctionInfo at remote 0x95dc38>)
    at /usr/src/debug/Python-2.7.5/Python/ceval.c:4316
#7  call_function (oparg=<optimized out>, pp_stack=0x7fffffffcd00) at 
/usr/src/debug/Python-2.7.5/Python/ceval.c:4121
#8  PyEval_EvalFrameEx (
    f=f entry=Frame 0x9d5cc0, for file /usr/lib64/python2.7/site-
packages/gi/overrides/Gdk.py, line 159, in cairo_create (self=<X11Window at 
remote 0x95b1e0>), throwflag=throwflag entry=0) at 
/usr/src/debug/Python-2.7.5/Python/ceval.c:2740
#9  0x00007ffff7af7980 in fast_function (nk=<optimized out>, na=1, n=1, 
pp_stack=0x7fffffffce60, func=<function at remote 0x8ccd70>)
    at /usr/src/debug/Python-2.7.5/Python/ceval.c:4184
(More stack frames follow...)

***** cairo_reference is called to as part of the process of
***** marshelling the cairo_t into python land.
***** As far as I can tell this is the root cause of the leak.
***** THere is no conditional logic here to prevent extra
***** ref_count increment that I could see in the code.

(gdb) c
Continuing.
sleep
^C

***** I added a time.sleep( 10 ) so that we can see the ref count of the 
returned cairo_t.

Program received signal SIGINT, Interrupt.
0x00007ffff6e1a463 in select () at ../sysdeps/unix/syscall-template.S:81
81      T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) p *(int *)0x9d8c60
$57 = 2

**** As expected from the available code the ref_count is 2 which 1 to big.

(gdb) c
Continuing.
done

Breakpoint 3, INT_cairo_destroy (cr=0x9d8c60) at cairo.c:300
300         if (cr == NULL || CAIRO_REFERENCE_COUNT_IS_INVALID (&cr-
ref_count))
(gdb) bt 10
#0  INT_cairo_destroy (cr=0x9d8c60) at cairo.c:300
#1  0x00007fffe67ebd72 in pycairo_dealloc (o=0x7ffff7f68310) at context.c:75
#2  0x00007ffff7a84b02 in frame_dealloc (f=Frame 0x9d5af0, for file gdk-cairo-
leak-barry.py, line 49, in draw ())
    at /usr/src/debug/Python-2.7.5/Objects/frameobject.c:460
#3  0x00007ffff7af799c in fast_function (nk=<optimized out>, na=<optimized 
out>, n=1, pp_stack=0x7fffffffcfc0, func=<function at remote 0x9515f0>)
    at /usr/src/debug/Python-2.7.5/Python/ceval.c:4186
#4  call_function (oparg=<optimized out>, pp_stack=0x7fffffffcfc0) at 
/usr/src/debug/Python-2.7.5/Python/ceval.c:4119
#5  PyEval_EvalFrameEx (
    f=f entry=Frame 0x9d5920, for file gdk-cairo-leak-barry.py, line 34, in 
__gdkEventHandler (self=<CairoLeak(repeat_count=0, window=<X11Window at remote 
0x95b1e0>, letterbox_colour=[255, 0, 0, 255]) at remote 0x958200>, 
event=<Event at remote 0x8b58d8>, data=None), throwflag=throwflag entry=0)
    at /usr/src/debug/Python-2.7.5/Python/ceval.c:2740
#6  0x00007ffff7af91dd in PyEval_EvalCodeEx (co=<optimized out>, 
globals=<optimized out>, locals=locals entry=0x0, args=args entry=0x95b068, 
argcount=3, 
    kws=kws entry=0x0, kwcount=kwcount entry=0, defs=defs entry=0x0, 
defcount=defcount entry=0, closure=0x0)
    at /usr/src/debug/Python-2.7.5/Python/ceval.c:3330
#7  0x00007ffff7a860d8 in function_call (func=<function at remote 0x951578>, 
    arg=(<CairoLeak(repeat_count=0, window=<X11Window at remote 0x95b1e0>, 
letterbox_colour=[255, 0, 0, 255]) at remote 0x958200>, <Event at remote 
0x8b58d8>, None), kw=0x0) at 
/usr/src/debug/Python-2.7.5/Objects/funcobject.c:526
#8  0x00007ffff7a610d3 in PyObject_Call (func=func entry=<function at remote 
0x951578>, 
    arg=arg entry=(<CairoLeak(repeat_count=0, window=<X11Window at remote 
0x95b1e0>, letterbox_colour=[255, 0, 0, 255]) at remote 0x958200>, <Event at 
remote 0x8b58d8>, None), kw=kw entry=0x0) at 
/usr/src/debug/Python-2.7.5/Objects/abstract.c:2529
#9  0x00007ffff7a700c5 in instancemethod_call (func=<function at remote 
0x951578>, 
    arg=(<CairoLeak(repeat_count=0, window=<X11Window at remote 0x95b1e0>, 
letterbox_colour=[255, 0, 0, 255]) at remote 0x958200>, <Event at remote 
0x8b58d8>, None), kw=0x0) at 
/usr/src/debug/Python-2.7.5/Objects/classobject.c:2602
(More stack frames follow...)
(gdb) c
Continuing.

***** we hit cairo_destory on the freeing of the python side object.

Hardware watchpoint 12: *(int *)0x9d8c60

Old value = 2
New value = 1
0x00007fffea678e04 in INT_cairo_destroy (cr=0x9d8c60) at cairo.c:305
305         if (! _cairo_reference_count_dec_and_test (&cr->ref_count))

***** and the ref count goes to 1.

(gdb) c
Continuing.
[Thread 0x7ffff7fe7740 (LWP 18063) exited]
[Inferior 1 (process 18063) exited normally]

***** process exits with 1 leaked cairo_t.

(gdb) 

Barry

Attachment: gdk-cairo-leak.py
Description: Text Data



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]