QEventDispatcherWin32: properly support 64-bit timeouts
The Windows timer APIs take intervals as uint milliseconds. As a result, the code was unconditionally truncating an qint64 millisecond value to uint, leading to incorrect timeouts. Detect such cases, and implement a logic to adjust the interval passed to the Windows APIs and send a timer event at every second timeout. This will give a 1ms or 2ms error in the timeout calculation, but that should be an acceptable tolerance when we talk about intervals around 50 days or longer. Adjust the tst_QChronoTimer::remainingTimeInitial() test to check that the remaining time is not less than "expected interval - 1 minute". This should allow to catch cases when internal calculations are terribly broken. It's not really possible to provide even a manual test for timeouts that are larger than std::numeric_limits<uint>::max(), so just rely on the existing tests to make sure that the updated implementation does not break regular cases with smaller timeouts. Amends 0d0b346322f6b078e6fe60cd3612e8d08a0d5f00. Task-number: QTBUG-129170 Change-Id: Ib9885dcb7aca18e9fb193fad14bace0efeb437da Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
34f1b6b6d1
commit
bb716c0111
@ -15,6 +15,8 @@
|
|||||||
#include "qcoreapplication_p.h"
|
#include "qcoreapplication_p.h"
|
||||||
#include <private/qthread_p.h>
|
#include <private/qthread_p.h>
|
||||||
|
|
||||||
|
#include "q26numeric.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
#ifndef TIME_KILL_SYNCHRONOUS
|
#ifndef TIME_KILL_SYNCHRONOUS
|
||||||
@ -298,7 +300,7 @@ static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatch
|
|||||||
|
|
||||||
static ULONG calculateNextTimeout(WinTimerInfo *t, quint64 currentTime)
|
static ULONG calculateNextTimeout(WinTimerInfo *t, quint64 currentTime)
|
||||||
{
|
{
|
||||||
uint interval = t->interval;
|
qint64 interval = t->interval;
|
||||||
ULONG tolerance = TIMERV_DEFAULT_COALESCING;
|
ULONG tolerance = TIMERV_DEFAULT_COALESCING;
|
||||||
switch (t->timerType) {
|
switch (t->timerType) {
|
||||||
case Qt::PreciseTimer:
|
case Qt::PreciseTimer:
|
||||||
@ -347,7 +349,13 @@ void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
|
|||||||
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
ULONG tolerance = calculateNextTimeout(t, qt_msectime());
|
ULONG tolerance = calculateNextTimeout(t, qt_msectime());
|
||||||
uint interval = t->interval;
|
uint interval = q26::saturate_cast<uint>(t->interval);
|
||||||
|
if (interval != t->interval) {
|
||||||
|
// Now we'll need to post the timer event at each second timeout.
|
||||||
|
interval = q26::saturate_cast<uint>(t->interval / 2 + 1);
|
||||||
|
t->usesExtendedInterval = true;
|
||||||
|
t->isSecondTimeout = false;
|
||||||
|
}
|
||||||
if (interval == 0u) {
|
if (interval == 0u) {
|
||||||
// optimization for single-shot-zero-timer
|
// optimization for single-shot-zero-timer
|
||||||
QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
|
QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
|
||||||
@ -390,6 +398,12 @@ void QEventDispatcherWin32Private::sendTimerEvent(int timerId)
|
|||||||
{
|
{
|
||||||
WinTimerInfo *t = timerDict.value(timerId);
|
WinTimerInfo *t = timerDict.value(timerId);
|
||||||
if (t && !t->inTimerEvent) {
|
if (t && !t->inTimerEvent) {
|
||||||
|
if (t->usesExtendedInterval && !t->isSecondTimeout) {
|
||||||
|
// That's the case when the interval is larger than uint
|
||||||
|
t->isSecondTimeout = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// send event, but don't allow it to recurse
|
// send event, but don't allow it to recurse
|
||||||
t->inTimerEvent = true;
|
t->inTimerEvent = true;
|
||||||
|
|
||||||
@ -403,6 +417,7 @@ void QEventDispatcherWin32Private::sendTimerEvent(int timerId)
|
|||||||
if (t->timerId == -1) {
|
if (t->timerId == -1) {
|
||||||
delete t;
|
delete t;
|
||||||
} else {
|
} else {
|
||||||
|
t->isSecondTimeout = false;
|
||||||
t->inTimerEvent = false;
|
t->inTimerEvent = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -702,6 +717,8 @@ void QEventDispatcherWin32::registerTimer(Qt::TimerId timerId, Duration interval
|
|||||||
t->obj = object;
|
t->obj = object;
|
||||||
t->inTimerEvent = false;
|
t->inTimerEvent = false;
|
||||||
t->fastTimerId = 0;
|
t->fastTimerId = 0;
|
||||||
|
t->usesExtendedInterval = false;
|
||||||
|
t->isSecondTimeout = false;
|
||||||
|
|
||||||
d->registerTimer(t);
|
d->registerTimer(t);
|
||||||
|
|
||||||
|
@ -93,6 +93,8 @@ struct WinTimerInfo { // internal timer info
|
|||||||
Qt::TimerType timerType;
|
Qt::TimerType timerType;
|
||||||
UINT fastTimerId;
|
UINT fastTimerId;
|
||||||
bool inTimerEvent;
|
bool inTimerEvent;
|
||||||
|
bool usesExtendedInterval;
|
||||||
|
bool isSecondTimeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QZeroTimerEvent : public QTimerEvent
|
class QZeroTimerEvent : public QTimerEvent
|
||||||
|
@ -284,6 +284,14 @@ void tst_QChronoTimer::remainingTimeInitial()
|
|||||||
} else {
|
} else {
|
||||||
QCOMPARE_LE(rt, startTimeNs);
|
QCOMPARE_LE(rt, startTimeNs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (startTimeNs > 1min) {
|
||||||
|
// The test will surely take less than 1 minute
|
||||||
|
auto startMinusOneMinute = std::chrono::nanoseconds{startTimeNs - 1min};
|
||||||
|
// If the remaining time is less than startMinusOneMinute, then
|
||||||
|
// something is terribly wrong with the internal interval calculations
|
||||||
|
QCOMPARE_GE(rt, startMinusOneMinute);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QChronoTimer::remainingTimeDuringActivation_data()
|
void tst_QChronoTimer::remainingTimeDuringActivation_data()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user