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: