Utils: Introduce Barrier primitive

This primitive is going to replace the TaskTree's built-in
mechanism consisting of Wait, Condition and ConditionActivator
elements.

When combining 2 barriers, one placed in a custom storage,
and the other in a tree, it's possible to fully substitute
the Wait, Condition and ConditionActivator with the comparable
amount of code.

However, the Barrier is much more versatile, since it
makes it possible to:

1. distribute the decision about the ultimate barrier pass on the
   whole tree.

   In order to utilize it, increase the limit of the shared barrier
   with setLimit() to the expected number of places that participate
   in the decision about the ultimate barrier pass and use advance()
   from multiple places in the tree. When the number of calls
   to advance() reaches the limit(), the shared barrier passes
   automatically.
   Whenever some participant failed, so that the shared barrier
   can not be passed, it may call stopWithResult(false).
   Whenever some other participant decided that all the needed
   data are already collected, so that the barrier may pass early,
   it may call stopWithResult(true), making the remaining calls to
   advance no-op.

2. wait for the same barrier from multiple places.

   Before, only one WaitFor was possible for a single Condition.

3. insert multiple Barriers into one Group element.

   Before, only one WaitFor could be placed in a single Group.

Provide ready-made SingleCondition and waitFor() helpers.
With the new approach, the following equivalents are provided:

- SingleBarrier (substitutes old Condition)
- WaitForBarrier() (substitutes old WaitFor)
- Barrier (substitutes old ConditionActivator)

This change replaces the mechanism introduced in
29f634a8ca.

This change conforms to the naming scheme proposed in QTCREATORBUG-29102.

Task-number: QTCREATORBUG-29102
Change-Id: I48b3e2ee723c3b9fe73a59a25eb7facc72940c3b
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
Jarek Kobus
2023-04-29 19:53:40 +02:00
parent 56b0d77c82
commit e46a4eba8d
4 changed files with 147 additions and 0 deletions

View File

@@ -13,6 +13,7 @@ add_qtc_library(Utils
archive.cpp archive.h archive.cpp archive.h
aspects.cpp aspects.h aspects.cpp aspects.h
asynctask.cpp asynctask.h asynctask.cpp asynctask.h
barrier.cpp barrier.h
basetreeview.cpp basetreeview.h basetreeview.cpp basetreeview.h
benchmarker.cpp benchmarker.h benchmarker.cpp benchmarker.h
buildablehelperlibrary.cpp buildablehelperlibrary.h buildablehelperlibrary.cpp buildablehelperlibrary.h

View File

@@ -0,0 +1,47 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "barrier.h"
#include "qtcassert.h"
namespace Utils {
void Barrier::setLimit(int value)
{
QTC_ASSERT(!isRunning(), return);
QTC_ASSERT(value > 0, return);
m_limit = value;
}
void Barrier::start()
{
QTC_ASSERT(!isRunning(), return);
m_current = 0;
m_result = {};
}
void Barrier::advance()
{
// Calling advance on finished is OK
QTC_ASSERT(isRunning() || m_result, return);
if (!isRunning()) // no-op
return;
++m_current;
if (m_current == m_limit)
stopWithResult(true);
}
void Barrier::stopWithResult(bool success)
{
// Calling stopWithResult on finished is OK when the same success is passed
QTC_ASSERT(isRunning() || (m_result && *m_result == success), return);
if (!isRunning()) // no-op
return;
m_current = -1;
m_result = success;
emit done(success);
}
} // namespace Utils

97
src/libs/utils/barrier.h Normal file
View File

@@ -0,0 +1,97 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "utils_global.h"
#include "tasktree.h"
namespace Utils {
class QTCREATOR_UTILS_EXPORT Barrier final : public QObject
{
Q_OBJECT
public:
void setLimit(int value);
int limit() const { return m_limit; }
void start();
void advance(); // If limit reached, stops with true
void stopWithResult(bool success); // Ignores limit
bool isRunning() const { return m_current >= 0; }
int current() const { return m_current; }
std::optional<bool> result() const { return m_result; }
signals:
void done(bool success);
private:
std::optional<bool> m_result = {};
int m_limit = 1;
int m_current = -1;
};
class QTCREATOR_UTILS_EXPORT BarrierAdapter : public Tasking::TaskAdapter<Barrier>
{
public:
BarrierAdapter() { connect(task(), &Barrier::done, this, &TaskInterface::done); }
void start() final { task()->start(); }
};
} // namespace Utils
QTC_DECLARE_CUSTOM_TASK(BarrierTask, Utils::BarrierAdapter);
namespace Utils::Tasking {
template <int Limit = 1>
class SharedBarrier
{
public:
static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more.");
SharedBarrier() : m_barrier(new Barrier) {
m_barrier->setLimit(Limit);
m_barrier->start();
}
Barrier *barrier() const { return m_barrier.get(); }
private:
std::shared_ptr<Barrier> m_barrier;
};
template <int Limit = 1>
using MultiBarrier = TreeStorage<SharedBarrier<Limit>>;
// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work.
// Can't have one alias with default type in C++17, getting the following error:
// alias template deduction only available with C++20.
using SingleBarrier = MultiBarrier<1>;
class WaitForBarrier : public BarrierTask
{
public:
template <int Limit>
WaitForBarrier(const MultiBarrier<Limit> &sharedBarrier)
: BarrierTask([sharedBarrier](Barrier &barrier) {
SharedBarrier<Limit> *activeBarrier = sharedBarrier.activeStorage();
if (!activeBarrier) {
qWarning("The barrier referenced from WaitForBarrier element "
"is not reachable in the running tree. "
"It is possible that no barrier was added to the tree, "
"or the storage is not reachable from where it is referenced. "
"The WaitForBarrier task will finish with error. ");
return TaskAction::StopWithError;
}
Barrier *activeSharedBarrier = activeBarrier->barrier();
const std::optional<bool> result = activeSharedBarrier->result();
if (result.has_value())
return result.value() ? TaskAction::StopWithDone : TaskAction::StopWithError;
QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult);
return TaskAction::Continue;
}) {}
};
} // namespace Utils::Tasking

View File

@@ -51,6 +51,8 @@ Project {
"aspects.h", "aspects.h",
"asynctask.cpp", "asynctask.cpp",
"asynctask.h", "asynctask.h",
"barrier.cpp",
"barrier.h",
"basetreeview.cpp", "basetreeview.cpp",
"basetreeview.h", "basetreeview.h",
"benchmarker.cpp", "benchmarker.cpp",