简体   繁体   English

PyQt5崩溃与QFileSystemModel和QSortFilterProxyModel ..做错什么了吗?

[英]PyQt5 Crash with QFileSystemModel and QSortFilterProxyModel..doing something wrong?

I've had an intermittent problem with a crash when navigating a directory structure with pyqt5 on Ubuntu. 在Ubuntu上使用pyqt5导航目录结构时,出现了间歇性的崩溃问题。 Sometimes just navigating to a folder crashes, other times it is triggered by external changes to the directory being displayed. 有时,仅浏览到一个文件夹会崩溃,而其他时候,它是由对所显示目录的外部更改触发的。 The following code is an example of the latter. 以下代码是后者的示例。

The application I'm building checks the directory being displayed when the program is activated. 我正在构建的应用程序会在激活程序时检查正在显示的目录。 If it is no longer a valid directory, it moves up the tree to next valid directory. 如果它不再是有效目录,它将在树中上移到下一个有效目录。 The code below simulates that by creating a directory structure, navigating down to a file and selecting it, renames the tree, then moves from the no longer valid directory up to one that is. 下面的代码通过创建目录结构,导航到文件并选择它,重命名树,然后从不再有效的目录移动到该目录,来模拟这种情况。 If I run this code a few times I will see it crash, sometimes on the first execution, sometimes after a few runs. 如果我多次运行此代码,有时会在第一次执行时,有时在几次运行后会崩溃。

I tried updating the mode based on seeing proxy model function in the gdb stack trace. 我尝试根据在gdb堆栈跟踪中看到的代理模型功能来更新模式。 This seemed to fix my problem, though this seems wrong to me. 这似乎解决了我的问题,尽管这对我来说似乎是错误的。

I had written it off as fixed, even though I wasn't happy with the solution, but today I got crashes in this code again even with the model update. 尽管我对解决方案不满意,但我已将其固定修复,但是今天即使进行了模型更新,我也再次在代码中崩溃。

There's not a lot of sample code out there showing how to use these classes together. 那里没有很多示例代码显示如何一起使用这些类。 Anyone see where I'm doing something wrong? 有人看到我在做什么错吗?

import os, sys, tempfile
from PyQt5 import QtCore, QtWidgets


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)
        self._view = QtWidgets.QTableView()
        layout.addWidget(self._view)

        self._model = QtWidgets.QFileSystemModel()
        self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)

        self._proxy = QtCore.QSortFilterProxyModel(self)
        self._proxy.setSourceModel(self._model)
        self._view.setModel(self._proxy)

        header = self._view.horizontalHeader()
        header.setSortIndicator(0, QtCore.Qt.AscendingOrder)
        self._proxy.sort(header.sortIndicatorSection(), header.sortIndicatorOrder())

        # Create a temporary directory structure (tmpxyz/a/b/c) starting at the location of this file
        # This is oddly very specific..less nesting or no file to select doesn't exhibit the problem!?
        path = os.path.dirname(os.path.abspath(__file__))
        self.temp_dir = tempfile.TemporaryDirectory(dir=path)
        print('created temporary directory', self.temp_dir.name)
        foo_dir = os.path.join(self.temp_dir.name, 'foo')
        os.mkdir(foo_dir)
        self.a_dir = os.path.join(foo_dir, 'a')
        os.mkdir(self.a_dir)
        b_dir = os.path.join(self.a_dir, 'b')
        os.mkdir(b_dir)
        c_dir = os.path.join(b_dir, 'c')
        os.mkdir(c_dir)
        self.temp_file_name = os.path.join(c_dir, 'foo.txt')
        with open(self.temp_file_name, 'w') as txtfile:
            print('foo', file=txtfile)

        self._model.setRootPath(c_dir)

        # moved the following to loaded..didn't seem to help
        # source_index = self._model.index(c_dir)
        # index = self._proxy.mapFromSource(source_index)
        # self._view.setRootIndex(index)

        QtCore.QTimer.singleShot(4000, self._select_temp_file)
        QtCore.QTimer.singleShot(5000, self._rename_test_dir)

        self._model.directoryLoaded.connect(self._loaded)

    def _loaded(self):
        print('_loaded')
        path = self._model.rootPath()
        print('_loaded', path)
        source_index = self._model.index(path)
        index = self._proxy.mapFromSource(source_index)
        self._view.setRootIndex(index)

    def _select_temp_file(self):
        source_index = self._model.index(self.temp_file_name)
        if source_index.isValid():
            index = self._proxy.mapFromSource(source_index)
            self._view.setCurrentIndex(index)

    # Simulate an external application moving the current directory
    def _rename_test_dir(self):
        os.rename(self.a_dir, self.a_dir+'x')
        path = self._model.rootPath()
        print('_rename_test_dir', path)
        if not os.path.isdir(path):
            def _make_valid_path(path_):
                if os.path.isdir(path_):
                    return path_
                while path_:
                    path_, _ = os.path.split(path_)
                    if os.path.isdir(path_):
                        return path_
                return '/'

            print('Path not valid:', path)
            path = _make_valid_path(path)
            print('New Path:', path)

            self._model.setRootPath(path)
            print('_rename_test_dir 5')
            # Using gdb showed a crash in the proxy model..so update it here...decreases crash frequency
            # self._proxy.setSourceModel(self._model)
            # print('_rename_test_dir 6')
            # self._view.setModel(self._proxy)
            print('_rename_test_dir 7')

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    widget = Widget()
    widget.show()
    sys.exit(app.exec_())

UPDATE: 更新:

