PyQt: 使用QApplication.quit时偶尔出现段错误

5

注意:我也在PyQt邮件列表上发布了这个问题 - 如果那里有一个好的答案,我会在这里回答自己的问题。

当执行QApplication.quit()时,我遇到了偶尔出现的段错误问题,可能与libQt5Network.so和/或QtWebkit有关。

首先,我使用以下3个测试系统:

  • Arch Linux,PyQt 5.2,Qt 5.2.0,Python 3.3.3
  • Ubuntu 13.10,PyQt 5.0.1,Qt 5.0.2,Python 3.3.2在虚拟机中
  • Windows 7,PyQt 5.2,Qt 5.2.0,Python 3.3.3

这些崩溃在Arch上从未发生过,但在Ubuntu上经常发生,在Windows上偶尔发生(尽管Windows只是一个猜测,我只是得到这个python.exe不再工作的提示)。

原始崩溃

我最初在一个大型项目qutebrowser中注意到了这个问题,在Ubuntu上键入:quit时给了我这个堆栈跟踪:

#0  0xb5c296fc in QMutex::lock() () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#1  0xb3bdd97d in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#2  0xb3bdf0d0 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#3  0xb3bd4418 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#4  0xb3bd8b1e in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#5  0xb5dedf10 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#6  0xb5dee48b in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#7  0xb5e59155 in QIODevice::readyRead() ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#8  0xb3bb1f14 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#9  0xb3ba4d99 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#10 0xb3bc03bb in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#11 0xb6483a54 in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/i386-linux-gnu/libQt5Widgets.so.5
#12 0xb6488e66 in QApplication::notify(QObject*, QEvent*) ()
   from /usr/lib/i386-linux-gnu/libQt5Widgets.so.5
#13 0xb6bb7e80 in sipQApplication::notify(QObject*, QEvent*) ()
   from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-33m-i386-linux-gnu.so
#14 0xb5dc737a in QCoreApplication::notifyInternal(QObject*, QEvent*) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#15 0xb5e11f67 in ?? () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#16 0xb5aaf83e in g_main_context_dispatch ()
   from /lib/i386-linux-gnu/libglib-2.0.so.0
#17 0xb5aafbe8 in ?? () from /lib/i386-linux-gnu/libglib-2.0.so.0
#18 0xb5aafca8 in g_main_context_iteration ()
   from /lib/i386-linux-gnu/libglib-2.0.so.0
#19 0xb5e1138f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#20 0xb5dc5c06 in QEventLoop::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#21 0xb5dc6014 in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#22 0xb5c2b90b in QThread::exec() ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#23 0xb5c2b99b in QThread::run() () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#24 0xb5c2fa08 in ?? () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#25 0xb7774d78 in start_thread (arg=0xa5314b40) at pthread_create.c:311
#26 0xb76ac01e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:131

这里是核心转储文件(15MB,gzip)。

最简单的示例

然后我用一个简单的示例再试了一次,使用QTimer自动在一秒钟后退出。在它崩溃之前,我必须循环运行大约一分钟左右:

from PyQt5.QtCore import QUrl, QTimer
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKitWidgets import QWebView

app = QApplication([])
wv = QWebView()
wv.load(QUrl("http://www.heise.de/"))
t = QTimer()
t.timeout.connect(QApplication.quit)
t.start(1000)
wv.show()
app.exec_()

