Em sexta-feira, 4 de fevereiro de 2011, às 05:24:26, Dawit A escreveu:
> > In the meantime, I've been discussing with Rémi about the issue and he's
> > not budging from his position that libraries shoul
> >  never use Unix signals. To be
> > honest, he's right: Unix signals are meant to be used centrally only.
> > But we need SIGCHLD to be notified that a child exited, so we need a
> > compromise between apps and libraries.
> 
> That is especially true for QProcess. It is used in several place
> within kdelibs. If any of those code paths are hit from an app like
> VLC, the same issue will manifest itself as well. I am still wondering
> whether or not the QtCreator freeze I get to this day is somehow
> related to this issue even though I doubt QtCreator does any signal
> blocking...

Qt workaround patch.

I will most definitely not add this to 4.7. I will show to other engineers in 
the office and we'll consider 4.8.

http://qt.pastebin.com/U6dF8kzd

-- 
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
  Senior Product Manager - Nokia, Qt Development Frameworks
      PGP/GPG: 0x6EF45358; fingerprint:
      E067 918B B660 DBD1 105C  966C 33F5 F005 6EF4 5358
>From 3fcabe791c4d6b939e6d43b7fbd4d8b4bb792e0c Mon Sep 17 00:00:00 2001
From: Thiago Macieira <thiago.macie...@nokia.com>
Date: Fri, 4 Feb 2011 17:39:09 +0100
Subject: [PATCH] Make QProcess on Unix use threads, not SIGCHLD.

This makes each process started spawn a new thread, which
will block on waitpid(2). There's no thread reusal or a
thread pool (we need one thread running per subprocess being
watched).

We really need instead the kernel to give us a file descriptor
that does the job of this thread.

I don't like this solution, as it's wasteful of resources. I'm
working on a solution that won't start the thread unless the
stderr and stdout pipes close (if they were open in the first place)
but the subprocess didn't exit. Those kinds of processes are
rare and I would accept the resource usage.

Reviewed-By: no one yet
---
 src/corelib/io/qprocess.cpp      |    8 +-
 src/corelib/io/qprocess_p.h      |    3 +
 src/corelib/io/qprocess_unix.cpp |  249 ++++++--------------------------------
 3 files changed, 48 insertions(+), 212 deletions(-)

diff --git a/src/corelib/io/qprocess.cpp b/src/corelib/io/qprocess.cpp
index 1ce1256..8575f0d 100644
--- a/src/corelib/io/qprocess.cpp
+++ b/src/corelib/io/qprocess.cpp
@@ -757,6 +757,7 @@ QProcessPrivate::QProcessPrivate()
 #endif // Q_WS_WIN
 #ifdef Q_OS_UNIX
     serial = 0;
+    unixWaiterThread = 0;
 #endif
 #ifdef Q_OS_SYMBIAN
     symbianProcess = NULL;
@@ -826,14 +827,15 @@ void QProcessPrivate::cleanup()
         qDeleteInEventHandler(notifier);
         notifier = 0;
     }
+#ifdef Q_OS_UNIX
+    serial = 0;
+    cleanUpWaiterThread();
+#endif
     destroyPipe(stdoutChannel.pipe);
     destroyPipe(stderrChannel.pipe);
     destroyPipe(stdinChannel.pipe);
     destroyPipe(childStartedPipe);
     destroyPipe(deathPipe);
-#ifdef Q_OS_UNIX
-    serial = 0;
-#endif
 #ifdef Q_OS_SYMBIAN
     if (symbianProcess) {
         symbianProcess->Close();
diff --git a/src/corelib/io/qprocess_p.h b/src/corelib/io/qprocess_p.h
index be4f2a0..1b317be 100644
--- a/src/corelib/io/qprocess_p.h
+++ b/src/corelib/io/qprocess_p.h
@@ -76,6 +76,7 @@ QT_BEGIN_NAMESPACE
 class QSocketNotifier;
 class QWindowsPipeWriter;
 class QWinEventNotifier;
+struct QUnixWaiterThread;
 class QTimer;
 #if defined(Q_OS_SYMBIAN)
 class RProcess;
@@ -205,6 +206,7 @@ public:
     void startProcess();
 #if defined(Q_OS_UNIX) && !defined(Q_OS_SYMBIAN)
     void execChild(const char *workingDirectory, char **path, char **argv, char **envp);
+    void cleanUpWaiterThread();
 #endif
     bool processStarted();
     void terminateProcess();
@@ -225,6 +227,7 @@ public:
     QProcess::ExitStatus exitStatus;
     bool crashed;
 #ifdef Q_OS_UNIX
+    QUnixWaiterThread *unixWaiterThread;
     int serial;
 #endif
 
diff --git a/src/corelib/io/qprocess_unix.cpp b/src/corelib/io/qprocess_unix.cpp
index 5f53300..b249538 100644
--- a/src/corelib/io/qprocess_unix.cpp
+++ b/src/corelib/io/qprocess_unix.cpp
@@ -105,6 +105,7 @@ QT_END_NAMESPACE
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
+#include <pthread.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -119,21 +120,6 @@ static inline char *strdup(const char *data)
 }
 #endif
 
