3

The given code below displays a QMainWindow with 4 QGraphicsView to draw with the mouse in it. It works as intended, but when closing it the following error message appears in the console:

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

What's wrong in the code?


main.py

import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtCore import Qt
from PyQt5.uic import loadUi

# Based on code from https://stackoverflow.com/a/44248794/7481773

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        loadUi("mainwindow.ui", self)

        self.verticalLayout_top_left.addWidget(GraphicsView())
        self.verticalLayout_top_right.addWidget(GraphicsView())
        self.verticalLayout_bottom_left.addWidget(GraphicsView())
        self.verticalLayout_bottom_right.addWidget(GraphicsView())


class GraphicsView(QGraphicsView):
    def __init__(self):
        super().__init__()
        self.start = None
        self.end = None

        self.setScene(QGraphicsScene())
        self.path = QPainterPath()
        self.item = GraphicsPathItem()
        self.scene().addItem(self.item)

        self.contents_rect = self.contentsRect()
        self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def mousePressEvent(self, event):
        self.start = self.mapToScene(event.pos())
        self.path.moveTo(self.start)

    def mouseMoveEvent(self, event):
        self.end = self.mapToScene(event.pos())
        self.path.lineTo(self.end)
        self.start = self.end
        self.item.setPath(self.path)


class GraphicsPathItem(QGraphicsPathItem):
    def __init__(self):
        super().__init__()
        pen = QPen()
        pen.setColor(Qt.black)
        pen.setWidth(5)
        self.setPen(pen)


def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    app.exec_()


if __name__ == "__main__":
    main()

mainwindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="1" column="0">
     <widget class="QPushButton" name="pushButton_left">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="0" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_top_left"/>
    </item>
    <item row="0" column="1">
     <layout class="QVBoxLayout" name="verticalLayout_top_right"/>
    </item>
    <item row="1" column="1">
     <widget class="QPushButton" name="pushButton_right">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="2" column="0">
     <layout class="QVBoxLayout" name="verticalLayout_bottom_left"/>
    </item>
    <item row="2" column="1">
     <layout class="QVBoxLayout" name="verticalLayout_bottom_right"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Atalanttore
  • 349
  • 5
  • 22

2 Answers2

6

This is caused by a long-standing issue that is due to be fixed in an upcoming release (probably PyQt-5.14). If you're using PyQt-5.13.1, you can test the fix by using the following temporary API:

from PyQt5.QtCore import pyqt5_enable_new_onexit_scheme

pyqt5_enable_new_onexit_scheme(True)

If there are no reported problems from this, it will eventually become the default behaviour (so there will be no need to enable it explicitly). The underlying problem that causes the issue is documented here:

Essentially, Python's garbage-collection scheme deletes objects in an unpredictable order, and this can sometimes result in Qt trying to delete objects that no longer exist (resulting in a crash). So the example in the question could also be fixed like this:

def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    app.exec_()
    # ensure correct deletion order
    del main_window, app

The upcoming changes noted above will mean that this kind of clean-up code will no longer be necessary.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Great explanation! – Atalanttore Dec 01 '19 at 15:35
  • Are there any updates on this issue? I have a case where adding a call to app.processEvents() starts causing these crashes. The fix outlined by @ekhumoro does not fix it. I am running PyQt 5.12 on a Mac, since that is the latest version available on conda. I will try testing it on Linux soon. – Ray Osborn May 25 '20 at 17:03
  • @RayOsborn The updates are specified in the first part of my answer. As stated, the "fix" in the second part only applies to the example in the question. If you have a different problem, you should ask a new question (with a [mcve]), since no else will see your comments here. – ekhumoro May 25 '20 at 20:12
  • Thank you for answering. I really appreciate the time people spend answering questions here - it has helped me enormously over the years, but if this "minimal reproducible example" has really become a rule, then I think it is a retrograde step. My application has tens of thousands of lines of code, so the problems probably arise because of the way they interlock. A simple example just isn't alway possible. People often come here to have experienced people give them advice, not necessarily explicitly solve the problem. Please don't discourage such questions. – Ray Osborn May 25 '20 at 21:28
  • By the way, someone on the PyQt mailing list just showed me how to fix the problem by using the QApplication allWidgets function to close all the top-level widgets explicitly, so deleting the main window and then the QApplication were not necessary after all. He had experienced similar problems to me and was able to guide me to the solution without a MRE. – Ray Osborn May 25 '20 at 22:48
  • @RayOsborn I don't understand why people make such a fuss about MREs. If you can provide one, do so - otherwise, just explain why you can't. Questions with an MRE are much more likely to be answered since they make it easier to reproduce and diagnose the problem. AFAICS from the PyQt mailing list thread, the solution suggested there is effectively the same as the one in my answer - i.e. delete the relevant objects in a more predictable order. The real solution, though, is to upgrade to the latest version of PyQt. If that doesn't work, it's probably a bug, and should be reported as such. – ekhumoro May 25 '20 at 23:16
  • @ekhumoro, sorry if I seemed a bit churlish, but I was told when I posted a related question yesterday that providing an MRE was now a stackoverflow rule, which I found bizarre. I'm delighted if it's not. Of course upgrading would be ideal, but users of my application have a variety of versions of PyQt5 on their systems, depending on what conda or Fedora, etc, provide. When there is a bug like this, I have no option but to find a work-around that is backwardly compatible. Thanks for your help. – Ray Osborn May 26 '20 at 04:01
3

The problem is caused because even when you finish running app.exec_() the application still releases resources that need access to the instance of QApplication but as in your case "app" is removed before doing so by making the internal functions of Qt Access unreserved memory.

Considering the above, a possible solution is to extend the scope of "app" so that it is not removed after executing main function by making a global variable.

# ...
app = None


def main():
    global app
    app = QApplication(sys.argv)
    # ...
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • It's the first time I've encountered this error with PyQt. Why is "app" removed too early in this code? – Atalanttore Nov 30 '19 at 22:03
  • @Atalanttore Because "app" is a local variable – eyllanesc Nov 30 '19 at 22:04
  • For most of my other PyQt5 applications "app" is also a local variable within the `main()` function and these can be closed without any error message appearing in console. What is different about this code? – Atalanttore Nov 30 '19 at 22:32
  • 1
    @Atalanttore Perhaps in these cases resources are not created that take time for their release, so analyzing where the problem is would be to analyze the private API of Qt but that takes too much time so my solution is what I normally use as a workaround. – eyllanesc Nov 30 '19 at 22:47
  • 1
    Interesting. I cannot reproduce this behavior on Linux (I've to say, with rather old versions, Python 3.4 and PyQt 5.7.1). Does `sys.exit(app.exec())` change the result? – musicamante Nov 30 '19 at 22:54
  • 2
    @musicamante I can reproduce the problem in Linux (Arch Linux) with PyQt5 5.13.2 in Python3.7/Python3.8 – eyllanesc Nov 30 '19 at 22:58