Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-37306

QProcess in a thread can cause a hang on OS X 10.9

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Closed
    • Priority: P1: Critical
    • Resolution: Done
    • Affects Version/s: 4.8.4, 4.8.5, 5.2.0, 5.2.1
    • Fix Version/s: 4.8.6, 5.3.0 Beta1
    • Component/s: Core: I/O
    • Labels:
      None
    • Environment:
      OS X 10.9
    • Platform/s:
      macOS
    • Commits:
      54491a2571669f0c232d2ab9926991ff9ecf3403 f15d9e6ccd006a2b47b50ae755fbe9534748a2eb b2216bbe06b8be2bef6d8bc2ffff1337f6d23358

      Description

      The following example runs a system tool and prints its output on finished().
      Then it does the same again. A thousand times.
      At some point the QProcess will not be able to start the tool anymore and the example vainly waits for the finished() signal (started() or error() are also not emitted).

      #include <QCoreApplication>
      #include <QTimer>
      #include <QProcess>
      #include <QDebug>
      #include <QThread>
      
      #include <stdio.h>
      
      static const bool reuseProcess = false;         // true or false here doesn't make a difference
      
      class ProcessStarter : public QObject
      {
          Q_OBJECT
      public:
          ProcessStarter(int count)
              : processesToStart(count)
              , current(0)
          {
              createProcess();
          }
      
      public slots:
          QProcess *createProcess()
          {
              QProcess *process = new QProcess(this);
              connect(process, SIGNAL(finished(int)), SLOT(onProcessFinished()));
              connect(process, SIGNAL(error(QProcess::ProcessError)), SLOT(onProcessError()));
              return process;
          }
      
          void start()
          {
              QProcess *process = 0;
              if (reuseProcess) {
                  process = findChild<QProcess *>();
              } else {
                  process = createProcess();
              }
      
              ++current;
              qDebug() << "start" << current;
              process->start("/usr/bin/sw_vers");
          }
      
          void onProcessError()
          {
              QProcess *process = qobject_cast<QProcess*>(sender());
              qDebug() << "error" << current << process->errorString();
              if (!reuseProcess) {
                  process->disconnect();
                  process->deleteLater();
              }
              emit finished();
          }
      
          void onProcessFinished()
          {
              QProcess *process = qobject_cast<QProcess*>(sender());
              qDebug() << "finish" << current;
              QString str = QString::fromLocal8Bit(process->readAllStandardOutput());
              if (!reuseProcess) {
                  process->disconnect();
                  process->deleteLater();
              }
              emit output(str);
              if (processesToStart == current) {
                  qDebug() << "all processes finished";
                  emit finished();
              } else {
                  QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
              }
          }
      
      signals:
          void finished();
          void output(QString);
      
      private:
          int processesToStart;
          int current;
      };
      
      class OutputSink : public QObject
      {
          Q_OBJECT
          int count;
      public:
          OutputSink()
              : count(0)
          {
          }
      
      public slots:
          void printOutput(QString str)
          {
              ++count;
              // Doing anything else here but printing to stdout/stderr doesn't cause the problem.
              fprintf(stdout, "%d %s\n", count, str.toUtf8().data());
              fflush(stdout);
          }
      };
      
      int main(int argc, char *argv[])
      {
          QCoreApplication a(argc, argv);
          ProcessStarter starter(1000);
          OutputSink sink;
      
      #if 1
          // Move the ProcessStarter to a different thread.
          QThread t;
          t.connect(&starter, SIGNAL(finished()), SLOT(quit()));
          a.connect(&t, SIGNAL(finished()), SLOT(quit()));
          starter.moveToThread(&t);
          t.start();
      #else
          // Everything in the main thread. Works fine.
          a.connect(&starter, SIGNAL(finished()), SLOT(quit()));
      #endif
      
          sink.connect(&starter, SIGNAL(output(QString)), SLOT(printOutput(QString)), Qt::QueuedConnection);
          QMetaObject::invokeMethod(&starter, "start", Qt::QueuedConnection);
          return a.exec();
      }
      
      #include "main.moc"
      

      Attaching to the forked child reveals that the child is locked in the fileno(stdout) call in QProcessPrivate::execChild.

      Note that the hang does not happen on OS X 10.8 or older.

        Attachments

          Issue Links

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

            Activity

              People

              Assignee:
              jbornema Joerg Bornemann
              Reporter:
              jbornema Joerg Bornemann
              Votes:
              2 Vote for this issue
              Watchers:
              5 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved:

                  Gerrit Reviews

                  There are no open Gerrit changes