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