TaskTree: Make the TimeoutTask reliable

Ensure the timeout tasks preserve the right order of
their done signals.

Change-Id: I62508d0710eb2324d7c347a9907a899c97d3975d
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
Jarek Kobus
2023-05-31 18:29:11 +02:00
parent f6e7dbd416
commit 1599106a22
3 changed files with 90 additions and 3 deletions

View File

@@ -9,6 +9,8 @@
#include <QSet> #include <QSet>
#include <QTimer> #include <QTimer>
using namespace std::chrono;
namespace Tasking { namespace Tasking {
// That's cut down qtcassert.{c,h} to avoid the dependency. // That's cut down qtcassert.{c,h} to avoid the dependency.
@@ -1848,14 +1850,95 @@ void TaskTreeTaskAdapter::start()
task()->start(); task()->start();
} }
using TimeoutCallback = std::function<void()>;
struct TimerData
{
system_clock::time_point m_deadline;
QPointer<QObject> m_context;
TimeoutCallback m_callback;
};
QMutex s_mutex;
std::atomic_int s_timerId = 0;
QHash<int, TimerData> s_timerIdToTimerData = {};
QMultiMap<system_clock::time_point, int> s_deadlineToTimerId = {};
static QList<TimerData> prepareForActivation(int timerId)
{
QMutexLocker lock(&s_mutex);
const auto it = s_timerIdToTimerData.constFind(timerId);
if (it == s_timerIdToTimerData.cend())
return {}; // the timer was already activated
const system_clock::time_point deadline = it->m_deadline;
QList<TimerData> toActivate;
auto itMap = s_deadlineToTimerId.cbegin();
while (itMap != s_deadlineToTimerId.cend()) {
if (itMap.key() > deadline)
break;
const auto it = s_timerIdToTimerData.constFind(itMap.value());
if (it != s_timerIdToTimerData.cend()) {
toActivate.append(it.value());
s_timerIdToTimerData.erase(it);
}
itMap = s_deadlineToTimerId.erase(itMap);
}
return toActivate;
}
static void removeTimerId(int timerId)
{
QMutexLocker lock(&s_mutex);
const auto it = s_timerIdToTimerData.constFind(timerId);
QTC_ASSERT(it != s_timerIdToTimerData.cend(),
qWarning("Removing active timerId failed."); return);
const system_clock::time_point deadline = it->m_deadline;
s_timerIdToTimerData.erase(it);
const int removedCount = s_deadlineToTimerId.remove(deadline, timerId);
QTC_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return);
}
static void handleTimeout(int timerId)
{
const QList<TimerData> toActivate = prepareForActivation(timerId);
for (const TimerData &timerData : toActivate) {
if (timerData.m_context)
QMetaObject::invokeMethod(timerData.m_context.get(), timerData.m_callback);
}
}
static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback)
{
const int timerId = s_timerId.fetch_add(1) + 1;
const system_clock::time_point deadline = system_clock::now() + timeout;
QTimer::singleShot(timeout, context, [timerId] { handleTimeout(timerId); });
QMutexLocker lock(&s_mutex);
s_timerIdToTimerData.emplace(timerId, TimerData{deadline, context, callback});
s_deadlineToTimerId.insert(deadline, timerId);
return timerId;
}
TimeoutTaskAdapter::TimeoutTaskAdapter() TimeoutTaskAdapter::TimeoutTaskAdapter()
{ {
*task() = std::chrono::milliseconds::zero(); *task() = std::chrono::milliseconds::zero();
} }
TimeoutTaskAdapter::~TimeoutTaskAdapter()
{
if (m_timerId)
removeTimerId(*m_timerId);
}
void TimeoutTaskAdapter::start() void TimeoutTaskAdapter::start()
{ {
QTimer::singleShot(*task(), this, [this] { emit done(true); }); if (*task() == milliseconds::zero())
QTimer::singleShot(0, this, [this] { emit done(true); });
else
m_timerId = scheduleTimeout(*task(), this, [this] { m_timerId = {}; emit done(true); });
} }
} // namespace Tasking } // namespace Tasking

View File

@@ -430,7 +430,11 @@ class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter<std::chrono::millis
{ {
public: public:
TimeoutTaskAdapter(); TimeoutTaskAdapter();
~TimeoutTaskAdapter();
void start() final; void start() final;
private:
std::optional<int> m_timerId;
}; };
} // namespace Tasking } // namespace Tasking

View File

@@ -781,7 +781,7 @@ void tst_Tasking::testTree_data()
parallel, parallel,
workflowPolicy(policy), workflowPolicy(policy),
createFailingTask(1, 1ms), createFailingTask(1, 1ms),
createSuccessTask(2, 2ms), createSuccessTask(2, 1ms),
groupDone(0), groupDone(0),
groupError(0) groupError(0)
}; };
@@ -853,7 +853,7 @@ void tst_Tasking::testTree_data()
workflowPolicy(policy), workflowPolicy(policy),
createSuccessTask(1), createSuccessTask(1),
createFailingTask(2, 1ms), createFailingTask(2, 1ms),
createSuccessTask(3, 2ms), createSuccessTask(3, 1ms),
groupDone(0), groupDone(0),
groupError(0) groupError(0)
}; };