summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qtimerinfo_unix.cpp
diff options
context:
space:
mode:
authorAhmad Samir <a.samirh78@gmail.com>2023-02-23 13:33:11 +0200
committerAhmad Samir <a.samirh78@gmail.com>2023-02-24 18:30:38 +0200
commit6754795e547211727b7e0409dd0c8eeff441bbfe (patch)
treeb224855b68c7ec627d7cf6050f513a92c3875eaa /src/corelib/kernel/qtimerinfo_unix.cpp
parent39a4cd126f177355e326238d95baaeaf845e9453 (diff)
QTimerInfo: use chrono for time intervals
For VeryCoarseTimer: - The code used to convert QTimerInfo::interval to seconds in registerTimer(), then convert to milliseconds when creating QTimerInfo in registeredTimers(); this is a bit confusing as "interval" was sometimes milliseconds and sometimes seconds depending on the type of the timer; instead "round" to the nearest second while always keeping "interval" in milliseconds (relying on chrono doing the conversion).. - Add roundToSecs() helper; it behaves like the original code (i.e. rounding each 500ms to 1 full second): const auto list = {300, 499, 500, 600, 1000, 1300, 1499, 1500, 1501, 1600, 2000, 2300, 2499, 2500, 2600}; using namespace std::chrono; for (int dur : list) { auto i = dur; i /= 500; i += 1; i >>= 1; // Original code milliseconds msec{dur}; seconds secs = duration_cast<seconds>(msec); milliseconds frac = msec - secs; if (frac >= 500ms) secs += 1s; assert(i == secs.count()); } ---- - Don't mix signed and unsigned when doing arithmetic The next "chrono-first" step would be changing QAbstractEventDispatcher::TimerInfo::interval from int to chrono::milliseconds, and adding a virtual QAbstractEventDispatcher::registerTimer() overload that takes chrono::milliseconds; neither can be done until Qt7 due to binary compatibility constraints, c.f.: https://community.kde.org/Policies/Binary_Compatibility_Issues_With_C++ Task-number: QTBUG-110059 Change-Id: I36f9bd8fb29565b1131afb3cdfc313452f625598 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Diffstat (limited to 'src/corelib/kernel/qtimerinfo_unix.cpp')
-rw-r--r--src/corelib/kernel/qtimerinfo_unix.cpp166
1 files changed, 93 insertions, 73 deletions
diff --git a/src/corelib/kernel/qtimerinfo_unix.cpp b/src/corelib/kernel/qtimerinfo_unix.cpp
index 0a9e7e0a6a..48b1c2cb71 100644
--- a/src/corelib/kernel/qtimerinfo_unix.cpp
+++ b/src/corelib/kernel/qtimerinfo_unix.cpp
@@ -9,7 +9,6 @@
#include "private/qtimerinfo_unix_p.h"
#include "private/qobject_p.h"
#include "private/qabstracteventdispatcher_p.h"
-#include <QtCore/private/qtools_p.h>
#ifdef QTIMERINFO_DEBUG
# include <QDebug>
@@ -18,6 +17,8 @@
#include <sys/times.h>
+using namespace std::chrono;
+
QT_BEGIN_NAMESPACE
Q_CORE_EXPORT bool qt_disable_lowpriority_timers=false;
@@ -153,6 +154,25 @@ static_assert(roundToMillisecond({0, 1'000'000}) == timespec{0, 1'000'000});
static_assert(roundToMillisecond({0, 999'999'999}) == timespec{1, 0});
static_assert(roundToMillisecond({1, 0}) == timespec{1, 0});
+static constexpr seconds roundToSecs(milliseconds msecs)
+{
+ // The very coarse timer is based on full second precision, so we want to
+ // round the interval to the closest second, rounding 500ms up to 1s.
+ //
+ // std::chrono::round() wouldn't work with all multiples of 500 because for the
+ // middle point it would round to even:
+ // value round() wanted
+ // 500 0 1
+ // 1500 2 2
+ // 2500 2 3
+
+ auto secs = duration_cast<seconds>(msecs);
+ const milliseconds frac = msecs - secs;
+ if (frac >= 500ms)
+ ++secs;
+ return secs;
+}
+
#ifdef QTIMERINFO_DEBUG
QDebug operator<<(QDebug s, timeval tv)
{
@@ -187,97 +207,96 @@ static void calculateCoarseTimerTimeout(QTimerInfo *t, timespec currentTime)
//
// The objective is to make most timers wake up at the same time, thereby reducing CPU wakeups.
- uint interval = uint(t->interval);
- Q_ASSERT(interval >= 20);
+ Q_ASSERT(t->interval >= 20ms);
+
// Calculate how much we can round and still keep within 5% error
- uint absMaxRounding = interval / 20;
+ const milliseconds absMaxRounding = t->interval / 20;
- using namespace std::chrono;
- uint msec = duration_cast<milliseconds>(nanoseconds{t->timeout.tv_nsec}).count();
+ auto fracMsec = duration_cast<milliseconds>(nanoseconds{t->timeout.tv_nsec});
- if (interval < 100 && interval != 25 && interval != 50 && interval != 75) {
+ if (t->interval < 100ms && t->interval != 25ms && t->interval != 50ms && t->interval != 75ms) {
+ auto fracCount = fracMsec.count();
// special mode for timers of less than 100 ms
- if (interval < 50) {
+ if (t->interval < 50ms) {
// round to even
// round towards multiples of 50 ms
- bool roundUp = (msec % 50) >= 25;
- msec >>= 1;
- msec |= uint(roundUp);
- msec <<= 1;
+ bool roundUp = (fracCount % 50) >= 25;
+ fracCount >>= 1;
+ fracCount |= roundUp;
+ fracCount <<= 1;
} else {
// round to multiple of 4
// round towards multiples of 100 ms
- bool roundUp = (msec % 100) >= 50;
- msec >>= 2;
- msec |= uint(roundUp);
- msec <<= 2;
+ bool roundUp = (fracCount % 100) >= 50;
+ fracCount >>= 2;
+ fracCount |= roundUp;
+ fracCount <<= 2;
}
+ fracMsec = milliseconds{fracCount};
} else {
- uint min = qMax<int>(0, msec - absMaxRounding);
- uint max = qMin(1000u, msec + absMaxRounding);
+ milliseconds min = std::max(0ms, fracMsec - absMaxRounding);
+ milliseconds max = std::min(1000ms, fracMsec + absMaxRounding);
// find the boundary that we want, according to the rules above
// extra rules:
// 1) whatever the interval, we'll take any round-to-the-second timeout
- if (min == 0) {
- msec = 0;
+ if (min == 0ms) {
+ fracMsec = 0ms;
goto recalculate;
- } else if (max == 1000) {
- msec = 1000;
+ } else if (max == 1000ms) {
+ fracMsec = 1000ms;
goto recalculate;
}
- uint wantedBoundaryMultiple;
+ milliseconds wantedBoundaryMultiple{25};
// 2) if the interval is a multiple of 500 ms and > 5000 ms, we'll always round
// towards a round-to-the-second
// 3) if the interval is a multiple of 500 ms, we'll round towards the nearest
// multiple of 500 ms
- if ((interval % 500) == 0) {
- if (interval >= 5000) {
- msec = msec >= 500 ? max : min;
+ if ((t->interval % 500) == 0ms) {
+ if (t->interval >= 5s) {
+ fracMsec = fracMsec >= 500ms ? max : min;
goto recalculate;
} else {
- wantedBoundaryMultiple = 500;
+ wantedBoundaryMultiple = 500ms;
}
- } else if ((interval % 50) == 0) {
+ } else if ((t->interval % 50) == 0ms) {
// 4) same for multiples of 250, 200, 100, 50
- uint mult50 = interval / 50;
- if ((mult50 % 4) == 0) {
+ milliseconds mult50 = t->interval / 50;
+ if ((mult50 % 4) == 0ms) {
// multiple of 200
- wantedBoundaryMultiple = 200;
- } else if ((mult50 % 2) == 0) {
+ wantedBoundaryMultiple = 200ms;
+ } else if ((mult50 % 2) == 0ms) {
// multiple of 100
- wantedBoundaryMultiple = 100;
- } else if ((mult50 % 5) == 0) {
+ wantedBoundaryMultiple = 100ms;
+ } else if ((mult50 % 5) == 0ms) {
// multiple of 250
- wantedBoundaryMultiple = 250;
+ wantedBoundaryMultiple = 250ms;
} else {
// multiple of 50
- wantedBoundaryMultiple = 50;
+ wantedBoundaryMultiple = 50ms;
}
- } else {
- wantedBoundaryMultiple = 25;
}
- uint base = msec / wantedBoundaryMultiple * wantedBoundaryMultiple;
- uint middlepoint = base + wantedBoundaryMultiple / 2;
- if (msec < middlepoint)
- msec = qMax(base, min);
+ milliseconds base = (fracMsec / wantedBoundaryMultiple) * wantedBoundaryMultiple;
+ milliseconds middlepoint = base + wantedBoundaryMultiple / 2;
+ if (fracMsec < middlepoint)
+ fracMsec = qMax(base, min);
else
- msec = qMin(base + wantedBoundaryMultiple, max);
+ fracMsec = qMin(base + wantedBoundaryMultiple, max);
}
recalculate:
- if (msec == 1000u) {
+ if (fracMsec == 1000ms) {
++t->timeout.tv_sec;
t->timeout.tv_nsec = 0;
} else {
- t->timeout.tv_nsec = nanoseconds{milliseconds{msec}}.count();
+ t->timeout.tv_nsec = nanoseconds{fracMsec}.count();
}
if (t->timeout < currentTime)
- t->timeout += interval;
+ t->timeout += t->interval;
}
static void calculateNextTimeout(QTimerInfo *t, timespec currentTime)
@@ -302,10 +321,11 @@ static void calculateNextTimeout(QTimerInfo *t, timespec currentTime)
return;
case Qt::VeryCoarseTimer:
- // we don't need to take care of the microsecond component of t->interval
- t->timeout.tv_sec += t->interval;
+ // t->interval already rounded to full seconds in registerTimer()
+ const auto secs = duration_cast<seconds>(t->interval).count();
+ t->timeout.tv_sec += secs;
if (t->timeout.tv_sec <= currentTime.tv_sec)
- t->timeout.tv_sec = currentTime.tv_sec + t->interval;
+ t->timeout.tv_sec = currentTime.tv_sec + secs;
#ifdef QTIMERINFO_DEBUG
t->expected.tv_sec += t->interval;
if (t->expected.tv_sec <= currentTime.tv_sec)
@@ -362,6 +382,11 @@ bool QTimerInfoList::timerWait(timespec &tm)
*/
qint64 QTimerInfoList::timerRemainingTime(int timerId)
{
+ return remainingDuration(timerId).count();
+}
+
+milliseconds QTimerInfoList::remainingDuration(int timerId)
+{
timespec currentTime = updateCurrentTime();
repairTimersIfNeeded();
timespec tm = {0, 0};
@@ -371,10 +396,9 @@ qint64 QTimerInfoList::timerRemainingTime(int timerId)
if (currentTime < t->timeout) {
// time to wait
tm = roundToMillisecond(t->timeout - currentTime);
- const std::chrono::milliseconds dur = QtMiscUtils::timespecToChronoMs(&tm);
- return dur.count();
+ return QtMiscUtils::timespecToChronoMs(&tm);
} else {
- return 0;
+ return milliseconds{0};
}
}
}
@@ -383,11 +407,17 @@ qint64 QTimerInfoList::timerRemainingTime(int timerId)
qWarning("QTimerInfoList::timerRemainingTime: timer id %i not found", timerId);
#endif
- return -1;
+ return milliseconds{-1};
}
void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
{
+ registerTimer(timerId, milliseconds{interval}, timerType, object);
+}
+
+void QTimerInfoList::registerTimer(int timerId, milliseconds interval,
+ Qt::TimerType timerType, QObject *object)
+{
QTimerInfo *t = new QTimerInfo;
t->id = timerId;
t->interval = interval;
@@ -409,30 +439,26 @@ void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType t
// so our boundaries are 20 ms and 20 s
// below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision
// above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer
- if (interval >= 20000) {
+ if (interval >= 20s) {
t->timerType = Qt::VeryCoarseTimer;
} else {
t->timeout = expected;
- if (interval <= 20) {
+ if (interval <= 20ms) {
t->timerType = Qt::PreciseTimer;
// no adjustment is necessary
- } else if (interval <= 20000) {
+ } else if (interval <= 20s) {
calculateCoarseTimerTimeout(t, currentTime);
}
break;
}
Q_FALLTHROUGH();
case Qt::VeryCoarseTimer:
- // the very coarse timer is based on full second precision,
- // so we keep the interval in seconds (round to closest second)
- t->interval /= 500;
- t->interval += 1;
- t->interval >>= 1;
- t->timeout.tv_sec = currentTime.tv_sec + t->interval;
+ const seconds secs = roundToSecs(t->interval);
+ t->interval = secs;
+ t->timeout.tv_sec = currentTime.tv_sec + secs.count();
t->timeout.tv_nsec = 0;
// if we're past the half-second mark, increase the timeout again
- using namespace std::chrono;
if (currentTime.tv_nsec > nanoseconds{500ms}.count())
++t->timeout.tv_sec;
}
@@ -493,15 +519,9 @@ bool QTimerInfoList::unregisterTimers(QObject *object)
QList<QAbstractEventDispatcher::TimerInfo> QTimerInfoList::registeredTimers(QObject *object) const
{
QList<QAbstractEventDispatcher::TimerInfo> list;
- for (int i = 0; i < size(); ++i) {
- const QTimerInfo * const t = at(i);
- if (t->obj == object) {
- list << QAbstractEventDispatcher::TimerInfo(t->id,
- (t->timerType == Qt::VeryCoarseTimer
- ? t->interval * 1000
- : t->interval),
- t->timerType);
- }
+ for (const QTimerInfo *const t : std::as_const(*this)) {
+ if (t->obj == object)
+ list.emplaceBack(t->id, t->interval.count(), t->timerType);
}
return list;
}
@@ -576,7 +596,7 @@ int QTimerInfoList::activateTimers()
// reinsert timer
timerInsert(currentTimerInfo);
- if (currentTimerInfo->interval > 0)
+ if (currentTimerInfo->interval > 0ms)
n_act++;
// Send event, but don't allow it to recurse: