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
#!/usr/bin/python import sys import gi from gi.repository import GObject, GLib, Gdk, GdkX11 import cairo GObject.threads_init() GLib.threads_init() class CairoLeak: def __init__( self ): window_attrs = Gdk.WindowAttr() window_attrs.title = "Leak" window_attrs.event_mask = Gdk.EventMask.EXPOSURE_MASK window_attrs.width = 400 window_attrs.height = 300 window_attrs.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT window_attrs.window_type = Gdk.WindowType.TOPLEVEL window_attrs.visual = Gdk.Screen.get_default().get_rgba_visual() self.window = Gdk.Window( None, window_attrs, Gdk.WindowAttributesType.TITLE | Gdk.WindowAttributesType.VISUAL ) self.letterbox_colour = [ 255, 0, 0, 255 ] Gdk.Event.handler_set( self.__gdkEventHandler, None ) self.window.show() def __gdkEventHandler( self, event, data ): self.draw() def draw( self ): cr = self.window.cairo_create() cr.set_operator( cairo.OPERATOR_SOURCE ) cr.set_source_rgba( self.letterbox_colour[0] / 255.0, self.letterbox_colour[1] / 255.0, self.letterbox_colour[2] / 255.0, self.letterbox_colour[3] / 255.0 ) cr.paint() Gdk.flush() self.letterbox_colour = [self.letterbox_colour[2], self.letterbox_colour[0], self.letterbox_colour[1], self.letterbox_colour[3]] def triggerLeak( leaker ): leaker.draw() global repeat_count repeat_count -= 1 if repeat_count <= 0: global main_loop main_loop.quit() return repeat_count > 0 def main( argv ): global repeat_count repeat_count = int( sys.argv[1] ) global main_loop main_loop = GLib.MainLoop() leaker = CairoLeak() GLib.timeout_add_seconds( 1, triggerLeak, leaker ) main_loop.run() if __name__ == '__main__': sys.exit( main( sys.argv ) )
_______________________________________________ python-hackers-list mailing list python-hackers-list@gnome.org https://mail.gnome.org/mailman/listinfo/python-hackers-list