[PyQt] [BUG] PyQt5 default behaviour related to sip.setdestroyonexit()

Vladimir Rutsky rutsky.vladimir at gmail.com
Mon Dec 14 17:08:30 GMT 2015


Attached example crashes under debug Python.

On Ubuntu 14.04 with debug Python 3.4.3, Qt 5.5.1, PyQt 5.5.1:

$ QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1 python pyqt5_destroy_on_exit_test.py
Weak ref <weakref at 0x7f8dc000f358; dead> is dead
Segmentation fault (core dumped)
$

On Windows 7 with debug Python 3.4.3, Qt 5.5.1, PyQt 5.5.1 fails
without additional accessibility configuration.

As far as I understand crash is caused by destructor of
QAccessibleCache (QtGui module private singleton) which tries to
destroy connection to QMenu (destroy[QObject] signal) which has been
already freed by then *but not C++-desctructed*. QMenu object is freed
by gc after interpreter exit because it's trapped in reference cycle.
Sip dealloc doesn't call C++-destructor because interpreter is nulled
and destroy_on_exit is False by default
(sip-4.17/siplib/siplib.c:11130):

static void forgetObject(sipSimpleWrapper *sw)
{
    /*
     * This is needed because we release the GIL when calling a C++ dtor.
     * Without it the cyclic garbage collector can be invoked from another
     * thread resulting in a crash.
     */
    PyObject_GC_UnTrack((PyObject *)sw);

    /*
     * Remove the object from the map before calling the class specific dealloc
     * code.  This code calls the C++ dtor and may result in further calls that
     * pass the instance as an argument.  If this is still in the map then it's
     * reference count would be increased (to one) and bad things happen when
     * it drops back to zero again.  (An example is PyQt events generated
     * during the dtor call being passed to an event filter implemented in
     * Python.)  By removing it from the map first we ensure that a new Python
     * object is created.
     */
    sipOMRemoveObject(&cppPyMap, sw);

   //==============================
   // ! Destructor of C++ object is not called, because sipInterpreter is NULL
   //==============================
   if (sipInterpreter != NULL || destroy_on_exit)
    {
        const sipClassTypeDef *ctd;

        if (getPtrTypeDef(sw, &ctd) != NULL && ctd->ctd_dealloc != NULL)
            ctd->ctd_dealloc(sw);
    }

    clear_access_func(sw);
}


If I change default destroy_on_exit behaviour with
sip.setdestroyonexit(True) everything works correctly. It's not clear
why objects are not destroyed on exit by default.

Anyway, if objects are not destroyed on exit their memory shouldn't be
freed too, otherwise code similar to that of the attached example will
fail.


Regards,

Vladimir Rutsky
-------------- next part --------------
A non-text attachment was scrubbed...
Name: pyqt5_destroy_on_exit_test.py
Type: text/x-python
Size: 1177 bytes
Desc: not available
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20151214/5672b181/attachment.py>


More information about the PyQt mailing list