<div dir="ltr">Phil, Kovid, and all,<div><br></div><div>Thanks for your thoughts on this. The difficulty of correctly reconciling Qt's memory management scheme (you must insure all objects are destroyed in the correct order) with Python's (the interpreter chooses an order which you have little control over) has puzzled me for a while. The following code crashes several ways, even with Kovid's suggestion. I'm running under Qt 5.5.1, SIP 4.17, PtQt 5.5.1, Python 3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:44:40) [MSC v.1600 64 bit (AMD64)], OS nt, platform Windows 7.</div><div><br></div><div>Bryan</div><div><br></div><div><div><font face="monospace, monospace">import sys</font></div><div><font face="monospace, monospace">import os</font></div><div><font face="monospace, monospace">import platform</font></div><div><font face="monospace, monospace">import gc</font></div><div><font face="monospace, monospace">import sip</font></div><div><font face="monospace, monospace">from PyQt5.QtWidgets import QApplication, QWidget</font></div><div><font face="monospace, monospace">from PyQt5.QtCore import QT_VERSION_STR</font></div><div><font face="monospace, monospace">from PyQt5.Qt import PYQT_VERSION_STR</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">app = None</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">def printDestroyed(qObject):</font></div><div><font face="monospace, monospace"> def destroyed():</font></div><div><font face="monospace, monospace"> print('destroyed {}'.format(qObject))</font></div><div><font face="monospace, monospace"> qObject.destroyed.connect(destroyed)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">def quickCrash():</font></div><div><font face="monospace, monospace"> print('quickCrash')</font></div><div><font face="monospace, monospace"> QApplication(sys.argv)</font></div><div><font face="monospace, monospace"> QWidget()</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">def slowCrash():</font></div><div><font face="monospace, monospace"> global app</font></div><div><font face="monospace, monospace"> print('slowCrash')</font></div><div><font face="monospace, monospace"> app = QApplication(sys.argv)</font></div><div><font face="monospace, monospace"> printDestroyed(app)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"> w = QWidget()</font></div><div><font face="monospace, monospace"> printDestroyed(w)</font></div><div><font face="monospace, monospace"> w.show()</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"> sip.delete(app)</font></div><div><font face="monospace, monospace"> sip.delete(w)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">def qtMain():</font></div><div><font face="monospace, monospace"> global app</font></div><div><font face="monospace, monospace"> app = QApplication(sys.argv)</font></div><div><font face="monospace, monospace"> printDestroyed(app)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"> w = QWidget()</font></div><div><font face="monospace, monospace"> printDestroyed(w)</font></div><div><font face="monospace, monospace"> w.show()</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">def gcCrash():</font></div><div><font face="monospace, monospace"> print('gcCrash')</font></div><div><font face="monospace, monospace"> qtMain()</font></div><div><font face="monospace, monospace"> gc.collect()</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">def main():</font></div><div><font face="monospace, monospace"> print('Qt {}, SIP {}, PtQt {}, Python {}, OS {}, platform {} {}'.format(</font></div><div><font face="monospace, monospace"> QT_VERSION_STR, sip.SIP_VERSION_STR, PYQT_VERSION_STR, sys.version,</font></div><div><font face="monospace, monospace"> <a href="http://os.name">os.name</a>, platform.system(), platform.release()))</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace"> crashDict = {'q' : quickCrash, 's' : slowCrash, 'g' : gcCrash}</font></div><div><font face="monospace, monospace"> try:</font></div><div><font face="monospace, monospace"> crashDict[sys.argv[1]]()</font></div><div><font face="monospace, monospace"> except (IndexError, KeyError):</font></div><div><font face="monospace, monospace"> quickCrash()</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">if __name__ == '__main__':</font></div><div><font face="monospace, monospace"> main()</font></div></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Feb 10, 2016 at 9:42 AM, Phil Thompson <span dir="ltr"><<a href="mailto:phil@riverbankcomputing.com" target="_blank">phil@riverbankcomputing.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">On 9 Feb 2016, at 4:37 am, Kovid Goyal <<a href="mailto:kovid@kovidgoyal.net">kovid@kovidgoyal.net</a>> wrote:<br>
><br>
> Surely, the advice should be to keep a module level reference to the<br>
> application global rather than to run code at module level. Like this:<br>
><br>
> app = None<br>
><br>
> def main():<br>
> global app<br>
> app = QApplication([])<br>
> app.exec_()<br>
><br>
> That way you get both behaviors. Although, in my experience, you cannot<br>
> avoid segfaults on exit by relying on sip.setdestroyonexit().<br>
<br>
</span>It might be worth taking a step back on this...<br>
<br>
Crashes are caused by C++ dtors being invoked (via the Python garbage collector) in an order that Qt is not happy with. The order is, in effect, random. Typically this happens at the end of a "scope", the most significant of which is when the interpreter exits.<br>
<br>
Probably (but it is a guess) it would be best if the QApplication instance was destructed last of all.<br>
<br>
PyQt5 disables the invocation of dtors when the interpreter is exiting. Therefore if the following pattern is used...<br>
<br>
if __name__ = '__main__':<br>
app = QApplication([])<br>
gui = QWidget()<br>
gui.show()<br>
app.exec()<br>
<br>
...there shouldn't be a problem with crashes on exit. If anybody has an example where they think this is not the case then I'd like to know.<br>
<br>
However it is not always possible to follow that pattern - setuptools requires a function entry point. When that function (ie. "scope") returns then the local objects can be garbage collected in a random order. Because the interpreter is still running, the dtors are still invoked.<br>
<br>
PyQt5 tries to mitigate this to a certain extent. When a QApplication is garbage collected it first makes sure that any top-level widgets that still exist are owned by C++. This effectively disables the dtors of those widgets. However it does mean that widgets may still outlive the C++ QApplication instance - and maybe Qt doesn't like that.<br>
<br>
The suggestion above (ie. the global reference to the QApplication object) has the effect of guaranteeing that the QApplication instance will outlive any objects that are garbage collected when main() returns. In fact the QApplication dtor will never be invoked as the object will only be garbage collected when the interpreter exits.<br>
<br>
This pattern, therefore, should also avoid any crashes on exit. Again, if anybody has a counter example then I'd like to know.<br>
<br>
I am considering changing the behaviour when the QApplication object gets garbage collected. Instead of transferring ownership of any top-level widgets it would instead explicitly invoke their dtors. This would guarantee the QApplication outlives any widgets (as the global reference trick does) but it would not be able to make the same guarantee regarding any other objects that might be referenced at the global level. However I think it would be an improvement over the current behaviour.<br>
<br>
PyQt4 has a mechanism for tracking objects of certain classes (actually it is only done for QSystemTrayIcon) so that they get handled in a similar way to top-level widgets. I'd consider adding the same mechanism to PyQt5 if it turned out that crashes always involved certain classes. I'd also consider exposing that mechanism to applications to that they could add specific objects to a cleanup handler that is invoked just before the QApplication dtor.<br>
<br>
As ever, feedback and comments welcome.<br>
<div class="HOEnZb"><div class="h5"><br>
Phil<br>
_______________________________________________<br>
PyQt mailing list <a href="mailto:PyQt@riverbankcomputing.com">PyQt@riverbankcomputing.com</a><br>
<a href="https://www.riverbankcomputing.com/mailman/listinfo/pyqt" rel="noreferrer" target="_blank">https://www.riverbankcomputing.com/mailman/listinfo/pyqt</a></div></div></blockquote></div><br><br clear="all"><div><br></div>-- <br><div class="gmail_signature">Bryan A. Jones, Ph.D.<br>Associate Professor<br>Department of Electrical and Computer Engineering<br>231 Simrall / PO Box 9571<br>Mississippi State University<br>Mississippi state, MS 39762<br><a href="http://www.ece.msstate.edu/~bjones" target="_blank">http://www.ece.msstate.edu/~bjones</a><br>bjones AT ece DOT msstate DOT edu<br>voice 662-325-3149<br>fax 662-325-2298<br><br>Our Master, Jesus Christ, is on his way. He'll show up right on<br>time, his arrival guaranteed by the Blessed and Undisputed Ruler,<br>High King, High God.<br>- 1 Tim. 6:14b-15 (The Message)<br></div>
</div>