Bladeren bron

Cancel and pause (on POSIX systems) support for a process-based QFuture.

Sascha Zelzer 12 jaren geleden
bovenliggende
commit
d7563dd526

+ 41 - 5
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleFutureTest.cpp

@@ -161,26 +161,62 @@ bool futureTestPauseAndCancel(ctkCmdLineModule* module)
 {
   qDebug() << "Testing ctkCmdLineModuleFuture pause and cancel capabilities";
 
-  QList<QString> expectedSignals;
-  expectedSignals.push_back("module.started");
-  expectedSignals.push_back("module.finished");
 
   ctkCmdLineModuleSignalTester signalTester;
 
   QFutureWatcher<ctkCmdLineModuleResult> watcher;
   QObject::connect(&watcher, SIGNAL(started()), &signalTester, SLOT(moduleStarted()));
-  QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &signalTester, SLOT(moduleProgressValueChanged(int)));
-  QObject::connect(&watcher, SIGNAL(progressTextChanged(QString)), &signalTester, SLOT(moduleProgressTextChanged(QString)));
+  QObject::connect(&watcher, SIGNAL(paused()), &signalTester, SLOT(modulePaused()));
+  QObject::connect(&watcher, SIGNAL(resumed()), &signalTester, SLOT(moduleResumed()));
+  QObject::connect(&watcher, SIGNAL(canceled()), &signalTester, SLOT(moduleCanceled()));
   QObject::connect(&watcher, SIGNAL(finished()), &signalTester, SLOT(moduleFinished()));
 
   module->setValue("runtimeVar", 60);
+  qDebug() << module->commandLineArguments();
   ctkCmdLineModuleFuture future = module->run();
   watcher.setFuture(future);
 
+  QList<QString> expectedSignals;
+  expectedSignals.push_back("module.started");
+  if (future.canPause())
+  {
+    // Due to Qt bug 12152, these two signals are reversed
+    expectedSignals.push_back("module.resumed");
+    expectedSignals.push_back("module.paused");
+  }
+  if (future.canCancel())
+  {
+    expectedSignals.push_back("module.canceled");
+  }
+  expectedSignals.push_back("module.finished");
+
+  sleep(1);
+
+  QCoreApplication::processEvents();
+  future.pause();
+  sleep(1);
+  QCoreApplication::processEvents();
+
+  if (future.canPause())
+  {
+    if (!(future.isPaused() && future.isRunning()))
+    {
+      qDebug() << "Pause state wrong";
+      future.setPaused(false);
+      future.cancel();
+      QCoreApplication::processEvents();
+      future.waitForFinished();
+      return false;
+    }
+  }
+
+  future.togglePaused();
+  QCoreApplication::processEvents();
 
   try
   {
     future.cancel();
+    QCoreApplication::processEvents();
     future.waitForFinished();
   }
   catch (const ctkCmdLineModuleRunException& e)

+ 15 - 0
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleSignalTester.cpp

@@ -43,6 +43,21 @@ void ctkCmdLineModuleSignalTester::moduleProgressTextChanged(const QString &/*te
   events.push_back("module.progressTextChanged");
 }
 
+void ctkCmdLineModuleSignalTester::modulePaused()
+{
+  events.push_back("module.paused");
+}
+
+void ctkCmdLineModuleSignalTester::moduleResumed()
+{
+  events.push_back("module.resumed");
+}
+
+void ctkCmdLineModuleSignalTester::moduleCanceled()
+{
+  events.push_back("module.canceled");
+}
+
 void ctkCmdLineModuleSignalTester::filterStarted(const QString &/*name*/, const QString &/*comment*/)
 {
   events.push_back("filter.started");

+ 4 - 0
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleSignalTester.h

@@ -42,6 +42,10 @@ public Q_SLOTS:
   virtual void moduleProgressValueChanged(int progress);
   virtual void moduleProgressTextChanged(const QString& text);
 
+  virtual void modulePaused();
+  virtual void moduleResumed();
+  virtual void moduleCanceled();
+
   virtual void filterStarted(const QString& name, const QString& comment);
   virtual void filterProgress(float progress);
   virtual void filterFinished(const QString& name);

+ 18 - 24
Libs/CommandLineModules/Core/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp

@@ -93,32 +93,24 @@ int main(int argc, char* argv[])
     outputs << "Output " + QString::number(i+1);
   }
 
-  if (outputs.empty())
-  {
-    // no outputs given, just return
-    if (exitCrash)
-    {
-      int* crash = 0;
-      *crash = 5;
-    }
-    if (exitCode != 0)
-    {
-      err << errorText;
-    }
-    return exitCode;
-  }
-
-  float stepTime = runtime / static_cast<float>(outputs.size());
+  float stepTime = outputs.size() ? runtime / static_cast<float>(outputs.size()) : runtime;
 
   QTime time;
   time.start();
 
   struct timespec nanostep;
 
-  out << "<filter-start>\n";
-  out << "<filter-name>Test Filter</filter-name>\n";
-  out << "<filter-comment>Does nothing useful</filter-comment>\n";
-  out << "</filter-start>\n";
+  if (!outputs.empty())
+  {
+    out << "<filter-start>\n";
+    out << "<filter-name>Test Filter</filter-name>\n";
+    out << "<filter-comment>Does nothing useful</filter-comment>\n";
+    out << "</filter-start>\n";
+  }
+  else
+  {
+    outputs.push_back("dummy");
+  }
 
   float progressStep = 1.0f / static_cast<float>(outputs.size());
   for(int i = 0; i < outputs.size(); ++i)
@@ -146,10 +138,12 @@ int main(int argc, char* argv[])
     nanosleep(&nanostep, NULL);
 
     // print the first output
-    out << output; endl(out);
-
-    // report progress
-    out << "<filter-progress>" << (i+1)*progressStep << "</filter-progress>\n";
+    if (output != "dummy")
+    {
+      out << output; endl(out);
+      // report progress
+      out << "<filter-progress>" << (i+1)*progressStep << "</filter-progress>\n";
+    }
   }
 
   // sleep 1 second to avoid squashing the last progress event with the finished event

+ 3 - 1
Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.h

@@ -41,7 +41,7 @@ public:
 
   QFutureInterface(const QFutureInterface &other)
     : QFutureInterfaceBase(other),
-      CanCancel(false), CanPause(false)
+      CanCancel(other.CanCancel), CanPause(other.CanPause)
   { }
 
   ~QFutureInterface()
@@ -58,6 +58,8 @@ public:
     if (referenceCountIsOne())
       resultStore().clear();
     QFutureInterfaceBase::operator=(other);
+    CanCancel = other.CanCancel;
+    CanPause = other.CanPause;
     return *this;
   }
 

+ 79 - 35
Libs/CommandLineModules/Core/ctkCmdLineModuleProcessTask.cpp

@@ -29,10 +29,14 @@
 #include <QThreadPool>
 #include <QProcess>
 
-ctkCmdLineModuleProcessProgressWatcher::ctkCmdLineModuleProcessProgressWatcher(QProcess& process, const QString& location,
-                                                                               ctkCmdLineModuleFutureInterface &futureInterface)
+#ifdef Q_OS_UNIX
+#include <signal.h>
+#endif
+
+ctkCmdLineModuleProcessWatcher::ctkCmdLineModuleProcessWatcher(QProcess& process, const QString& location,
+                                                               ctkCmdLineModuleFutureInterface &futureInterface)
   : process(process), location(location), futureInterface(futureInterface), processXmlWatcher(&process),
-    progressValue(0)
+    processPaused(false), progressValue(0)
 {
   futureInterface.setProgressRange(0, 1000);
 
@@ -40,30 +44,81 @@ ctkCmdLineModuleProcessProgressWatcher::ctkCmdLineModuleProcessProgressWatcher(Q
   connect(&processXmlWatcher, SIGNAL(filterProgress(float)), SLOT(filterProgress(float)));
   connect(&processXmlWatcher, SIGNAL(filterFinished(QString)), SLOT(filterFinished(QString)));
   connect(&processXmlWatcher, SIGNAL(filterXmlError(QString)), SLOT(filterXmlError(QString)));
+
+  connect(&futureWatcher, SIGNAL(canceled()), SLOT(cancelProcess()));
+#ifdef Q_OS_UNIX
+  connect(&futureWatcher, SIGNAL(resumed()), SLOT(resumeProcess()));
+  // Due to Qt bug 12152, we cannot listen to the "paused" signal because it is
+  // not emitted directly when the QFuture is paused. Instead, it is emitted after
+  // resuming the future, after the "resume" signal has been emitted...
+  //connect(&futureWatcher, SIGNAL(paused()), SLOT(pauseProcess()));
+  connect(&pollPauseTimer, SIGNAL(timeout()), this, SLOT(pauseProcess()));
+  pollPauseTimer.start(500);
+#endif
+  futureWatcher.setFuture(futureInterface.future());
 }
 
-void ctkCmdLineModuleProcessProgressWatcher::filterStarted(const QString& name, const QString& comment)
+void ctkCmdLineModuleProcessWatcher::filterStarted(const QString& name, const QString& comment)
 {
   Q_UNUSED(comment)
   futureInterface.setProgressValueAndText(incrementProgress(), name);
 }
 
-void ctkCmdLineModuleProcessProgressWatcher::filterProgress(float progress)
+void ctkCmdLineModuleProcessWatcher::filterProgress(float progress)
 {
   futureInterface.setProgressValue(updateProgress(progress));
 }
 
-void ctkCmdLineModuleProcessProgressWatcher::filterFinished(const QString& name)
+void ctkCmdLineModuleProcessWatcher::filterFinished(const QString& name)
 {
   futureInterface.setProgressValueAndText(incrementProgress(), "Finished: " + name);
 }
 
-void ctkCmdLineModuleProcessProgressWatcher::filterXmlError(const QString &error)
+void ctkCmdLineModuleProcessWatcher::filterXmlError(const QString &error)
 {
   qDebug().nospace() << "[Module " << location << "]: " << error;
 }
 
-int ctkCmdLineModuleProcessProgressWatcher::updateProgress(float progress)
+void ctkCmdLineModuleProcessWatcher::pauseProcess()
+{
+  if (processPaused || !futureInterface.isPaused()) return;
+
+#ifdef Q_OS_UNIX
+  if (::kill(process.pid(), SIGSTOP))
+  {
+    // error
+    futureInterface.setPaused(false);
+  }
+  else
+  {
+    processPaused = true;
+  }
+#endif
+}
+
+void ctkCmdLineModuleProcessWatcher::resumeProcess()
+{
+  if (!processPaused) return;
+
+#ifdef Q_OS_UNIX
+  if(::kill(process.pid(), SIGCONT))
+  {
+    // error
+    futureInterface.setPaused(true);
+  }
+  else
+  {
+    processPaused = false;
+  }
+#endif
+}
+
+void ctkCmdLineModuleProcessWatcher::cancelProcess()
+{
+  process.terminate();
+}
+
+int ctkCmdLineModuleProcessWatcher::updateProgress(float progress)
 {
   progressValue = static_cast<int>(progress * 1000.0f);
   // normalize the value to lie between 0 and 1000.
@@ -74,16 +129,19 @@ int ctkCmdLineModuleProcessProgressWatcher::updateProgress(float progress)
   return progressValue;
 }
 
-int ctkCmdLineModuleProcessProgressWatcher::incrementProgress()
+int ctkCmdLineModuleProcessWatcher::incrementProgress()
 {
   if (++progressValue > 999) progressValue = 999;
   return progressValue;
 }
 
 ctkCmdLineModuleProcessTask::ctkCmdLineModuleProcessTask(const QString& location, const QStringList& args)
-  : process(NULL), location(location), args(args)
+  : location(location), args(args)
 {
   this->setCanCancel(true);
+#ifdef Q_OS_UNIX
+  this->setCanPause(true);
+#endif
 }
 
 ctkCmdLineModuleProcessTask::~ctkCmdLineModuleProcessTask()
@@ -107,46 +165,32 @@ void ctkCmdLineModuleProcessTask::run()
     return;
   }
 
-  process = new QProcess();
-  process->setReadChannel(QProcess::StandardOutput);
+  QProcess process;
+  process.setReadChannel(QProcess::StandardOutput);
 
   QEventLoop localLoop;