In case it is not clear from the code, the program requires no interaction and interaction can interfere with the test. 如果代码中不清楚,则该程序不需要交互,并且交互会干扰测试。 After the directory structure is created it will automatically go to the leaf level of the structure. 创建目录结构后,它将自动进入结构的叶级。 After 4 seconds it will select the foo.txt file. 4秒钟后,它将选择foo.txt文件。 Another second later it will rename the 'a' directory in the tree and then navigate back up the tree to where 'a' used to be. 再过一秒钟,它将在树中重命名“ a”目录,然后在树上导航回到“ a”所在的位置。 If all goes well the foo directory will be displayed where the 'ax' directory can be seen and the program can be exited and retried. 如果一切顺利,将显示foo目录,从中可以看到'ax'目录,并且可以退出和重试该程序。

Executing under gbd took about 8 times before getting the following stack trace..edited down to last 2 runs: 在获取以下堆栈跟踪之前,在gbd下执行了大约8次。编辑为最后2次运行:

gdb --args python3 test_qfilesystemmodel_crash2.py  
(gdb) run
Starting program: /usr/bin/python3 test_qfilesystemmodel_crash2.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7fffe9ea5700 (LWP 21128)]
[New Thread 0x7fffdb5e5700 (LWP 21129)]
[New Thread 0x7fffdade4700 (LWP 21130)]
created temporary directory /home/myuser/Desktop/haiqu/src/tmp8dl0_uny
_loaded
_loaded /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo/a/b/c
_rename_test_dir /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo/a/b/c
Path not valid: /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo/a/b/c
New Path: /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo
_rename_test_dir 5
_rename_test_dir 7
_loaded
_loaded /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo
[Thread 0x7fffdb5e5700 (LWP 21129) exited]
[Thread 0x7fffe9ea5700 (LWP 21128) exited]
[Thread 0x7ffff7fbb700 (LWP 21127) exited]
[Inferior 1 (process 21127) exited normally]
(gdb) run
Starting program: /usr/bin/python3 test_qfilesystemmodel_crash2.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7fffe9ea5700 (LWP 21132)]
[New Thread 0x7fffdb5e5700 (LWP 21133)]
[New Thread 0x7fffdade4700 (LWP 21134)]
created temporary directory /home/myuser/Desktop/haiqu/src/tmpwr560n5l
_loaded
_loaded /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo/a/b/c
_rename_test_dir /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo/a/b/c
Path not valid: /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo/a/b/c
New Path: /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo
_rename_test_dir 5
_rename_test_dir 7

Thread 1 "python3" received signal SIGSEGV, Segmentation fault.
0x00007ffff5062e89 in QSortFilterProxyModel::sibling(int, int, QModelIndex const&) const ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
(gdb) bt
#0  0x00007ffff5062e89 in QSortFilterProxyModel::sibling(int, int, QModelIndex const&) const ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#1  0x00007ffff568d2d4 in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtCore.cpython-35m-x86_64-linux-gnu.so
#2  0x00007ffff0af35ff in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#3  0x00007ffff00f9779 in QAccessible::updateAccessibility(QAccessibleEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5
#4  0x00007ffff0b028cc in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#5  0x00007ffff0b07486 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#6  0x00007ffff50cb4a9 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#7  0x00007ffff51426e4 in QAbstractItemModel::rowsInserted(QModelIndex const&, int, int, QAbstractItemModel::QPrivateSignal) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#8  0x00007ffff5046f7b in QAbstractItemModel::endInsertRows() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#9  0x00007ffff5068246 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#10 0x00007ffff506a685 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#11 0x00007ffff506e089 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#12 0x00007ffff50cb4a9 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#13 0x00007ffff51426e4 in QAbstractItemModel::rowsInserted(QModelIndex const&, int, int, QAbstractItemModel::QPrivateSignal) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#14 0x00007ffff5046f7b in QAbstractItemModel::endInsertRows() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#15 0x00007ffff0acf6e6 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#16 0x00007ffff0ad51f9 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#17 0x00007ffff50cc359 in QObject::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#18 0x00007ffff0ad58b2 in QFileSystemModel::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#19 0x00007ffff131ecdb in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-35m-x86_64-linux-gnu.so
#20 0x00007ffff08b635c in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#21 0x00007ffff08bdb11 in QApplication::notify(QObject*, QEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#22 0x00007ffff124cdee in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-35m-x86_64-linux-gnu.so
#23 0x00007ffff509f8a0 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#24 0x00007ffff50a202d in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#25 0x00007ffff50f3b03 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#26 0x00007ffff3b35377 in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#27 0x00007ffff3b355e0 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#28 0x00007ffff3b3568c in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#29 0x00007ffff50f3f0f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#30 0x00007ffff509d88a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#31 0x00007ffff50a5ffc in QCoreApplication::exec() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#32 0x00007ffff1210ddb in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-35m-x86_64-linux-gnu.so
#33 0x000055555571b28f in PyCFunction_Call ()
#34 0x00005555556d21e9 in PyEval_EvalFrameEx ()
#35 0x00005555556d6d16 in ?? ()
#36 0x00005555556d7a1f in PyEval_EvalCode ()
#37 0x00005555557a4b02 in ?? ()
#38 0x00005555557a6f8d in PyRun_FileExFlags ()
#39 0x00005555557a772e in PyRun_SimpleFileExFlags ()
#40 0x00005555557d72e7 in Py_Main ()
#41 0x0000555555667b31 in main ()
(gdb) 

I'm writing this off as an old library problem. 我把它写成一个旧的图书馆问题。 After updating to pyqt5.8 and python3.6 I am no longer able to reproduce the crash. 更新到pyqt5.8和python3.6后,我不再能够重现崩溃。 Thanks for the help. 谢谢您的帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM