From 91c4b0305c0d36da4d959d1be744ee61863b4642 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Fri, 21 May 2010 17:41:51 +0200 Subject: [PATCH] Synchronous Process: Add a message box for timeouts. Optionally prompt the user whether a process should be killed after being non-responsive. Do the same with readProcessData. Add a way of creating a process without terminal (UNIX) for suppressing SSH password prompts. --- src/libs/utils/synchronousprocess.cpp | 120 ++++++++++++++++++++++++-- src/libs/utils/synchronousprocess.h | 26 +++++- 2 files changed, 139 insertions(+), 7 deletions(-) diff --git a/src/libs/utils/synchronousprocess.cpp b/src/libs/utils/synchronousprocess.cpp index 3fe37b9500c..7a8d5a007eb 100644 --- a/src/libs/utils/synchronousprocess.cpp +++ b/src/libs/utils/synchronousprocess.cpp @@ -36,11 +36,16 @@ #include <QtCore/QTextCodec> #include <QtCore/QFileInfo> #include <QtCore/QDir> +#include <QtGui/QMessageBox> #include <QtGui/QApplication> #include <limits.h> +#ifdef Q_OS_UNIX +# include <unistd.h> +#endif + enum { debug = 0 }; enum { syncDebug = 0 }; @@ -48,6 +53,30 @@ enum { defaultMaxHangTimerCount = 10 }; namespace Utils { +// A special QProcess derivative allowing for terminal control. +class TerminalControllingProcess : public QProcess { +public: + TerminalControllingProcess() : m_flags(0) {} + + unsigned flags() const { return m_flags; } + void setFlags(unsigned tc) { m_flags = tc; } + +protected: + virtual void setupChildProcess(); + +private: + unsigned m_flags; +}; + +void TerminalControllingProcess::setupChildProcess() +{ +#ifdef Q_OS_UNIX + // Disable terminal by becoming a session leader. + if (m_flags & SynchronousProcess::UnixTerminalDisabled) + setsid(); +#endif +} + // ----------- SynchronousProcessResponse SynchronousProcessResponse::SynchronousProcessResponse() : result(StartFailed), @@ -63,6 +92,25 @@ void SynchronousProcessResponse::clear() stdErr.clear(); } +QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeoutMS) const +{ + switch (result) { + case Finished: + return SynchronousProcess::tr("The command '%1' finished successfully.").arg(binary); + case FinishedError: + return SynchronousProcess::tr("The command '%1' terminated with exit code %2.").arg(binary).arg(exitCode); + break; + case TerminatedAbnormally: + return SynchronousProcess::tr("The command '%1' terminated abnormally.").arg(binary); + case StartFailed: + return SynchronousProcess::tr("The command '%1' could not be started.").arg(binary); + case Hang: + return SynchronousProcess::tr("The command '%1' did not respond within the timeout limit (%2 ms)."). + arg(binary).arg(timeoutMS); + } + return QString(); +} + QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r) { QDebug nsp = str.nospace(); @@ -120,13 +168,15 @@ struct SynchronousProcessPrivate { void clearForRun(); QTextCodec *m_stdOutCodec; - QProcess m_process; + TerminalControllingProcess m_process; QTimer m_timer; QEventLoop m_eventLoop; SynchronousProcessResponse m_result; int m_hangTimerCount; int m_maxHangTimerCount; bool m_startFailure; + bool m_timeOutMessageBoxEnabled; + QString m_binary; ChannelBuffer m_stdOut; ChannelBuffer m_stdErr; @@ -136,7 +186,8 @@ SynchronousProcessPrivate::SynchronousProcessPrivate() : m_stdOutCodec(0), m_hangTimerCount(0), m_maxHangTimerCount(defaultMaxHangTimerCount), - m_startFailure(false) + m_startFailure(false), + m_timeOutMessageBoxEnabled(false) { } @@ -147,6 +198,7 @@ void SynchronousProcessPrivate::clearForRun() m_stdErr.clearForRun(); m_result.clear(); m_startFailure = false; + m_binary.clear(); } // ----------- SynchronousProcess @@ -217,6 +269,16 @@ QStringList SynchronousProcess::environment() const return m_d->m_process.environment(); } +bool SynchronousProcess::timeOutMessageBoxEnabled() const +{ + return m_d->m_timeOutMessageBoxEnabled; +} + +void SynchronousProcess::setTimeOutMessageBoxEnabled(bool v) +{ + m_d->m_timeOutMessageBoxEnabled = v; +} + void SynchronousProcess::setEnvironment(const QStringList &e) { m_d->m_process.setEnvironment(e); @@ -232,6 +294,16 @@ QProcessEnvironment SynchronousProcess::processEnvironment() const return m_d->m_process.processEnvironment(); } +unsigned SynchronousProcess::flags() const +{ + return m_d->m_process.flags(); +} + +void SynchronousProcess::setFlags(unsigned tc) +{ + m_d->m_process.setFlags(tc); +} + void SynchronousProcess::setWorkingDirectory(const QString &workingDirectory) { m_d->m_process.setWorkingDirectory(workingDirectory); @@ -263,6 +335,7 @@ SynchronousProcessResponse SynchronousProcess::run(const QString &binary, // On Windows, start failure is triggered immediately if the // executable cannot be found in the path. Do not start the // event loop in that case. + m_d->m_binary = binary; m_d->m_process.start(binary, args, QIODevice::ReadOnly); if (!m_d->m_startFailure) { m_d->m_timer.start(); @@ -285,13 +358,36 @@ SynchronousProcessResponse SynchronousProcess::run(const QString &binary, return m_d->m_result; } +static inline bool askToKill(const QString &binary = QString()) +{ + const QString title = SynchronousProcess::tr("Process not Responding"); + QString msg = binary.isEmpty() ? + SynchronousProcess::tr("The process is not responding.") : + SynchronousProcess::tr("The process '%1' is not responding.").arg(binary); + msg += QLatin1Char(' '); + msg += SynchronousProcess::tr(" Would you like to terminate it?"); + // Restore the cursor that is set to wait while running. + const bool hasOverrideCursor = QApplication::overrideCursor() != 0; + if (hasOverrideCursor) + QApplication::restoreOverrideCursor(); + QMessageBox::StandardButton answer = QMessageBox::question(0, title, msg, QMessageBox::Yes|QMessageBox::No); + if (hasOverrideCursor) + QApplication::setOverrideCursor(Qt::WaitCursor); + return answer == QMessageBox::Yes; +} + void SynchronousProcess::slotTimeout() { if (++m_d->m_hangTimerCount > m_d->m_maxHangTimerCount) { if (debug) qDebug() << Q_FUNC_INFO << "HANG detected, killing"; - SynchronousProcess::stopProcess(m_d->m_process); - m_d->m_result.result = SynchronousProcessResponse::Hang; + const bool terminate = !m_d->m_timeOutMessageBoxEnabled || askToKill(m_d->m_binary); + if (terminate) { + SynchronousProcess::stopProcess(m_d->m_process); + m_d->m_result.result = SynchronousProcessResponse::Hang; + } else { + m_d->m_hangTimerCount = 0; + } } else { if (debug) qDebug() << Q_FUNC_INFO << m_d->m_hangTimerCount; @@ -401,9 +497,17 @@ void SynchronousProcess::processStdErr(bool emitSignals) } } +QSharedPointer<QProcess> SynchronousProcess::createProcess(unsigned flags) +{ + TerminalControllingProcess *process = new TerminalControllingProcess; + process->setFlags(flags); + return QSharedPointer<QProcess>(process); +} + // Static utilities: Keep running as long as it gets data. bool SynchronousProcess::readDataFromProcess(QProcess &p, int timeOutMS, - QByteArray *stdOut, QByteArray *stdErr) + QByteArray *stdOut, QByteArray *stdErr, + bool showTimeOutMessageBox) { if (syncDebug) qDebug() << ">readDataFromProcess" << timeOutMS; @@ -435,6 +539,12 @@ bool SynchronousProcess::readDataFromProcess(QProcess &p, int timeOutMS, if (stdErr) stdErr->append(newStdErr); } + // Prompt user, pretend we have data if says 'No'. + const bool hang = !hasData && !finished; + if (hang && showTimeOutMessageBox) { + if (!askToKill()) + hasData = true; + } } while (hasData && !finished); if (syncDebug) qDebug() << "<readDataFromProcess" << finished; diff --git a/src/libs/utils/synchronousprocess.h b/src/libs/utils/synchronousprocess.h index ebe22296180..102476960e1 100644 --- a/src/libs/utils/synchronousprocess.h +++ b/src/libs/utils/synchronousprocess.h @@ -35,6 +35,7 @@ #include <QtCore/QObject> #include <QtCore/QProcess> #include <QtCore/QStringList> +#include <QtCore/QSharedPointer> QT_BEGIN_NAMESPACE class QTextCodec; @@ -64,6 +65,9 @@ struct QTCREATOR_UTILS_EXPORT SynchronousProcessResponse SynchronousProcessResponse(); void clear(); + // Helper to format an exit message. + QString exitMessage(const QString &binary, int timeoutMS) const; + Result result; int exitCode; QString stdOut; @@ -87,12 +91,20 @@ QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessRes * There is a timeout handling that takes effect after the last data have been * read from stdout/stdin (as opposed to waitForFinished(), which measures time * since it was invoked). It is thus also suitable for slow processes that continously - * output data (like version system operations). */ + * output data (like version system operations). + * + * The property timeOutMessageBoxEnabled influences whether a message box is + * shown asking the user if they want to kill the process on timeout (default: false). */ class QTCREATOR_UTILS_EXPORT SynchronousProcess : public QObject { Q_OBJECT public: + enum Flags { + // Unix: Do not give the child process a terminal for input prompting. + UnixTerminalDisabled = 0x1 + }; + SynchronousProcess(); virtual ~SynchronousProcess(); @@ -113,6 +125,9 @@ public: bool stdErrBufferedSignalsEnabled() const; void setStdErrBufferedSignalsEnabled(bool); + bool timeOutMessageBoxEnabled() const; + void setTimeOutMessageBoxEnabled(bool); + QStringList environment() const; void setEnvironment(const QStringList &); @@ -122,14 +137,21 @@ public: void setWorkingDirectory(const QString &workingDirectory); QString workingDirectory() const; + unsigned flags() const; + void setFlags(unsigned); + SynchronousProcessResponse run(const QString &binary, const QStringList &args); + // Create a (derived) processes with flags applied. + static QSharedPointer<QProcess> createProcess(unsigned flags); + // Static helper for running a process synchronously in the foreground with timeout // detection similar SynchronousProcess' handling (taking effect after no more output // occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout // occurs. Checking of the process' exit state/code still has to be done. static bool readDataFromProcess(QProcess &p, int timeOutMS, - QByteArray *stdOut = 0, QByteArray *stdErr = 0); + QByteArray *stdOut = 0, QByteArray *stdErr = 0, + bool timeOutMessageBox = false); // Stop a process by first calling terminate() (allowing for signal handling) and // then kill(). static bool stopProcess(QProcess &p); -- GitLab