-  QObject::connect(process, SIGNAL(finished(int)), &localLoop, SLOT(quit()));
-  QObject::connect(process, SIGNAL(error(QProcess::ProcessError)), &localLoop, SLOT(quit()));
+  QObject::connect(&process, SIGNAL(finished(int)), &localLoop, SLOT(quit()));
+  QObject::connect(&process, SIGNAL(error(QProcess::ProcessError)), &localLoop, SLOT(quit()));
 
-  QTimer pollCancelTimer;
-  pollCancelTimer.setInterval(500);
-  this->connect(&pollCancelTimer, SIGNAL(timeout()), SLOT(pollCancelState()));
+  process.start(location, args);
 
-  process->start(location, args);
-
-  ctkCmdLineModuleProcessProgressWatcher progressWatcher(*process, location, *this);
+  ctkCmdLineModuleProcessWatcher progressWatcher(process, location, *this);
   Q_UNUSED(progressWatcher)
 
   localLoop.exec();
 
-  if (process->error() != QProcess::UnknownError)
+  if (process.error() != QProcess::UnknownError)
   {
-    this->reportException(ctkCmdLineModuleRunException(location, process->exitCode(), process->errorString()));
+    this->reportException(ctkCmdLineModuleRunException(location, process.exitCode(), process.errorString()));
   }
-  else if (process->exitCode() != 0)
+  else if (process.exitCode() != 0)
   {
-    this->reportException(ctkCmdLineModuleRunException(location, process->exitCode(), process->readAllStandardError()));
+    this->reportException(ctkCmdLineModuleRunException(location, process.exitCode(), process.readAllStandardError()));
   }
 
-  this->setProgressValueAndText(1000, process->readAllStandardError());
+  this->setProgressValueAndText(1000, process.readAllStandardError());
 
   //this->reportResult(result);
   this->reportFinished();
 
-  delete process;
-
-}
-
-void ctkCmdLineModuleProcessTask::pollCancelState()
-{
-  if (this->isCanceled() && process->state() == QProcess::Running)
-  {
-    process->terminate();
-  }
 }

+ 13 - 10
Libs/CommandLineModules/Core/ctkCmdLineModuleProcessTask.h

@@ -29,12 +29,13 @@
 #include <QRunnable>
 #include <QStringList>
 #include <QBuffer>
+#include <QFutureWatcher>
+#include <QTimer>
 
 class QProcess;
 
-class ctkCmdLineModuleProcessTask : public QObject, public ctkCmdLineModuleFutureInterface, public QRunnable
+class ctkCmdLineModuleProcessTask : public ctkCmdLineModuleFutureInterface, public QRunnable
 {
-  Q_OBJECT
 
 public:
 
@@ -45,26 +46,21 @@ public:
 
   void run();
 
-protected Q_SLOTS:
-
-  void pollCancelState();
-
 private:
 
-  QProcess* process;
   const QString location;
   const QStringList args;
 
 };
 
-class ctkCmdLineModuleProcessProgressWatcher : public QObject
+class ctkCmdLineModuleProcessWatcher : public QObject
 {
   Q_OBJECT
 
 public:
 
-  ctkCmdLineModuleProcessProgressWatcher(QProcess& process, const QString& location,
-                                         ctkCmdLineModuleFutureInterface& futureInterface);
+  ctkCmdLineModuleProcessWatcher(QProcess& process, const QString& location,
+                                 ctkCmdLineModuleFutureInterface& futureInterface);
 
 protected Q_SLOTS:
 
@@ -74,6 +70,10 @@ protected Q_SLOTS:
 
   void filterXmlError(const QString& error);
 
+  void pauseProcess();
+  void resumeProcess();
+  void cancelProcess();
+
 private:
 
   int updateProgress(float progress);
@@ -83,6 +83,9 @@ private:
   QString location;
   ctkCmdLineModuleFutureInterface& futureInterface;
   ctkCmdLineModuleXmlProgressWatcher processXmlWatcher;
+  QFutureWatcher<ctkCmdLineModuleResult> futureWatcher;
+  QTimer pollPauseTimer;
+  bool processPaused;
   int progressValue;
 };