[PyQt] QProcess hangup using PyQt + threads
Denis RIVIERE
denis.riviere at cea.fr
Fri Feb 26 10:05:44 GMT 2010
We have run into what we think to be more or less a bug in PyQt4, in QProcess,
somewhat indirectly.
QProcess sometimes hangsup in threaded PyQt applications, at least on Linux
systems.
The situation is the following:
- A PyQt program is using two or more threads (say, python threads)
- one of the threads is using a QProcess (whatever thread), while the other is
using python (of course).
We think we have understood what is going on:
Internally, QProcess forks then probably uses a kind of exec().
But the child forked process sometimes gets locked on the python GIL lock.
Then the calling process waits for the child, and also gets hung on a select()
call.
Actually, when forking a multi-threaded process, only the forking thread is
duplicated: so the child process has only 1 thread. But the locks, mutexes
and other threading items have kept the state they had in their parent
process: some are locked, but the other threads which would normally release
them do not exist in the child.
Here, the python GIL happens to be locked by another thread in the parent
process, and the child process seems to try to lock the GIL too. We guess
QProcess calls a virtual method in the forked process, which is overloaded by
SIP/PyQt, which in turn calls the Python API, and finally gets hung trying to
lock the GIL.
We have never encountered this problem using PyQt3, maybe because QProcess or
its PyQt binding may be implemented differently.
This threading + fork problem is a known difficulty in fork(), explained in
the linux manpages for instance.
We have 2 possible solutions for this problem:
1. Using pthreads, it is possible to define handlers functions that can take
care of locking/unlocking the needed locks whenever fork() is called, both in
the parent and child processes: this is done using the pthread_atfork()
function. Typically PyQt may define such handlers to take care of the GIL
locking when fork() is used. However we don't know if such a mechanism exists
on non-pthread systems (Windows?). We also don't really know if the problem
exists on Windows; possibly not because Windows doesn't use fork() but
probably rather a spawn equivalent (?).
2. The problem actually happens because the python API gets called from the
forked process. Normally, in C++ Qt, the QProcess doesn't trigger such a
locking problem. However the SIP binding defines a subclass which traps all
virtual methods calls and redirects them to python. So if we are not using a
sip-inherited QProcess class, the problem should not happen. We have made the
test, making a sip-bound factory function which instantiates a QProcess from
C++, then returns it to the pyton layer. This way no inherited class is used,
and the problem is avoided. And it actually works.
Would it be difficult to have solution 1 included in the standard PyQt ?
Denis
More information about the PyQt
mailing list