QProcess умирает без видимой причины

Кодируя, казалось бы, простую часть приложения Qt, которое будет запускать подпроцесс и читать данные из его стандартного вывода, я натолкнулся на проблему, которая меня действительно озадачила. Приложение должно считывать блоки данных (необработанные видеокадры) из подпроцесса и обрабатывать их по мере их поступления:

  1. запустить QProcess
  2. собирать данные пока не хватит на один кадр
  3. обработать кадр
  4. вернитесь к шагу 2

Идея заключалась в том, чтобы реализовать цикл обработки с использованием сигналов и слотов - это может показаться глупым в простом урезанном примере, который я привожу ниже, но в рамках исходного приложения показалось вполне разумным. Итак, поехали:

app::app() {
  process.start("cat /dev/zero");
  buffer = new char[frameLength];
  connect(this, SIGNAL(wantNewFrame()), SLOT(readFrame()), Qt::QueuedConnection);
  connect(this, SIGNAL(frameReady()), SLOT(frameHandler()), Qt::QueuedConnection);
  emit wantNewFrame();
}

Я начинаю здесь тривиальный процесс (cat /dev/zero), так что мы можем быть уверены, что он не будет исчерпан данными. Я также делаю два соединения: одно начинает чтение, когда необходим кадр, а другое вызывает функцию обработки данных по прибытии кадра. Обратите внимание, что этот тривиальный пример выполняется в одном потоке, поэтому соединения сделаны из типа очереди, чтобы избежать бесконечной рекурсии. wantNewFrame() сигнал инициирует получение первого кадра; он обрабатывается, когда элемент управления возвращается в цикл обработки событий.

bool app::readFrame() {
  qint64 bytesNeeded = frameLength;
  qint64 bytesRead = 0;
  char* ptr = buffer;
  while (bytesNeeded > 0) {
    process.waitForReadyRead();
    bytesRead = process.read(ptr, bytesNeeded);
    if (bytesRead == -1) {
      qDebug() << "process state" << process.state();
      qDebug() << "process error" << process.error();
      qDebug() << "QIODevice error" << process.errorString();
      QCoreApplication::quit();
      break;
    }
    ptr += bytesRead;
    bytesNeeded -= bytesRead;
  }
  if (bytesNeeded == 0) {
    emit frameReady();
    return true;
  } else
    return false;
}

Чтение кадра: в основном, я просто помещаю данные в буфер по мере его поступления. frameReady() сигнал в конце сообщает о готовности кадра и, в свою очередь, запускает функцию обработки данных.

void app::frameHandler() {
  static qint64 frameno = 0;
  qDebug() << "frame" << frameno++;
  emit wantNewFrame();
}

Тривиальный процессор данных: он просто считает кадры. Когда это сделано, оно испускает wantNewFrame() начать цикл чтения заново.

Это оно. Для полноты я также опубликую файл заголовка и main() здесь.

app.h:

#include <QDebug>
#include <QCoreApplication>
#include <QProcess>

class app : public QObject
{
Q_OBJECT
public:
  app();
  ~app() { delete[] buffer; }

signals:
  void wantNewFrame();
  void frameReady();

public slots:
  bool readFrame();
  void frameHandler();

private:
  static const quint64 frameLength = 614400;
  QProcess process;
  char* buffer;
};

main.cpp:

#include "app.h"

int main(int argc, char** argv)
{
    QCoreApplication coreapp(argc, argv);
    app foo;
    return coreapp.exec();
}

А теперь о странной части. Эта программа обрабатывает случайное количество кадров очень хорошо (я видел что-то от пятнадцати до более тысячи), но в конце концов останавливается и жалуется, что QProcess потерпел крах:

$ ./app
frame 1
...
frame 245 
frame 246 
frame 247 
process state 0 
process error 1 
QIODevice error "Process crashed" 

Состояние процесса 0 означает "не работает", а ошибка процесса 1 означает "сбой". Я исследовал его и обнаружил, что дочерний процесс получает SIGPIPE - т. Е. Родитель закрыл канал для него. Но я абсолютно не знаю, где и почему это происходит. Кто-нибудь еще?

1 ответ

Решение

Код выглядит немного странно (не используя readyRead сигнал и вместо этого полагаясь на задержанные сигналы / слоты). Как вы указали в обсуждении, вы уже видели ветку на ML-интереса ML, где я спрашивал о подобной проблеме. Я только что понял, что я тоже использовал QueuedConnection в это время. Я не могу объяснить, почему это неправильно - поставленные в очередь сигналы "должны работать", на мой взгляд. Слепой выстрел в том, что invokeMethod который используется реализацией Qt, каким-то образом конкурирует с доставкой вашего сигнала, так что вы очищаете буфер чтения до того, как Qt получит возможность обработать данные. Это будет означать, что Qt в конечном итоге будет читать нулевые байты и (правильно) интерпретировать это как EOF, закрывая трубу.

Я больше не могу найти ссылочную "Qt task 217111", но в их Jira есть пара сообщений о waitForReadyRead не работает, как ожидают пользователи, см., например, QTBUG-9529.

Я бы отнес это к списку рассылки Qt "проценты" и держаться подальше от waitFor... семейство методов. Я согласен, что их документация заслуживает обновления.

Другие вопросы по тегам