-static int qt_qprocess_deadChild_pipe[2];
-static struct sigaction qt_sa_old_sigchld_handler;
-static void qt_sa_sigchld_handler(int signum)
-{
-    qt_safe_write(qt_qprocess_deadChild_pipe[1], "", 1);
-#if defined (QPROCESS_DEBUG)
-    fprintf(stderr, "*** SIGCHLD\n");
-#endif
-
-    // load it as volatile
-    void (*oldAction)(int) = ((volatile struct sigaction *)&qt_sa_old_sigchld_handler)->sa_handler;
-    if (oldAction && oldAction != SIG_IGN)
-        oldAction(signum);
-}
-
 static inline void add_fd(int &nfds, int fd, fd_set *fdset)
 {
     FD_SET(fd, fdset);
@@ -141,187 +127,24 @@ static inline void add_fd(int &nfds, int fd, fd_set *fdset)
         nfds = fd;
 }
 
-struct QProcessInfo {
-    QProcess *process;
+struct QUnixWaiterThread {
+    QProcessPrivate *process;
+    pthread_t waitThread;
     int deathPipe;
     int exitResult;
     pid_t pid;
-    int serialNumber;
 };
 
-class QProcessManager : public QThread
-{
-    Q_OBJECT
-public:
-    QProcessManager();
-    ~QProcessManager();
-
-    void run();
-    void catchDeadChildren();
-    void add(pid_t pid, QProcess *process);
-    void remove(QProcess *process);
-    void lock();
-    void unlock();
-
-private:
-    QMutex mutex;
-    QMap<int, QProcessInfo *> children;
-};
-
-
-Q_GLOBAL_STATIC(QMutex, processManagerGlobalMutex)
-
-static QProcessManager *processManager() {
-    // The constructor of QProcessManager should be called only once
-    // so we cannot use Q_GLOBAL_STATIC directly for QProcessManager
-    QMutex *mutex = processManagerGlobalMutex();
-    QMutexLocker locker(mutex);
-    static QProcessManager processManager;
-    return &processManager;
-}
-
-QProcessManager::QProcessManager()
-{
-#if defined (QPROCESS_DEBUG)
-    qDebug() << "QProcessManager::QProcessManager()";
-#endif
-    // initialize the dead child pipe and make it non-blocking. in the
-    // extremely unlikely event that the pipe fills up, we do not under any
-    // circumstances want to block.
-    qt_safe_pipe(qt_qprocess_deadChild_pipe, O_NONBLOCK);
-
-    // set up the SIGCHLD handler, which writes a single byte to the dead
-    // child pipe every time a child dies.
-    struct sigaction action;
-    memset(&action, 0, sizeof(action));
-    action.sa_handler = qt_sa_sigchld_handler;
-    action.sa_flags = SA_NOCLDSTOP;
-    ::sigaction(SIGCHLD, &action, &qt_sa_old_sigchld_handler);
-}
-
-QProcessManager::~QProcessManager()
-{
-    // notify the thread that we're shutting down.
-    qt_safe_write(qt_qprocess_deadChild_pipe[1], "@", 1);
-    qt_safe_close(qt_qprocess_deadChild_pipe[1]);
-    wait();
-
-    // on certain unixes, closing the reading end of the pipe will cause
-    // select in run() to block forever, rather than return with EBADF.
-    qt_safe_close(qt_qprocess_deadChild_pipe[0]);
-
-    qt_qprocess_deadChild_pipe[0] = -1;
-    qt_qprocess_deadChild_pipe[1] = -1;
-
-    qDeleteAll(children.values());
-    children.clear();
-
-    struct sigaction currentAction;
-    ::sigaction(SIGCHLD, 0, &currentAction);
-    if (currentAction.sa_handler != qt_sa_sigchld_handler) {
-        ::sigaction(SIGCHLD, &qt_sa_old_sigchld_handler, 0);
-    }
-}
-
-void QProcessManager::run()
-{
-    forever {
-        fd_set readset;
-        FD_ZERO(&readset);
-        FD_SET(qt_qprocess_deadChild_pipe[0], &readset);
-
-#if defined (QPROCESS_DEBUG)
-        qDebug() << "QProcessManager::run() waiting for children to die";
-#endif
-
-        // block forever, or until activity is detected on the dead child
-        // pipe. the only other peers are the SIGCHLD signal handler, and the
-        // QProcessManager destructor.
-        int nselect = select(qt_qprocess_deadChild_pipe[0] + 1, &readset, 0, 0, 0);
-        if (nselect < 0) {
-            if (errno == EINTR)
-                continue;
-            break;
-        }
-
-        // empty only one byte from the pipe, even though several SIGCHLD
-        // signals may have been delivered in the meantime, to avoid race
-        // conditions.
-        char c;
-        if (qt_safe_read(qt_qprocess_deadChild_pipe[0], &c, 1) < 0 || c == '@')
-            break;
-
-        // catch any and all children that we can.
-        catchDeadChildren();
-    }
-}
-
-void QProcessManager::catchDeadChildren()
+extern "C" {
+void *waitThreadFunction(void *arg)
 {
-    QMutexLocker locker(&mutex);
-
-    // try to catch all children whose pid we have registered, and whose
-    // deathPipe is still valid (i.e, we have not already notified it).
-    QMap<int, QProcessInfo *>::Iterator it = children.begin();
-    while (it != children.end()) {
-        // notify all children that they may have died. they need to run
-        // waitpid() in their own thread.
-        QProcessInfo *info = it.value();
-        qt_safe_write(info->deathPipe, "", 1);
-
-#if defined (QPROCESS_DEBUG)
-        qDebug() << "QProcessManager::run() sending death notice to" << info->process;
-#endif
-        ++it;
-    }
-}
-
-static QBasicAtomicInt idCounter = Q_BASIC_ATOMIC_INITIALIZER(1);
-
-void QProcessManager::add(pid_t pid, QProcess *process)
-{
-#if defined (QPROCESS_DEBUG)
-    qDebug() << "QProcessManager::add() adding pid" << pid << "process" << process;
-#endif
-
-    // insert a new info structure for this process
-    QProcessInfo *info = new QProcessInfo;
-    info->process = process;
-    info->deathPipe = process->d_func()->deathPipe[1];
-    info->exitResult = 0;
-    info->pid = pid;
-
-    int serial = idCounter.fetchAndAddRelaxed(1);
-    process->d_func()->serial = serial;
-    children.insert(serial, info);
-}
-
-void QProcessManager::remove(QProcess *process)
-{
-    QMutexLocker locker(&mutex);
-
-    int serial = process->d_func()->serial;
-    QProcessInfo *info = children.value(serial);
-    if (!info)
-        return;
-
-#if defined (QPROCESS_DEBUG)
-    qDebug() << "QProcessManager::remove() removing pid" << info->pid << "process" << info->process;
-#endif
-
-    children.remove(serial);
-    delete info;
-}
-
-void QProcessManager::lock()
-{
-    mutex.lock();
-}
-
-void QProcessManager::unlock()
-{
-    mutex.unlock();
+    // this function must be fully cancellable!
+    QUnixWaiterThread *info = static_cast<QUnixWaiterThread *>(arg);
+    quintptr result = qt_safe_waitpid(info->pid, &info->exitResult, 0);
+    qt_safe_write(info->deathPipe, "", 1);
+    return (void*)result;
 }
+} // extern "C"
 
 static void qt_create_pipe(int *pipe)
 {
@@ -519,8 +342,6 @@ void QProcessPrivate::startProcess()
     qDebug("QProcessPrivate::startProcess()");
 #endif
 
-    processManager()->start();
-
     // Initialize pipes
     if (!createChannel(stdinChannel) ||
         !createChannel(stdoutChannel) ||
@@ -625,7 +446,6 @@ void QProcessPrivate::startProcess()
     }
 
     // Start the process manager, and fork off the child process.
-    processManager()->lock();
     pid_t childPid = qt_fork();
     int lastForkErrno = errno;
     if (childPid != 0) {
@@ -646,7 +466,6 @@ void QProcessPrivate::startProcess()
 #if defined (QPROCESS_DEBUG)
         qDebug("qt_fork failed: %s", qt_error_string(lastForkErrno));
 #endif
-        processManager()->unlock();
         q->setProcessState(QProcess::NotRunning);
         processError = QProcess::FailedToStart;
         q->setErrorString(QProcess::tr("Resource error (fork failure): %1").arg(qt_error_string(lastForkErrno)));
@@ -663,9 +482,19 @@ void QProcessPrivate::startProcess()
 
     // Register the child. In the mean time, we can get a SIGCHLD, so we need
     // to keep the lock held to avoid a race to catch the child.
-    processManager()->add(childPid, q);
     pid = Q_PID(childPid);
-    processManager()->unlock();
+    unixWaiterThread = new QUnixWaiterThread;
+    unixWaiterThread->process = this;
+    unixWaiterThread->pid = childPid;
+    unixWaiterThread->deathPipe = deathPipe[1];
+    {
+        // start the waiter thread
+        pthread_attr_t thattr;
+        pthread_attr_init(&thattr);
+        pthread_attr_setstacksize(&thattr, PTHREAD_STACK_MIN);
+        pthread_create(&unixWaiterThread->waitThread, &thattr, waitThreadFunction, unixWaiterThread);
+        pthread_attr_destroy(&thattr);
+    }
 
     // parent
     // close the ends we don't use and make all pipes non-blocking
@@ -760,6 +589,17 @@ void QProcessPrivate::execChild(const char *workingDir, char **path, char **argv
     childStartedPipe[1] = -1;
 }
 
+void QProcessPrivate::cleanUpWaiterThread()
+{
+    if (unixWaiterThread) {
+        void *arg;
+        pthread_cancel(unixWaiterThread->waitThread);
+        pthread_join(unixWaiterThread->waitThread, &arg);
+        delete unixWaiterThread;
+        unixWaiterThread = 0;
+    }
+}
+
 bool QProcessPrivate::processStarted()
 {
     ushort buf[errorBufferMax];
@@ -1134,30 +974,23 @@ bool QProcessPrivate::waitForWrite(int msecs)
 
 void QProcessPrivate::findExitCode()
 {
-    Q_Q(QProcess);
-    processManager()->remove(q);
+    // nothing to do
 }
 
 bool QProcessPrivate::waitForDeadChild()
 {
-    Q_Q(QProcess);
-
     // read a byte from the death pipe
     char c;
     qt_safe_read(deathPipe[0], &c, 1);
 
     // check if our process is dead
-    int exitStatus;
-    if (qt_safe_waitpid(pid_t(pid), &exitStatus, WNOHANG) > 0) {
-        processManager()->remove(q);
-        crashed = !WIFEXITED(exitStatus);
-        exitCode = WEXITSTATUS(exitStatus);
+    crashed = !WIFEXITED(unixWaiterThread->exitResult);
+    exitCode = WEXITSTATUS(unixWaiterThread->exitResult);
 #if defined QPROCESS_DEBUG
         qDebug() << "QProcessPrivate::waitForDeadChild() dead with exitCode"
                  << exitCode << ", crashed?" << crashed;
 #endif
-        return true;
-    }
+    return true;
 #if defined QPROCESS_DEBUG
     qDebug() << "QProcessPrivate::waitForDeadChild() not dead!";
 #endif
@@ -1170,8 +1003,6 @@ void QProcessPrivate::_q_notified()
 
 bool QProcessPrivate::startDetached(const QString &program, const QStringList &arguments, const QString &workingDirectory, qint64 *pid)
 {
-    processManager()->start();
-
     QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory);
 
     // To catch the startup of the child
@@ -1284,7 +1115,7 @@ bool QProcessPrivate::startDetached(const QString &program, const QStringList &a
 
 void QProcessPrivate::initializeProcessManager()
 {
-    (void) processManager();
+    // Unix no longer has a process manager
 }
 
 QT_END_NAMESPACE
-- 
1.7.3.2.431.ge2f5c

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to