forked from qt-creator/qt-creator
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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user