UicCodeModel: Make process killing more robust

One possible cause for the crash reported in QTCREATORBUG-15672 is
that uic is in the process of getting killed while somebody else
already starts a new instance.

So this patch makes that more robust by using a new QProcess instance
for each uic run.

Task-number: QTCREATORBUG-15672
Change-Id: Ibf4feda4fd783fbcadbdbbed30edeb0ff3c85871
Reviewed-by: Eike Ziller <eike.ziller@theqtcompany.com>
This commit is contained in:
Tobias Hunger
2016-03-02 11:33:17 +01:00
parent 1698b77827
commit fc57682120
2 changed files with 78 additions and 71 deletions

View File

@@ -75,31 +75,26 @@ UiCodeModelSupport::UiCodeModelSupport(CppTools::CppModelManager *modelmanager,
: CppTools::AbstractEditorSupport(modelmanager), : CppTools::AbstractEditorSupport(modelmanager),
m_project(project), m_project(project),
m_uiFileName(uiFile), m_uiFileName(uiFile),
m_headerFileName(uiHeaderFile), m_headerFileName(uiHeaderFile)
m_state(BARE)
{ {
QLoggingCategory log("qtc.qtsupport.uicodemodelsupport"); QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
qCDebug(log) << "ctor UiCodeModelSupport for" << m_uiFileName << uiHeaderFile; qCDebug(log) << "ctor UiCodeModelSupport for" << m_uiFileName << uiHeaderFile;
connect(&m_process, SIGNAL(finished(int)),
this, SLOT(finishProcess()));
init(); init();
} }
UiCodeModelSupport::~UiCodeModelSupport() UiCodeModelSupport::~UiCodeModelSupport()
{ {
disconnect(&m_process, SIGNAL(finished(int)), cleanUpProcess();
this, SLOT(finishProcess()));
m_process.kill();
CppTools::CppModelManager::instance()->emitAbstractEditorSupportRemoved(m_headerFileName); CppTools::CppModelManager::instance()->emitAbstractEditorSupportRemoved(m_headerFileName);
QLoggingCategory log("qtc.qtsupport.uicodemodelsupport"); QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
qCDebug(log) << "dtor ~UiCodeModelSupport for" << m_uiFileName; qCDebug(log) << "dtor ~UiCodeModelSupport for" << m_uiFileName;
} }
void UiCodeModelSupport::init() const void UiCodeModelSupport::init()
{ {
QTC_ASSERT(!m_process, return);
QLoggingCategory log("qtc.qtsupport.uicodemodelsupport"); QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
if (m_state != BARE)
return;
QDateTime sourceTime = QFileInfo(m_uiFileName).lastModified(); QDateTime sourceTime = QFileInfo(m_uiFileName).lastModified();
QFileInfo uiHeaderFileInfo(m_headerFileName); QFileInfo uiHeaderFileInfo(m_headerFileName);
QDateTime uiHeaderTime = uiHeaderFileInfo.exists() ? uiHeaderFileInfo.lastModified() : QDateTime(); QDateTime uiHeaderTime = uiHeaderFileInfo.exists() ? uiHeaderFileInfo.lastModified() : QDateTime();
@@ -110,7 +105,6 @@ void UiCodeModelSupport::init() const
QTextStream stream(&file); QTextStream stream(&file);
m_contents = stream.readAll().toUtf8(); m_contents = stream.readAll().toUtf8();
m_cacheTime = uiHeaderTime; m_cacheTime = uiHeaderTime;
m_state = FINISHED;
notifyAboutUpdatedContents(); notifyAboutUpdatedContents();
return; return;
} }
@@ -129,14 +123,12 @@ void UiCodeModelSupport::init() const
qCDebug(log) << "uic run wasn't succesfull"; qCDebug(log) << "uic run wasn't succesfull";
m_cacheTime = QDateTime (); m_cacheTime = QDateTime ();
m_contents.clear(); m_contents.clear();
m_state = FINISHED;
notifyAboutUpdatedContents(); notifyAboutUpdatedContents();
return; return;
} }
} else { } else {
qCDebug(log) << "Could not open " << m_uiFileName << "needed for the cpp model"; qCDebug(log) << "Could not open " << m_uiFileName << "needed for the cpp model";
m_contents.clear(); m_contents.clear();
m_state = FINISHED;
notifyAboutUpdatedContents(); notifyAboutUpdatedContents();
} }
} }
@@ -161,11 +153,7 @@ void UiCodeModelSupport::setHeaderFileName(const QString &name)
if (m_headerFileName == name && m_cacheTime.isValid()) if (m_headerFileName == name && m_cacheTime.isValid())
return; return;
if (m_state == RUNNING) { cleanUpProcess();
m_state = ABORTING;
m_process.kill();
m_process.waitForFinished(3000);
}
QLoggingCategory log("qtc.qtsupport.uicodemodelsupport"); QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
qCDebug(log) << "UiCodeModelSupport::setFileName" << name; qCDebug(log) << "UiCodeModelSupport::setFileName" << name;
@@ -173,45 +161,57 @@ void UiCodeModelSupport::setHeaderFileName(const QString &name)
m_headerFileName = name; m_headerFileName = name;
m_contents.clear(); m_contents.clear();
m_cacheTime = QDateTime(); m_cacheTime = QDateTime();
m_state = BARE;
init(); init();
} }
bool UiCodeModelSupport::runUic(const QString &ui) const bool UiCodeModelSupport::runUic(const QString &ui)
{ {
QTC_ASSERT(!m_process, return false);
const QString uic = uicCommand(); const QString uic = uicCommand();
if (uic.isEmpty()) if (uic.isEmpty())
return false; return false;
QLoggingCategory log("qtc.qtsupport.uicodemodelsupport"); QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
m_process.setEnvironment(environment()); m_process = new QProcess(this);
connect(m_process, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
this, &UiCodeModelSupport::finishProcess);
connect(m_process, &QProcess::started, this, [this, ui]() { startProcess(ui); });
#if QT_VERSION < QT_VERSION_CHECK(5, 6, 0) // ### fixme: Remove once 5.6 is the minimum Qt version required
# define QPROCESS_ERROR_SIGNAL static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error)
#else
# define QPROCESS_ERROR_SIGNAL &QProcess::errorOccurred
#endif
connect(m_process, QPROCESS_ERROR_SIGNAL, this, &UiCodeModelSupport::errorProcess);
#undef QPROCESS_ERROR_SIGNAL
qCDebug(log) << " UiCodeModelSupport::runUic " << uic << " on " << ui.size() << " bytes"; qCDebug(log) << " UiCodeModelSupport::runUic " << uic << " on " << ui.size() << " bytes";
m_process.start(uic, QStringList(), QIODevice::ReadWrite); m_process->setEnvironment(environment());
if (!m_process.waitForStarted()) m_process->start(uic, QStringList(), QIODevice::ReadWrite);
return false;
m_process.write(ui.toUtf8());
if (!m_process.waitForBytesWritten(3000))
goto error;
m_process.closeWriteChannel();
m_state = RUNNING;
return true; return true;
}
error: void UiCodeModelSupport::cleanUpProcess()
qCDebug(log) << "failed" << m_process.readAllStandardError(); {
m_process.kill(); if (!m_process)
m_state = FINISHED; return;
return false;
disconnect(m_process);
if (m_process->state() == QProcess::Running) {
connect(m_process, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
m_process, &QProcess::deleteLater);
m_process->kill();
} else {
m_process->deleteLater();
}
m_process = nullptr;
} }
void UiCodeModelSupport::updateFromEditor(const QString &formEditorContents) void UiCodeModelSupport::updateFromEditor(const QString &formEditorContents)
{ {
QLoggingCategory log("qtc.qtsupport.uicodemodelsupport"); QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
qCDebug(log) << "updating from editor" << m_uiFileName; qCDebug(log) << "updating from editor" << m_uiFileName;
if (m_state == RUNNING) { cleanUpProcess();
m_state = ABORTING;
m_process.kill();
m_process.waitForFinished(3000);
}
runUic(formEditorContents); runUic(formEditorContents);
} }
@@ -273,31 +273,39 @@ QStringList UiCodeModelSupport::environment() const
} }
} }
bool UiCodeModelSupport::finishProcess() void UiCodeModelSupport::startProcess(const QString &ui)
{ {
if (m_state != RUNNING) QTC_ASSERT(m_process, return);
return false; m_process->write(ui.toUtf8());
m_process->closeWriteChannel();
}
void UiCodeModelSupport::errorProcess()
{
QTC_ASSERT(m_process, return);
QLoggingCategory log("qtc.qtsupport.uicodemodelsupport"); QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
if (!m_process.waitForFinished(3000) qCDebug(log) << "failed" << m_process->readAllStandardError();
&& m_process.exitStatus() != QProcess::NormalExit cleanUpProcess();
&& m_process.exitCode() != 0) { }
qCDebug(log) << "finish process: failed" << m_process.readAllStandardError(); void UiCodeModelSupport::finishProcess()
m_process.kill(); {
m_state = FINISHED; QTC_ASSERT(m_process, return);
return false; QLoggingCategory log("qtc.qtsupport.uicodemodelsupport");
}
if (m_process->exitStatus() == QProcess::NormalExit && m_process->exitCode() == 0) {
// As far as I can discover in the UIC sources, it writes out local 8-bit encoding. The // As far as I can discover in the UIC sources, it writes out local 8-bit encoding. The
// conversion below is to normalize both the encoding, and the line terminators. // conversion below is to normalize both the encoding, and the line terminators.
QString normalized = QString::fromLocal8Bit(m_process.readAllStandardOutput()); const QString normalized = QString::fromLocal8Bit(m_process->readAllStandardOutput());
m_contents = normalized.toUtf8(); m_contents = normalized.toUtf8();
m_cacheTime = QDateTime::currentDateTime(); m_cacheTime = QDateTime::currentDateTime();
qCDebug(log) << "finish process: ok" << m_contents.size() << "bytes."; qCDebug(log) << "finish process: ok" << m_contents.size() << "bytes.";
m_state = FINISHED;
notifyAboutUpdatedContents(); notifyAboutUpdatedContents();
updateDocument(); updateDocument();
return true; }
cleanUpProcess();
} }
UiCodeModelManager *UiCodeModelManager::m_instance = 0; UiCodeModelManager *UiCodeModelManager::m_instance = 0;

View File

@@ -72,22 +72,21 @@ private:
QString uicCommand() const; QString uicCommand() const;
QStringList environment() const; QStringList environment() const;
private slots:
bool finishProcess();
private: private:
void startProcess(const QString &ui);
void errorProcess();
void finishProcess();
ProjectExplorer::Project *m_project; ProjectExplorer::Project *m_project;
enum State { BARE, RUNNING, FINISHED, ABORTING };
void init() const; void init();
bool runUic(const QString &ui) const; bool runUic(const QString &ui);
mutable QProcess m_process; void cleanUpProcess();
QProcess *m_process = nullptr;
QString m_uiFileName; QString m_uiFileName;
QString m_headerFileName; QString m_headerFileName;
mutable State m_state; QByteArray m_contents;
mutable QByteArray m_contents; QDateTime m_cacheTime;
mutable QDateTime m_cacheTime;
static QList<UiCodeModelSupport *> m_waitingForStart;
}; };
class QTSUPPORT_EXPORT UiCodeModelManager : public QObject class QTSUPPORT_EXPORT UiCodeModelManager : public QObject