forked from qt-creator/qt-creator
Core::OutputWindow: Prevent UI thread overload
... by splitting up huge amounts of output and feeding it to the formatter in more digestable chunks, with event processing in between. This prevents a UI freeze in the case that a single file emits a large amount of diagnostics and the build tool buffers the output (as ninja and qbs do), which therefore comes in all at once. Apart from keeping the UI responsive, this also speeds up execution of the build step itself, as the remaining output can now be displayed after it has finished. If another build step is started and there is too much output pending to flush all at once, we discard the pending output, in order to prevent the delay to accumulate. Fixes: QTCREATORBUG-23944 Task-number: QTCREATORBUG-22914 Change-Id: I7cfef939a85bbd13730f607b0f83c36473b0e550 Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -32,6 +32,7 @@
|
|||||||
#include "icore.h"
|
#include "icore.h"
|
||||||
|
|
||||||
#include <utils/outputformatter.h>
|
#include <utils/outputformatter.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
@@ -45,6 +46,10 @@
|
|||||||
#include <QtTest>
|
#include <QtTest>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
const int chunkSize = 10000;
|
||||||
|
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
@@ -68,7 +73,10 @@ public:
|
|||||||
IContext *outputWindowContext = nullptr;
|
IContext *outputWindowContext = nullptr;
|
||||||
QString settingsKey;
|
QString settingsKey;
|
||||||
OutputFormatter formatter;
|
OutputFormatter formatter;
|
||||||
|
QList<QPair<QString, OutputFormat>> queuedOutput;
|
||||||
|
QTimer queueTimer;
|
||||||
|
|
||||||
|
bool flushRequested = false;
|
||||||
bool scrollToBottom = true;
|
bool scrollToBottom = true;
|
||||||
bool linksActive = true;
|
bool linksActive = true;
|
||||||
bool zoomEnabled = false;
|
bool zoomEnabled = false;
|
||||||
@@ -98,6 +106,10 @@ OutputWindow::OutputWindow(Context context, const QString &settingsKey, QWidget
|
|||||||
setUndoRedoEnabled(false);
|
setUndoRedoEnabled(false);
|
||||||
d->formatter.setPlainTextEdit(this);
|
d->formatter.setPlainTextEdit(this);
|
||||||
|
|
||||||
|
d->queueTimer.setSingleShot(true);
|
||||||
|
d->queueTimer.setInterval(10);
|
||||||
|
connect(&d->queueTimer, &QTimer::timeout, this, &OutputWindow::handleNextOutputChunk);
|
||||||
|
|
||||||
d->settingsKey = settingsKey;
|
d->settingsKey = settingsKey;
|
||||||
|
|
||||||
d->outputWindowContext = new IContext;
|
d->outputWindowContext = new IContext;
|
||||||
@@ -224,6 +236,7 @@ void OutputWindow::keyPressEvent(QKeyEvent *ev)
|
|||||||
|
|
||||||
void OutputWindow::setLineParsers(const QList<OutputLineParser *> &parsers)
|
void OutputWindow::setLineParsers(const QList<OutputLineParser *> &parsers)
|
||||||
{
|
{
|
||||||
|
reset();
|
||||||
d->formatter.setLineParsers(parsers);
|
d->formatter.setLineParsers(parsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,18 +383,26 @@ void OutputWindow::filterNewContent()
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OutputWindow::setMaxCharCount(int count)
|
void OutputWindow::handleNextOutputChunk()
|
||||||
{
|
{
|
||||||
d->maxCharCount = count;
|
QTC_ASSERT(!d->queuedOutput.isEmpty(), return);
|
||||||
setMaximumBlockCount(count / 100);
|
auto &chunk = d->queuedOutput.first();
|
||||||
|
if (chunk.first.size() <= chunkSize) {
|
||||||
|
handleOutputChunk(chunk.first, chunk.second);
|
||||||
|
d->queuedOutput.removeFirst();
|
||||||
|
} else {
|
||||||
|
handleOutputChunk(chunk.first.left(chunkSize), chunk.second);
|
||||||
|
chunk.first.remove(0, chunkSize);
|
||||||
|
}
|
||||||
|
if (!d->queuedOutput.isEmpty())
|
||||||
|
d->queueTimer.start();
|
||||||
|
else if (d->flushRequested) {
|
||||||
|
d->formatter.flush();
|
||||||
|
d->flushRequested = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int OutputWindow::maxCharCount() const
|
void OutputWindow::handleOutputChunk(const QString &output, OutputFormat format)
|
||||||
{
|
|
||||||
return d->maxCharCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OutputWindow::appendMessage(const QString &output, OutputFormat format)
|
|
||||||
{
|
{
|
||||||
QString out = output;
|
QString out = output;
|
||||||
if (out.size() > d->maxCharCount) {
|
if (out.size() > d->maxCharCount) {
|
||||||
@@ -422,6 +443,27 @@ void OutputWindow::appendMessage(const QString &output, OutputFormat format)
|
|||||||
enableUndoRedo();
|
enableUndoRedo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OutputWindow::setMaxCharCount(int count)
|
||||||
|
{
|
||||||
|
d->maxCharCount = count;
|
||||||
|
setMaximumBlockCount(count / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OutputWindow::maxCharCount() const
|
||||||
|
{
|
||||||
|
return d->maxCharCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OutputWindow::appendMessage(const QString &output, OutputFormat format)
|
||||||
|
{
|
||||||
|
if (d->queuedOutput.isEmpty() || d->queuedOutput.last().second != format)
|
||||||
|
d->queuedOutput << qMakePair(output, format);
|
||||||
|
else
|
||||||
|
d->queuedOutput.last().first.append(output);
|
||||||
|
if (!d->queueTimer.isActive())
|
||||||
|
d->queueTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
bool OutputWindow::isScrollbarAtBottom() const
|
bool OutputWindow::isScrollbarAtBottom() const
|
||||||
{
|
{
|
||||||
return verticalScrollBar()->value() == verticalScrollBar()->maximum();
|
return verticalScrollBar()->value() == verticalScrollBar()->maximum();
|
||||||
@@ -461,9 +503,32 @@ void OutputWindow::clear()
|
|||||||
|
|
||||||
void OutputWindow::flush()
|
void OutputWindow::flush()
|
||||||
{
|
{
|
||||||
|
const int totalQueuedSize = std::accumulate(d->queuedOutput.cbegin(), d->queuedOutput.cend(), 0,
|
||||||
|
[](int val, const QPair<QString, OutputFormat> &c) { return val + c.first.size(); });
|
||||||
|
if (totalQueuedSize > 5 * chunkSize) {
|
||||||
|
d->flushRequested = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
d->queueTimer.stop();
|
||||||
|
for (const auto &chunk : d->queuedOutput)
|
||||||
|
handleOutputChunk(chunk.first, chunk.second);
|
||||||
|
d->queuedOutput.clear();
|
||||||
d->formatter.flush();
|
d->formatter.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OutputWindow::reset()
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
d->queueTimer.stop();
|
||||||
|
d->formatter.reset();
|
||||||
|
if (!d->queuedOutput.isEmpty()) {
|
||||||
|
d->queuedOutput.clear();
|
||||||
|
d->formatter.appendMessage(tr("[Discarding excessive amount of pending output.]\n"),
|
||||||
|
ErrorMessageFormat);
|
||||||
|
}
|
||||||
|
d->flushRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
void OutputWindow::scrollToBottom()
|
void OutputWindow::scrollToBottom()
|
||||||
{
|
{
|
||||||
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ public:
|
|||||||
void grayOutOldContent();
|
void grayOutOldContent();
|
||||||
void clear();
|
void clear();
|
||||||
void flush();
|
void flush();
|
||||||
|
void reset();
|
||||||
|
|
||||||
void scrollToBottom();
|
void scrollToBottom();
|
||||||
|
|
||||||
@@ -109,6 +110,8 @@ private:
|
|||||||
QElapsedTimer m_lastMessage;
|
QElapsedTimer m_lastMessage;
|
||||||
void enableUndoRedo();
|
void enableUndoRedo();
|
||||||
void filterNewContent();
|
void filterNewContent();
|
||||||
|
void handleNextOutputChunk();
|
||||||
|
void handleOutputChunk(const QString &output, Utils::OutputFormat format);
|
||||||
|
|
||||||
Internal::OutputWindowPrivate *d = nullptr;
|
Internal::OutputWindowPrivate *d = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -703,7 +703,7 @@ void BuildManager::nextStep()
|
|||||||
connect(d->m_currentBuildStep, &BuildStep::finished, instance(), finishedHandler);
|
connect(d->m_currentBuildStep, &BuildStep::finished, instance(), finishedHandler);
|
||||||
connect(d->m_currentBuildStep, &BuildStep::progress,
|
connect(d->m_currentBuildStep, &BuildStep::progress,
|
||||||
instance(), &BuildManager::progressChanged);
|
instance(), &BuildManager::progressChanged);
|
||||||
d->m_outputWindow->outputFormatter()->reset();
|
d->m_outputWindow->reset();
|
||||||
d->m_currentBuildStep->setupOutputFormatter(d->m_outputWindow->outputFormatter());
|
d->m_currentBuildStep->setupOutputFormatter(d->m_outputWindow->outputFormatter());
|
||||||
d->m_currentBuildStep->run();
|
d->m_currentBuildStep->run();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -285,6 +285,11 @@ void CompileOutputWindow::flush()
|
|||||||
m_outputWindow->flush();
|
m_outputWindow->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CompileOutputWindow::reset()
|
||||||
|
{
|
||||||
|
m_outputWindow->reset();
|
||||||
|
}
|
||||||
|
|
||||||
void CompileOutputWindow::setSettings(const CompileOutputSettings &settings)
|
void CompileOutputWindow::setSettings(const CompileOutputSettings &settings)
|
||||||
{
|
{
|
||||||
m_settings = settings;
|
m_settings = settings;
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ public:
|
|||||||
void showPositionOf(const Task &task);
|
void showPositionOf(const Task &task);
|
||||||
|
|
||||||
void flush();
|
void flush();
|
||||||
|
void reset();
|
||||||
|
|
||||||
const CompileOutputSettings &settings() const { return m_settings; }
|
const CompileOutputSettings &settings() const { return m_settings; }
|
||||||
void setSettings(const CompileOutputSettings &settings);
|
void setSettings(const CompileOutputSettings &settings);
|
||||||
|
|||||||
Reference in New Issue
Block a user