Threading: PyQt crashes with “unknown request in q

2020-06-04 03:29发布

问题:

One part of an application I'm developing needs to send some emails to a small group of people. Since it may take a little while to connect to the SMTP server and send the emails, I want to provide a progress bar during this operation. I've only barely used threading before, so using QThread to do it has proven somewhat difficult for me.

What happens now is that I can implement a test structure that works just fine, but then as soon as I try to create an object from the backend of my application to actually do any emailing operations, it crashes completely (as though it had segfaulted), dumping this to the console:

[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
python: ../../src/xcb_io.c:179: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.
Aborted

The only relevant thread I found searching for these errors said something about the signals being implemented wrong (for PySide, PySide and QProgressBar update in a different thread), but in my case the signals work totally fine until I try to create that object (which isn't based on Qt classes at all).

Here's a simplified version of my GUI code:

class SendingDialog(QtGui.QDialog):
    def __init__(self, parent, optsDict, cls, zid):
        QtGui.QDialog.__init__(self)
        self.form = Ui_Dialog()
        self.form.setupUi(self)
        # initialize some class variables...

        self.beginConnect()
        self.thread = WorkerThread()
        self.thread.insertOptions(self.opts, self.cls, self.zid)
        self.thread.finished.connect(self.endOfThread)
        self.thread.serverContacted.connect(self.startProgress)
        self.thread.aboutToEmail.connect(self.updateProgress)
        self.thread.start()

    def beginConnect(self):
        # start busy indicator

    def startProgress(self):
        # set up progress bar

    def updateProgress(self):
        # increment progress bar

    def endOfThread(self):
        self.thread.quit()
        self.reject()

class WorkerThread(QtCore.QThread):
    serverContacted = QtCore.pyqtSignal(name="serverContacted")
    aboutToEmail = QtCore.pyqtSignal(name="aboutToEmail")

    def insertOptions(self, opts, cls, zid):
        self.opts = opts
        self.cls = cls
        self.zid = zid

    def run(self):
        # upon running the following line, the application crashes.
        emailman = db.emailing.EmailManager(self.opts, self.cls, self.zid)

If I put some dummy code into run() that sleeps, emits the appropriate signals, or prints test values, everything works fine; but as soon as I try to instantiate the EmailManager, the whole thing crashes.

EmailManager is an unremarkable class derived from object, taking the parameters I've given it (opts is a dictionary, cls is a different type of similarly unremarkable object, and zid is just a plain number). The constructor looks like this:

def __init__(self, optsDict, cls, zid):
    self.opts = optsDict
    self.cls = cls
    self.historyItem = HistoryItem(zid)
    self.studentsList = studentsInClass(cls)
    self.connection = None

I'm constructing a couple of other objects based on the parameters, but other than that, nothing complicated or unusual is happening. The code in the db.emailing module does not use Qt or threading at all.

I don't even know how to begin debugging this, so any advice as to what might be going on or how I could try to find out would be very much appreciated.

Edit: In case it's helpful, here's the backtrace from gdb (I don't know enough about what's going on to find it helpful):

Program received signal SIGABRT, Aborted.
[Switching to Thread 0x7fffeb146700 (LWP 31150)]
0x00007ffff762acc9 in __GI_raise (sig=sig@entry=6)
    at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56  ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) bt
#0  0x00007ffff762acc9 in __GI_raise (sig=sig@entry=6)
    at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
#1  0x00007ffff762e0d8 in __GI_abort () at abort.c:89
#2  0x00007ffff7623b86 in __assert_fail_base (
    fmt=0x7ffff7774830 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n", 
    assertion=assertion@entry=0x7ffff6a4420d "!xcb_xlib_unknown_req_in_deq", file=file@entry=0x7ffff6a441db "../../src/xcb_io.c", line=line@entry=179, 
    function=function@entry=0x7ffff6a446b0 "dequeue_pending_request")
    at assert.c:92
#3  0x00007ffff7623c32 in __GI___assert_fail (
    assertion=0x7ffff6a4420d "!xcb_xlib_unknown_req_in_deq", 
    file=0x7ffff6a441db "../../src/xcb_io.c", line=179, 
    function=0x7ffff6a446b0 "dequeue_pending_request") at assert.c:101
#4  0x00007ffff69d479c in ?? () from /usr/lib/x86_64-linux-gnu/libX11.so.6
#5  0x00007ffff69d55c3 in _XReply ()
   from /usr/lib/x86_64-linux-gnu/libX11.so.6
#6  0x00007ffff69bc346 in XGetWindowProperty ()
   from /usr/lib/x86_64-linux-gnu/libX11.so.6
#7  0x00007ffff69bb22e in XGetWMHints ()
   from /usr/lib/x86_64-linux-gnu/libX11.so.6
#8  0x00007ffff4c87c4b in QWidgetPrivate::setWindowIcon_sys(bool) ()
   from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#9  0x00007ffff4c38405 in QWidget::create(unsigned long, bool, bool) ()
   from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#10 0x00007ffff4c4086a in QWidget::setVisible(bool) ()
   from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#11 0x00007ffff509956e in QDialog::setVisible(bool) ()
   from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#12 0x00007ffff5c24b7c in ?? ()
   from /usr/lib/python2.7/dist-packages/PyQt4/QtGui.so
#13 0x00007ffff5099026 in QDialog::exec() ()
   from /usr/lib/x86_64-linux-gnu/libQtGui.so.4
#14 0x00007ffff5be5fb5 in ?? ()
   from /usr/lib/python2.7/dist-packages/PyQt4/QtGui.so
#15 0x000000000049968d in PyEval_EvalFrameEx ()
#16 0x00000000004a090c in PyEval_EvalCodeEx ()
#17 0x0000000000499a52 in PyEval_EvalFrameEx ()
#18 0x00000000004a1c9a in ?? ()
#19 0x00000000004dfe94 in ?? ()
#20 0x00000000004dc9cb in PyEval_CallObjectWithKeywords ()
#21 0x000000000043734b in PyErr_PrintEx ()
#22 0x00007ffff186fd4d in ?? ()
   from /usr/lib/python2.7/dist-packages/sip.so
#23 0x00007ffff14b2ece in ?? ()
   from /usr/lib/python2.7/dist-packages/PyQt4/QtCore.so
#24 0x00007ffff45be32f in ?? ()
   from /usr/lib/x86_64-linux-gnu/libQtCore.so.4
#25 0x00007ffff79c1182 in start_thread (arg=0x7fffeb146700)
    at pthread_create.c:312
#26 0x00007ffff76ee47d in clone ()
    at ../sysdeps/unix/sysv/linux/x86_64/clone.S:111

回答1:

Wow, this was obscure.

The X11 windowing functions are apparently not threadsafe unless explicitly set to be so, and for whatever reason PyQt doesn't automatically set them to be. This can be corrected by adding the following before the QApplication constructor:

QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)

See the documentation on QApplicationAttributes.

For what it's worth, EmailManager now does not work correctly when launched in a thread, because it uses a shared SQLite database connection to construct other objects, which is a no-go with the sqlite3 library. I'm also getting segmentation faults now. But this particular problem is solved, at least.