[PyQt] QProcess hangup using PyQt + threads
Phil Thompson
phil at riverbankcomputing.com
Fri Feb 26 12:31:02 GMT 2010
On Fri, 26 Feb 2010 12:08:58 +0000, Phil Thompson
<phil at riverbankcomputing.com> wrote:
> On Fri, 26 Feb 2010 11:05:44 +0100, Denis RIVIERE <denis.riviere at cea.fr>
> wrote:
>> 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 ?
>
> Unless you can provide me with a test case you'll have to experiment...
>
> Try adding...
>
> pthread_atfork(NULL, NULL, PyOS_AfterFork);
>
> ...after the call to PyEval_InitThreads() in siplib.c.
An alternative (which I may be more comfortable with) is to add the
/HoldGIL/ annotation to all QProcess.start() overloads. However the
asynchronous nature of process starts may mean it has no effect.
Phil
More information about the PyQt
mailing list