QmlDesigner: Timeout thread

The thread will be stopped after 10min. And then started if needed.

Change-Id: I66f64081353fa4f24c7927139d1676ee8de85679
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
Marco Bubke
2023-08-26 02:32:34 +02:00
parent 674d753262
commit b2fff59b8c
9 changed files with 198 additions and 101 deletions

View File

@@ -12,20 +12,7 @@ namespace QmlDesigner {
AsynchronousExplicitImageCache::AsynchronousExplicitImageCache(ImageCacheStorageInterface &storage)
: m_storage(storage)
{
m_backgroundThread = std::thread{[this] {
while (isRunning()) {
if (auto entry = getEntry(); entry) {
request(entry->name,
entry->extraId,
entry->requestType,
std::move(entry->captureCallback),
std::move(entry->abortCallback),
m_storage);
}
waitForEntries();
}
}};
}
AsynchronousExplicitImageCache::~AsynchronousExplicitImageCache()
@@ -110,9 +97,10 @@ void AsynchronousExplicitImageCache::clean()
clearEntries();
}
std::optional<AsynchronousExplicitImageCache::RequestEntry> AsynchronousExplicitImageCache::getEntry()
std::optional<AsynchronousExplicitImageCache::RequestEntry> AsynchronousExplicitImageCache::getEntry(
std::unique_lock<std::mutex> lock)
{
std::unique_lock lock{m_mutex};
auto l = std::move(lock);
if (m_requestEntries.empty())
return {};
@@ -131,6 +119,8 @@ void AsynchronousExplicitImageCache::addEntry(Utils::PathString &&name,
{
std::unique_lock lock{m_mutex};
ensureThreadIsRunning();
m_requestEntries.emplace_back(std::move(name),
std::move(extraId),
std::move(captureCallback),
@@ -146,11 +136,23 @@ void AsynchronousExplicitImageCache::clearEntries()
m_requestEntries.clear();
}
void AsynchronousExplicitImageCache::waitForEntries()
std::tuple<std::unique_lock<std::mutex>, bool> AsynchronousExplicitImageCache::waitForEntries()
{
using namespace std::literals::chrono_literals;
std::unique_lock lock{m_mutex};
if (m_requestEntries.empty())
m_condition.wait(lock, [&] { return m_requestEntries.size() || m_finishing; });
if (m_finishing)
return {std::move(lock), true};
if (m_requestEntries.empty()) {
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
return m_requestEntries.size() || m_finishing;
});
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
m_sleeping = true;
return {std::move(lock), true};
}
}
return {std::move(lock), false};
}
void AsynchronousExplicitImageCache::stopThread()
@@ -159,10 +161,35 @@ void AsynchronousExplicitImageCache::stopThread()
m_finishing = true;
}
bool AsynchronousExplicitImageCache::isRunning()
void AsynchronousExplicitImageCache::ensureThreadIsRunning()
{
std::unique_lock lock{m_mutex};
return !m_finishing || m_requestEntries.size();
if (m_finishing)
return;
if (!m_sleeping)
return;
if (m_backgroundThread.joinable())
m_backgroundThread.join();
m_sleeping = false;
m_backgroundThread = std::thread{[this] {
while (true) {
auto [lock, abort] = waitForEntries();
if (abort)
return;
if (auto entry = getEntry(std::move(lock)); entry) {
request(entry->name,
entry->extraId,
entry->requestType,
std::move(entry->captureCallback),
std::move(entry->abortCallback),
m_storage);
}
}
}};
}
} // namespace QmlDesigner

View File

@@ -18,23 +18,6 @@ AsynchronousImageCache::AsynchronousImageCache(ImageCacheStorageInterface &stora
, m_generator(generator)
, m_timeStampProvider(timeStampProvider)
{
m_backgroundThread = std::thread{[this] {
while (isRunning()) {
if (auto entry = getEntry(); entry) {
request(entry->name,
entry->extraId,
entry->requestType,
std::move(entry->captureCallback),
std::move(entry->abortCallback),
std::move(entry->auxiliaryData),
m_storage,
m_generator,
m_timeStampProvider);
}
waitForEntries();
}
}};
}
AsynchronousImageCache::~AsynchronousImageCache()
@@ -169,10 +152,10 @@ void AsynchronousImageCache::clean()
m_generator.clean();
}
std::optional<AsynchronousImageCache::Entry> AsynchronousImageCache::getEntry()
std::optional<AsynchronousImageCache::Entry> AsynchronousImageCache::getEntry(
std::unique_lock<std::mutex> lock)
{
std::unique_lock lock{m_mutex};
auto l = std::move(lock);
if (m_entries.empty())
return {};
@@ -191,6 +174,8 @@ void AsynchronousImageCache::addEntry(Utils::PathString &&name,
{
std::unique_lock lock{m_mutex};
ensureThreadIsRunning();
m_entries.emplace_back(std::move(name),
std::move(extraId),
std::move(captureCallback),
@@ -207,11 +192,23 @@ void AsynchronousImageCache::clearEntries()
m_entries.clear();
}
void AsynchronousImageCache::waitForEntries()
std::tuple<std::unique_lock<std::mutex>, bool> AsynchronousImageCache::waitForEntries()
{
using namespace std::literals::chrono_literals;
std::unique_lock lock{m_mutex};
if (m_entries.empty())
m_condition.wait(lock, [&] { return m_entries.size() || m_finishing; });
if (m_finishing)
return {std::move(lock), true};
if (m_entries.empty()) {
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
return m_entries.size() || m_finishing;
});
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
m_sleeping = true;
return {std::move(lock), true};
}
}
return {std::move(lock), false};
}
void AsynchronousImageCache::stopThread()
@@ -220,10 +217,37 @@ void AsynchronousImageCache::stopThread()
m_finishing = true;
}
bool AsynchronousImageCache::isRunning()
void AsynchronousImageCache::ensureThreadIsRunning()
{
std::unique_lock lock{m_mutex};
return !m_finishing || m_entries.size();
if (m_finishing)
return;
if (!m_sleeping)
return;
if (m_backgroundThread.joinable())
m_backgroundThread.join();
m_sleeping = false;
m_backgroundThread = std::thread{[this] {
while (true) {
auto [lock, abort] = waitForEntries();
if (abort)
return;
if (auto entry = getEntry(std::move(lock)); entry) {
request(entry->name,
entry->extraId,
entry->requestType,
std::move(entry->captureCallback),
std::move(entry->abortCallback),
std::move(entry->auxiliaryData),
m_storage,
m_generator,
m_timeStampProvider);
}
}
}};
}
} // namespace QmlDesigner

View File

@@ -17,20 +17,7 @@ AsynchronousImageFactory::AsynchronousImageFactory(ImageCacheStorageInterface &s
, m_timeStampProvider(timeStampProvider)
, m_collector(collector)
{
m_backgroundThread = std::thread{[this] {
while (isRunning()) {
if (auto entry = getEntry(); entry) {
request(entry->name,
entry->extraId,
std::move(entry->auxiliaryData),
m_storage,
m_timeStampProvider,
m_collector);
}
waitForEntries();
}
}};
}
AsynchronousImageFactory::~AsynchronousImageFactory()
@@ -53,25 +40,64 @@ void AsynchronousImageFactory::addEntry(Utils::SmallStringView name,
{
std::unique_lock lock{m_mutex};
ensureThreadIsRunning();
m_entries.emplace_back(std::move(name), std::move(extraId), std::move(auxiliaryData));
}
bool AsynchronousImageFactory::isRunning()
void AsynchronousImageFactory::ensureThreadIsRunning()
{
std::unique_lock lock{m_mutex};
return !m_finishing || m_entries.size();
if (m_finishing)
return;
if (!m_sleeping)
return;
if (m_backgroundThread.joinable())
m_backgroundThread.join();
m_sleeping = false;
m_backgroundThread = std::thread{[this] {
while (true) {
auto [lock, abort] = waitForEntries();
if (abort)
return;
if (auto entry = getEntry(std::move(lock)); entry) {
request(entry->name,
entry->extraId,
std::move(entry->auxiliaryData),
m_storage,
m_timeStampProvider,
m_collector);
}
}
}};
}
void AsynchronousImageFactory::waitForEntries()
std::tuple<std::unique_lock<std::mutex>, bool> AsynchronousImageFactory::waitForEntries()
{
using namespace std::literals::chrono_literals;
std::unique_lock lock{m_mutex};
if (m_entries.empty())
m_condition.wait(lock, [&] { return m_entries.size() || m_finishing; });
if (m_finishing)
return {std::move(lock), true};
if (m_entries.empty()) {
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
return m_entries.size() || m_finishing;
});
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
m_sleeping = true;
return {std::move(lock), true};
}
}
return {std::move(lock), false};
}
std::optional<AsynchronousImageFactory::Entry> AsynchronousImageFactory::getEntry()
std::optional<AsynchronousImageFactory::Entry> AsynchronousImageFactory::getEntry(
std::unique_lock<std::mutex> lock)
{
std::unique_lock lock{m_mutex};
auto l = std::move(lock);
if (m_entries.empty())
return {};

View File

@@ -53,9 +53,9 @@ private:
void addEntry(Utils::SmallStringView name,
Utils::SmallStringView extraId,
ImageCache::AuxiliaryData &&auxiliaryData);
bool isRunning();
void waitForEntries();
std::optional<Entry> getEntry();
void ensureThreadIsRunning();
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForEntries();
[[nodiscard]] std::optional<Entry> getEntry(std::unique_lock<std::mutex> lock);
void request(Utils::SmallStringView name,
Utils::SmallStringView extraId,
ImageCache::AuxiliaryData auxiliaryData,
@@ -75,6 +75,7 @@ private:
TimeStampProviderInterface &m_timeStampProvider;
ImageCacheCollectorInterface &m_collector;
bool m_finishing{false};
bool m_sleeping{true};
};
} // namespace QmlDesigner

View File

@@ -14,10 +14,7 @@ ImageCacheGenerator::ImageCacheGenerator(ImageCacheCollectorInterface &collector
ImageCacheStorageInterface &storage)
: m_collector{collector}
, m_storage(storage)
{
m_backgroundThread.reset(QThread::create([this]() { startGeneration(); }));
m_backgroundThread->start();
}
{}
ImageCacheGenerator::~ImageCacheGenerator()
{
@@ -25,6 +22,22 @@ ImageCacheGenerator::~ImageCacheGenerator()
waitForFinished();
}
void ImageCacheGenerator::ensureThreadIsRunning()
{
if (m_finishing)
return;
if (m_sleeping) {
if (m_backgroundThread)
m_backgroundThread->wait();
m_sleeping = false;
m_backgroundThread.reset(QThread::create([this]() { startGeneration(); }));
m_backgroundThread->start();
}
}
void ImageCacheGenerator::generateImage(Utils::SmallStringView name,
Utils::SmallStringView extraId,
Sqlite::TimeStamp timeStamp,
@@ -35,6 +48,8 @@ void ImageCacheGenerator::generateImage(Utils::SmallStringView name,
{
std::lock_guard lock{m_mutex};
ensureThreadIsRunning();
auto found = std::find_if(m_tasks.begin(), m_tasks.end(), [&](const Task &task) {
return task.filePath == name && task.extraId == extraId;
});
@@ -91,18 +106,14 @@ void ImageCacheGenerator::waitForFinished()
void ImageCacheGenerator::startGeneration()
{
while (isRunning()) {
waitForEntries();
while (true) {
Task task;
{
std::lock_guard lock{m_mutex};
auto [lock, abort] = waitForEntries();
if (m_finishing && m_tasks.empty()) {
m_storage.walCheckpointFull();
if (abort)
return;
}
task = std::move(m_tasks.front());
@@ -141,11 +152,23 @@ void ImageCacheGenerator::startGeneration()
}
}
void ImageCacheGenerator::waitForEntries()
std::tuple<std::unique_lock<std::mutex>, bool> ImageCacheGenerator::waitForEntries()
{
using namespace std::literals::chrono_literals;
std::unique_lock lock{m_mutex};
if (m_tasks.empty())
m_condition.wait(lock, [&] { return m_tasks.size() || m_finishing; });
if (m_finishing)
return {std::move(lock), true};
if (m_tasks.empty()) {
auto timedOutWithoutEntriesOrFinishing = !m_condition.wait_for(lock, 10min, [&] {
return m_tasks.size() || m_finishing;
});
if (timedOutWithoutEntriesOrFinishing || m_finishing) {
m_sleeping = true;
return {std::move(lock), true};
}
}
return {std::move(lock), false};
}
void ImageCacheGenerator::stopThread()
@@ -154,10 +177,4 @@ void ImageCacheGenerator::stopThread()
m_finishing = true;
}
bool ImageCacheGenerator::isRunning()
{
std::unique_lock lock{m_mutex};
return !m_finishing || m_tasks.size();
}
} // namespace QmlDesigner

View File

@@ -67,10 +67,9 @@ private:
};
void startGeneration();
void waitForEntries();
void ensureThreadIsRunning();
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForEntries();
void stopThread();
bool isRunning();
private:
private:
@@ -81,6 +80,7 @@ private:
ImageCacheCollectorInterface &m_collector;
ImageCacheStorageInterface &m_storage;
bool m_finishing{false};
bool m_sleeping{true};
};
} // namespace QmlDesigner

View File

@@ -62,16 +62,16 @@ private:
RequestType requestType = RequestType::Image;
};
std::optional<RequestEntry> getEntry();
[[nodiscard]] std::optional<RequestEntry> getEntry(std::unique_lock<std::mutex> lock);
void addEntry(Utils::PathString &&name,
Utils::SmallString &&extraId,
ImageCache::CaptureImageCallback &&captureCallback,
ImageCache::AbortCallback &&abortCallback,
RequestType requestType);
void clearEntries();
void waitForEntries();
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForEntries();
void stopThread();
bool isRunning();
void ensureThreadIsRunning();
static void request(Utils::SmallStringView name,
Utils::SmallStringView extraId,
AsynchronousExplicitImageCache::RequestType requestType,
@@ -89,6 +89,7 @@ private:
std::thread m_backgroundThread;
ImageCacheStorageInterface &m_storage;
bool m_finishing{false};
bool m_sleeping{true};
};
} // namespace QmlDesigner

View File

@@ -73,7 +73,7 @@ private:
RequestType requestType = RequestType::Image;
};
std::optional<Entry> getEntry();
[[nodiscard]] std::optional<Entry> getEntry(std::unique_lock<std::mutex> lock);
void addEntry(Utils::PathString &&name,
Utils::SmallString &&extraId,
ImageCache::CaptureImageCallback &&captureCallback,
@@ -81,9 +81,9 @@ private:
ImageCache::AuxiliaryData &&auxiliaryData,
RequestType requestType);
void clearEntries();
void waitForEntries();
[[nodiscard]] std::tuple<std::unique_lock<std::mutex>, bool> waitForEntries();
void stopThread();
bool isRunning();
void ensureThreadIsRunning();
static void request(Utils::SmallStringView name,
Utils::SmallStringView extraId,
AsynchronousImageCache::RequestType requestType,
@@ -106,6 +106,7 @@ private:
ImageCacheGeneratorInterface &m_generator;
TimeStampProviderInterface &m_timeStampProvider;
bool m_finishing{false};
bool m_sleeping{true};
};
} // namespace QmlDesigner

View File

@@ -391,7 +391,7 @@ TEST_F(ImageCacheGenerator, wait_for_finished)
generator.generateImage(
"name2", {}, {11}, imageCallbackMock.AsStdFunction(), abortCallbackMock.AsStdFunction(), {});
EXPECT_CALL(imageCallbackMock, Call(_, _, _)).Times(2);
EXPECT_CALL(imageCallbackMock, Call(_, _, _)).Times(AtMost(2));
waitInThread.notify();
generator.waitForFinished();