For what it’s worth, here are debugging sessions with Python pdb and some object graphs of the original miniapp, which leaks memory, and the modified app that defines the clicked signal handler as a function, which fixes the leak in this case (please mind the misleading variable names in some places).
Debugging info: app.py
❯ python3 -m pdb app.py
> ./app.py(1)<module>()
(Pdb) import sys
(Pdb) r
(APP STARTS, I BROWSE 10 VIEWS AND QUIT APP)
--Return--
> ./app.py(108)<module>()->None
-> sys.exit(exit_status)
(Pdb) import gc
(Pdb) gc.collect()
92
(Pdb) gc.collect()
0
(Pdb) import objgraph
(Pdb) views_tracked_by_gc = objgraph.by_type("ImageView")
(Pdb) len(views_tracked_by_gc)
10
(Pdb) leaked_views = objgraph.get_leaking_objects(views_tracked_by_gc)
(Pdb) len(leaked_views)
10
(Pdb) import random
(Pdb) random_leaked_view = random.choice(leaked_views)
(Pdb) random_leaked_view
<__main__.ImageView object at 0x7ff6ac645240 (ImageView at 0x2da573b0)>
(Pdb) sys.getrefcount(random_leaked_view)
5
(Pdb) objgraph.show_chain(objgraph.find_backref_chain(random_leaked_view, objgraph.is_proper_module), filename="/tmp/app-random-leaked-view.png")
Graph written to /tmp/objgraph-cz0eglb4.dot (3 nodes)
Image generated as /tmp/app-random-leaked-view.png
(Pdb) objgraph.show_backrefs([random_leaked_view], max_depth=10, filename="/tmp/app-random-leaked-view-depth-10.png")
Graph written to /tmp/objgraph-7u47m7xb.dot (69 nodes)
Image generated as /tmp/app-random-leaked-view-depth-10.png
(Pdb)
Graphs of randomly selected “leaked” view:
Debugging info: Modified app.py with callback function
❯ python3 -m pdb app-manual-connection-to-callback-function.py
> ./app-manual-connection-to-callback-function.py(1)<module>()
(Pdb) import sys
(Pdb) r
(APP STARTS, I BROWSE 10 VIEWS AND QUIT APP)
ImageView garbage-collected
ImageView garbage-collected
ImageView garbage-collected
ImageView garbage-collected
ImageView garbage-collected
ImageView garbage-collected
ImageView garbage-collected
ImageView garbage-collected
ImageView garbage-collected
--Return--
> ./app-manual-connection-to-callback-function.py(127)<module>()->None
-> sys.exit(exit_status)
(Pdb) import gc
(Pdb) gc.collect()
92
(Pdb) gc.collect()
0
(Pdb) import objgraph
(Pdb) views_tracked_by_gc = objgraph.by_type("ImageView")
(Pdb) len(views_tracked_by_gc)
1
(Pdb) leaked_views = objgraph.get_leaking_objects(views_tracked_by_gc)
(Pdb) len(leaked_views)
1
(Pdb) leaked_view = leaked_views[0]
(Pdb) leaked_view
<__main__.ImageView object at 0x7f6b62935240 (ImageView at 0x3205ec0)>
(Pdb) sys.getrefcount(leaked_view)
5
(Pdb) objgraph.show_chain(objgraph.find_backref_chain(leaked_view, objgraph.is_proper_module), filename="/tmp/app-with-callback-functions-leaked-view.png")
Graph written to /tmp/objgraph-d99bzl3b.dot (3 nodes)
Image generated as /tmp/app-with-callback-functions-leaked-view.png
(Pdb) objgraph.show_backrefs([leaked_view], max_depth=10, filename="/tmp/app-with-callback-functions-leaked-view-depth-10.png")
Graph written to /tmp/objgraph-1rwt8bvt.dot (47 nodes)
Image generated as /tmp/app-with-callback-functions-leaked-view-depth-10.png
(Pdb)