这给了我一个非常相似的堆栈跟踪(在Ubuntu上):
#0  0xb6cfd8d2 in QCoreApplication::postEvent(QObject*, QEvent*, int) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#1  0xb6d21c83 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#2  0xb6d2248b in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#3  0xb3e47935 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#4  0xb3dcf687 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#5  0xb3e483b3 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#6  0xb6d21f10 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#7  0xb6d2248b in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#8  0xb3e43fe5 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#9  0xb3d93b1e in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#10 0xb3d94630 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#11 0xb3d9471b in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#12 0xb6d21f10 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#13 0xb6d2248b in QMetaObject::activate(QObject*, QMetaObject const*, int, void**) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#14 0xb6d8d155 in QIODevice::readyRead() ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#15 0xb3e09f14 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#16 0xb3dfcd99 in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#17 0xb3e183bb in ?? () from /usr/lib/i386-linux-gnu/libQt5Network.so.5
#18 0xb492ba54 in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/i386-linux-gnu/libQt5Widgets.so.5
#19 0xb4930e66 in QApplication::notify(QObject*, QEvent*) ()
   from /usr/lib/i386-linux-gnu/libQt5Widgets.so.5
#20 0xb505fe80 in sipQApplication::notify(QObject*, QEvent*) ()
   from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-33m-i386-linux-gnu.so
#21 0xb6cfb37a in QCoreApplication::notifyInternal(QObject*, QEvent*) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#22 0xb6d45f67 in ?? () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#23 0xb65f483e in g_main_context_dispatch ()
   from /lib/i386-linux-gnu/libglib-2.0.so.0
#24 0xb65f4be8 in ?? () from /lib/i386-linux-gnu/libglib-2.0.so.0
#25 0xb65f4ca8 in g_main_context_iteration ()
   from /lib/i386-linux-gnu/libglib-2.0.so.0
#26 0xb6d4536d in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#27 0xb6cf9c06 in QEventLoop::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#28 0xb6cfa014 in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#29 0xb6b5f90b in QThread::exec() ()
   from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#30 0xb6b5f99b in QThread::run() () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#31 0xb6b63a08 in ?? () from /usr/lib/i386-linux-gnu/libQt5Core.so.5
#32 0xb7798d78 in start_thread (arg=0xa7812b40) at pthread_create.c:311
#33 0xb76d001e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:131
在此处生成核心转储文件(15MB,gzip)。
有人知道这里出了什么问题吗?是否与垃圾回收的方式有关?我还尝试了一些解决 PyQt4 相似症状的方法,但也没有帮助。
* 找不到描述它的 StackOverflow 答案 - 基本上在运行 exec_() 之前将 QtWidgets.qApp 设置为 QApplication 实例,并在之后设置为 None
1个回答

8
我已经花了相当长的时间来捕获PyQt / PySide中的段错误。基本上,我发现大多数段错误都可以归咎于库的异步性质(因此也归咎于我们)。
对于您的特定示例,您将timeout信号连接到quit方法。在timeout事件发生时,可能会调用quit,并且在进程终止时,突然所有对应用程序对象的引用都无效了。但是,当此操作正在进行时,QT的事件循环仍在运行,并且它尝试访问其QNetworkAccessManager以分派另一个信号,但是对该内存位置的引用已经无效,因此导致了段错误。
在这些情况下需要做的是,实现一种关闭方法,确保停止所有操作,在正确的顺序中删除您使用的QT组件,然后才允许退出。
关于这个主题,我在这里写得更详细,包括QtWebKit应用程序的安全关闭方法:https://github.com/integricho/path-of-a-pyqter/tree/master/qttut08

谢谢!您的教程似乎对其他未来问题也很有用。如果您有时间,能否看一下我实现它(针对原始崩溃,而不是最小示例)是否合理? - The Compiler
1
不客气 :) 我看了你的代码,但实际上我还没有测试过。我注意到的只是,正如我在教程中提到的那样,这个 deleteLater 方法也是异步的,所以可能会发生的情况是,你的 shutdown 方法完成后,deleteLater 被调度,应用程序退出,但对象并没有被真正快速地删除。我通过将 destroyed 信号连接到一个方法来解决这个问题,在该方法中检查是否成功删除了所有组件,然后才允许应用程序退出。 - andrean
你说得对,我没有考虑到那个。我现在也已经实现了,至少在我的(少数)测试中,目前还没有出现段错误。 - The Compiler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接