Subject: pkg/30562: qt3 doesn't handle child processes correctly
To: None <pkg-manager@netbsd.org, gnats-admin@netbsd.org,>
From: None <thesing@cs.uni-sb.de>
List: pkgsrc-bugs
Date: 06/20/2005 08:53:00
>Number: 30562
>Category: pkg
>Synopsis: qt3 doesn't handle child processes in QProcess class correctly
>Confidential: no
>Severity: non-critical
>Priority: medium
>Responsible: pkg-manager
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Mon Jun 20 08:53:00 +0000 2005
>Originator: Stephan Thesing
>Release: NetBSD 3.99.5
>Organization:
= Tel.: +49-681-302-5571 = Universitaet des Saarlandes =
= Fax.: +49-681-302-3065 = Postfach 15 11 50 =
= Compiler Research Group = 66041 Saarbruecken =
= FR 6.2 - Informatik = GERMANY =
>Environment:
System: NetBSD gargoyle.cs.uni-sb.de 3.99.5 NetBSD 3.99.5 (Gargoyle) #13: Thu Jun 9 12:47:43 CEST 2005 thesing@gargoyle.cs.uni-sb.de:/local/thesing/netbsd/current/obj/sys/arch/i386/compile.i386/Gargoyle i386
Architecture: i386
Machine: i386
>Description:
The qt3-libs package under pkgsrc/x11/qt3-libs, version 3.3.4 has a Qt inherent bug
in the handling of child processes via the QProcess class (src/kernel/qprocess_unix.cpp).
The problem is the following:
The QProcess class (more exactly, the QProcessManager class) installs a signal handler
to capture SIGCHLD for exiting child processes. In the handler, it writes to a socket,
which again is bound to a QNotifier which notifies the main event loop about child
processes that have finished.
Unfortunately, the old UNIX problem arises: it is not guaranteed that the signal handler
will be called for _every_ child process that exited. In fact, if multiple children
have exited, it will be called only once for them, causing the QProcess class to loose
notification for at least one child. This is more severe since 2.0 as QT Apps are
threaded and thus use scheduler activations with the native pthread library. This means
that the invocation of the signal handler can be delayed quite a bit, if no idle thread
is available to take the handler. ( with PTHREAD_CONCURRENCY >1, this will also hit
assertions in libpthread, but this is a different story).
In one local application, which invokes a number of QProcess classes sequentially, this leads to the
loss of child exit notification somewhere along the way.
>How-To-Repeat:
Invoke a number of QProcesses in a QT Application and wait for them. At some point they will
loose the notification of completed child processes...
>Fix:
the attached patch for x11/qt3-libs does the following:
on NetBSD >=2.0 it uses kqueue to keep track of the occurences of SIGCHLD.
For this, a new QNotifier is added to the QProcessManager class that waits
for events on the kqueue descriptor (kfd in the class).
Instead of installing a signal handler, a new kqueue descriptor is opened, a
filter for SIGCHLD is added and the notifier is bound to the kqueue descriptor.
The handler for the kqueue descriptor determines the number of SIGCHLD signals that
occured and writes one byte per occured signal to the old pipe, which will trigger
the old QNotifier (once per child), which was previously triggered via the signal
handler (once per handler invocation).
For my local application, this fixed the problem.
I have tested this on 3.99.5 and compile tested on 2.0. I did not test on 1.6.x yet.
--- src/kernel/qprocess_unix.cpp.orig 2005-01-21 18:16:11.000000000 +0100
+++ src/kernel/qprocess_unix.cpp 2005-06-19 21:51:33.000000000 +0200
@@ -59,6 +59,11 @@
#include <errno.h>
#include <sys/types.h>
+#if defined(__NetBSD__) && __NetBSD_Version__>=200000000
+#include <sys/event.h>
+#include <sys/time.h>
+#endif
+
#ifdef __MIPSEL__
# ifndef SOCK_DGRAM
# define SOCK_DGRAM 1
@@ -187,15 +192,26 @@
public slots:
void removeMe();
void sigchldHnd( int );
+#if defined(__NetBSD__) && __NetBSD_Version__>=200000000
+ void sigchldHnd2( int );
+#endif
public:
+#if !defined(__NetBSD__) || __NetBSD_Version__<200000000
struct sigaction oldactChld;
+#endif
struct sigaction oldactPipe;
QPtrList<QProc> *procList;
int sigchldFd[2];
-
+#if defined(__NetBSD__) && __NetBSD_Version__>=200000000
+ int kfd;
+ struct kevent kevent;
+#endif
private:
QSocketNotifier *sn;
+#if defined(__NetBSD__) && __NetBSD_Version__>=200000000
+ QSocketNotifier *sn2;
+#endif
};
static void qprocess_cleanup()
@@ -253,7 +269,7 @@
#undef BAILOUT
#endif
-QProcessManager::QProcessManager() : sn(0)
+QProcessManager::QProcessManager() : sn(0), sn2(0)
{
procList = new QPtrList<QProc>;
procList->setAutoDelete( TRUE );
@@ -261,7 +277,7 @@
// The SIGCHLD handler writes to a socket to tell the manager that
// something happened. This is done to get the processing in sync with the
// event reporting.
-#ifndef Q_OS_QNX6
+#if !defined(Q_OS_QNX6)
if ( ::socketpair( AF_UNIX, SOCK_STREAM, 0, sigchldFd ) ) {
#else
if ( qnx6SocketPairReplacement (sigchldFd) ) {
@@ -272,6 +288,40 @@
#if defined(QT_QPROCESS_DEBUG)
qDebug( "QProcessManager: install socket notifier (%d)", sigchldFd[1] );
#endif
+#if defined(__NetBSD__) && __NetBSD_Version__>=200000000
+
+ kfd=kqueue();
+ if (-1==kfd) {
+ qWarning("Cannot create kqueue filter");
+ kfd=0;
+ } else {
+ struct kfilter_mapping km;
+ int n;
+
+ km.name="EVFILT_SIGNAL";
+ if (-1==ioctl(kfd, KFILTER_BYNAME, &km)) {
+ qWarning("Getting filter number failed");
+ close(kfd);
+ kfd=0;
+ } else {
+ bzero(&kevent, sizeof(kevent));
+ kevent.ident=SIGCHLD;
+ kevent.filter=km.filter;
+ kevent.flags=EV_ADD|EV_ENABLE;
+ n=::kevent(kfd, &kevent, 1, NULL, 0, NULL);
+ if (-1==n) {
+ qWarning("Cannot add signal filter");
+ close(kfd);
+ kfd=0;
+ } else {
+ sn2 = new QSocketNotifier(kfd, QSocketNotifier::Read, this);
+ connect( sn2, SIGNAL(activated(int)),
+ this, SLOT(sigchldHnd2(int)) );
+ sn2->setEnabled( TRUE );
+ }
+ }
+ }
+#endif
sn = new QSocketNotifier( sigchldFd[1],
QSocketNotifier::Read, this );
connect( sn, SIGNAL(activated(int)),
@@ -282,6 +332,7 @@
// install a SIGCHLD handler and ignore SIGPIPE
struct sigaction act;
+#if !defined(__NetBSD__) || __NetBSD_Version__<200000000
#if defined(QT_QPROCESS_DEBUG)
qDebug( "QProcessManager: install a SIGCHLD handler" );
#endif
@@ -294,6 +345,7 @@
#endif
if ( sigaction( SIGCHLD, &act, &oldactChld ) != 0 )
qWarning( "Error installing SIGCHLD handler" );
+#endif
#if defined(QT_QPROCESS_DEBUG)
qDebug( "QProcessManager: install a SIGPIPE handler (SIG_IGN)" );
@@ -314,13 +366,18 @@
::close( sigchldFd[0] );
if ( sigchldFd[1] != 0 )
::close( sigchldFd[1] );
-
+#if defined(__NetBSD__) && __NetBSD_Version__>=200000000
+ if (kfd != 0)
+ ::close( kfd );
+#endif
// restore SIGCHLD handler
#if defined(QT_QPROCESS_DEBUG)
qDebug( "QProcessManager: restore old sigchild handler" );
#endif
+#if !defined(__NetBSD__) || __NetBSD_Version__<200000000
if ( sigaction( SIGCHLD, &oldactChld, 0 ) != 0 )
qWarning( "Error restoring SIGCHLD handler" );
+#endif
#if defined(QT_QPROCESS_DEBUG)
qDebug( "QProcessManager: restore old sigpipe handler" );
@@ -362,6 +419,29 @@
}
}
+#if defined(__NetBSD__) && __NetBSD_Version__>=200000000
+void QProcessManager::sigchldHnd2( int fd )
+{
+ char a;
+ int n;
+ struct timespec tm;
+
+ if (!sn2)
+ return;
+
+ tm.tv_sec=0; tm.tv_nsec=0;
+
+ n=::kevent(kfd, NULL, 0, &QProcessPrivate::procManager->kevent, 1, &tm);
+ if (1==n) {
+ a=' ';
+ for (n=QProcessPrivate::procManager->kevent.data; n>0; n--) {
+ if (0!=QProcessPrivate::procManager->sigchldFd[0])
+ ::write( QProcessPrivate::procManager->sigchldFd[0], &a, sizeof(a) );
+ }
+ }
+}
+#endif
+
void QProcessManager::sigchldHnd( int fd )
{
// Disable the socket notifier to make sure that this function is not
@@ -1075,6 +1155,7 @@
*/
bool QProcess::isRunning() const
{
+ pid_t pres;
if ( d->exitValuesCalculated ) {
#if defined(QT_QPROCESS_DEBUG)
qDebug( "QProcess::isRunning(): FALSE (already computed)" );
@@ -1084,7 +1165,8 @@
if ( d->proc == 0 )
return FALSE;
int status;
- if ( ::waitpid( d->proc->pid, &status, WNOHANG ) == d->proc->pid ) {
+ pres=::waitpid( d->proc->pid, &status, WNOHANG );
+ if (pres == d->proc->pid || (-1==pres && errno==ECHILD)) {
// compute the exit values
QProcess *that = (QProcess*)this; // mutable
that->exitNormal = WIFEXITED( status ) != 0;
>Unformatted: