Fix the remainingTime() result after the first activation of a QTimer

On Windows, t->timeout was updated only once, at creation time, so the
timer would always show as "overdue" after the first activation.

The timer is updated to indicate the full remaining time during the slot
activation, which is the behavior of the Unix and Glib dispatchers.

Task-number: QTBUG-46940
Change-Id: I255870833a024a36adf6ffff13ecadb021c4358c
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@theqtcompany.com>
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@theqtcompany.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Thiago Macieira 2015-06-30 18:15:53 -07:00
parent 1ac0c4a2f7
commit d01d08971a
2 changed files with 74 additions and 6 deletions

View File

@ -561,6 +561,17 @@ static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatch
return wnd; return wnd;
} }
static void calculateNextTimeout(WinTimerInfo *t, quint64 currentTime)
{
uint interval = t->interval;
if ((interval >= 20000u && t->timerType != Qt::PreciseTimer) || t->timerType == Qt::VeryCoarseTimer) {
// round the interval, VeryCoarseTimers only have full second accuracy
interval = ((interval + 500)) / 1000 * 1000;
}
t->interval = interval;
t->timeout = currentTime + interval;
}
void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t) void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
{ {
Q_ASSERT(internalHwnd); Q_ASSERT(internalHwnd);
@ -568,6 +579,7 @@ void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
Q_Q(QEventDispatcherWin32); Q_Q(QEventDispatcherWin32);
int ok = 0; int ok = 0;
calculateNextTimeout(t, qt_msectime());
uint interval = t->interval; uint interval = t->interval;
if (interval == 0u) { if (interval == 0u) {
// optimization for single-shot-zero-timer // optimization for single-shot-zero-timer
@ -576,17 +588,13 @@ void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
} else if ((interval < 20u || t->timerType == Qt::PreciseTimer) && qtimeSetEvent) { } else if ((interval < 20u || t->timerType == Qt::PreciseTimer) && qtimeSetEvent) {
ok = t->fastTimerId = qtimeSetEvent(interval, 1, qt_fast_timer_proc, (DWORD_PTR)t, ok = t->fastTimerId = qtimeSetEvent(interval, 1, qt_fast_timer_proc, (DWORD_PTR)t,
TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS); TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);
} else if (interval >= 20000u || t->timerType == Qt::VeryCoarseTimer) {
// round the interval, VeryCoarseTimers only have full second accuracy
interval = ((interval + 500)) / 1000 * 1000;
} }
if (ok == 0) { if (ok == 0) {
// user normal timers for (Very)CoarseTimers, or if no more multimedia timers available // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available
ok = SetTimer(internalHwnd, t->timerId, interval, 0); ok = SetTimer(internalHwnd, t->timerId, interval, 0);
} }
t->timeout = qt_msectime() + interval;
if (ok == 0) if (ok == 0)
qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer"); qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer");
} }
@ -611,6 +619,9 @@ void QEventDispatcherWin32Private::sendTimerEvent(int timerId)
// send event, but don't allow it to recurse // send event, but don't allow it to recurse
t->inTimerEvent = true; t->inTimerEvent = true;
// recalculate next emission
calculateNextTimeout(t, qt_msectime());
QTimerEvent e(t->timerId); QTimerEvent e(t->timerId);
QCoreApplication::sendEvent(t->obj, &e); QCoreApplication::sendEvent(t->obj, &e);

View File

@ -54,6 +54,8 @@ private slots:
void singleShotTimeout(); void singleShotTimeout();
void timeout(); void timeout();
void remainingTime(); void remainingTime();
void remainingTimeDuringActivation_data();
void remainingTimeDuringActivation();
void livelock_data(); void livelock_data();
void livelock(); void livelock();
void timerInfiniteRecursion_data(); void timerInfiniteRecursion_data();
@ -79,14 +81,16 @@ class TimerHelper : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
TimerHelper() : QObject(), count(0) TimerHelper() : QObject(), count(0), remainingTime(-1)
{ {
} }
int count; int count;
int remainingTime;
public slots: public slots:
void timeout(); void timeout();
void fetchRemainingTime();
}; };
void TimerHelper::timeout() void TimerHelper::timeout()
@ -94,6 +98,12 @@ void TimerHelper::timeout()
++count; ++count;
} }
void TimerHelper::fetchRemainingTime()
{
QTimer *timer = static_cast<QTimer *>(sender());
remainingTime = timer->remainingTime();
}
void tst_QTimer::zeroTimer() void tst_QTimer::zeroTimer()
{ {
TimerHelper helper; TimerHelper helper;
@ -158,6 +168,53 @@ void tst_QTimer::remainingTime()
int remainingTime = timer.remainingTime(); int remainingTime = timer.remainingTime();
QVERIFY2(qAbs(remainingTime - 150) < 50, qPrintable(QString::number(remainingTime))); QVERIFY2(qAbs(remainingTime - 150) < 50, qPrintable(QString::number(remainingTime)));
// wait for the timer to actually fire now
connect(&timer, SIGNAL(timeout()), &QTestEventLoop::instance(), SLOT(exitLoop()));
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(helper.count, 1);
// the timer is still active, so it should have a non-zero remaining time
remainingTime = timer.remainingTime();
QVERIFY2(remainingTime > 150, qPrintable(QString::number(remainingTime)));
}
void tst_QTimer::remainingTimeDuringActivation_data()
{
QTest::addColumn<bool>("singleShot");
QTest::newRow("repeating") << true;
QTest::newRow("single-shot") << true;
}
void tst_QTimer::remainingTimeDuringActivation()
{
QFETCH(bool, singleShot);
TimerHelper helper;
QTimer timer;
const int timeout = 20; // 20 ms is short enough and should not round down to 0 in any timer mode
connect(&timer, SIGNAL(timeout()), &helper, SLOT(fetchRemainingTime()));
connect(&timer, SIGNAL(timeout()), &QTestEventLoop::instance(), SLOT(exitLoop()));
timer.start(timeout);
timer.setSingleShot(singleShot);
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
if (singleShot)
QCOMPARE(helper.remainingTime, -1); // timer not running
else
QCOMPARE(helper.remainingTime, timeout);
if (!singleShot) {
// do it again - see QTBUG-46940
helper.remainingTime = -1;
QTestEventLoop::instance().enterLoop(5);
QVERIFY(!QTestEventLoop::instance().timeout());
QCOMPARE(helper.remainingTime, timeout);
}
} }
void tst_QTimer::livelock_data() void tst_QTimer::livelock_data()