Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qtimerinfo_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include <qelapsedtimer.h>
6#include <qcoreapplication.h>
7
8#include "private/qcore_unix_p.h"
9#include "private/qtimerinfo_unix_p.h"
10#include "private/qobject_p.h"
11#include "private/qabstracteventdispatcher_p.h"
12
13#include <sys/times.h>
14
15using namespace std::chrono;
16// Implied by "using namespace std::chrono", but be explicit about it, for grep-ability
17using namespace std::chrono_literals;
18
20
21Q_CORE_EXPORT bool qt_disable_lowpriority_timers=false;
22
23/*
24 * Internal functions for manipulating timer data structures. The
25 * timerBitVec array is used for keeping track of timer identifiers.
26 */
27
29
30steady_clock::time_point QTimerInfoList::updateCurrentTime()
31{
32 currentTime = steady_clock::now();
33 return currentTime;
34}
35
43{
44 if (isEmpty())
45 return false;
46 return updateCurrentTime() < constFirst()->timeout;
47}
48
49/*
50 insert timer info into list
51*/
53{
54 int index = size();
55 while (index--) {
56 const QTimerInfo * const t = at(index);
57 if (!(ti->timeout < t->timeout))
58 break;
59 }
60 insert(index+1, ti);
61}
62
63static constexpr milliseconds roundToMillisecond(nanoseconds val)
64{
65 // always round up
66 // worst case scenario is that the first trigger of a 1-ms timer is 0.999 ms late
67 return ceil<milliseconds>(val);
68}
69
70static_assert(roundToMillisecond(0ns) == 0ms);
71static_assert(roundToMillisecond(1ns) == 1ms);
72static_assert(roundToMillisecond(999'999ns) == 1ms);
73static_assert(roundToMillisecond(1'000'000ns) == 1ms);
74static_assert(roundToMillisecond(999'000'000ns) == 999ms);
75static_assert(roundToMillisecond(999'000'001ns) == 1000ms);
76static_assert(roundToMillisecond(999'999'999ns) == 1000ms);
77static_assert(roundToMillisecond(1s) == 1s);
78
79static constexpr seconds roundToSecs(milliseconds msecs)
80{
81 // The very coarse timer is based on full second precision, so we want to
82 // round the interval to the closest second, rounding 500ms up to 1s.
83 //
84 // std::chrono::round() wouldn't work with all multiples of 500 because for the
85 // middle point it would round to even:
86 // value round() wanted
87 // 500 0 1
88 // 1500 2 2
89 // 2500 2 3
90
91 auto secs = duration_cast<seconds>(msecs);
92 const milliseconds frac = msecs - secs;
93 if (frac >= 500ms)
94 ++secs;
95 return secs;
96}
97
98static void calculateCoarseTimerTimeout(QTimerInfo *t, steady_clock::time_point now)
99{
100 // The coarse timer works like this:
101 // - interval under 40 ms: round to even
102 // - between 40 and 99 ms: round to multiple of 4
103 // - otherwise: try to wake up at a multiple of 25 ms, with a maximum error of 5%
104 //
105 // We try to wake up at the following second-fraction, in order of preference:
106 // 0 ms
107 // 500 ms
108 // 250 ms or 750 ms
109 // 200, 400, 600, 800 ms
110 // other multiples of 100
111 // other multiples of 50
112 // other multiples of 25
113 //
114 // The objective is to make most timers wake up at the same time, thereby reducing CPU wakeups.
115
116 Q_ASSERT(t->interval >= 20ms);
117
118 const auto timeoutInSecs = time_point_cast<seconds>(t->timeout);
119
120 auto recalculate = [&](const milliseconds frac) {
121 t->timeout = timeoutInSecs + frac;
122 if (t->timeout < now)
123 t->timeout += t->interval;
124 };
125
126 // Calculate how much we can round and still keep within 5% error
127 const milliseconds absMaxRounding = t->interval / 20;
128
129 auto fracMsec = duration_cast<milliseconds>(t->timeout - timeoutInSecs);
130
131 if (t->interval < 100ms && t->interval != 25ms && t->interval != 50ms && t->interval != 75ms) {
132 auto fracCount = fracMsec.count();
133 // special mode for timers of less than 100 ms
134 if (t->interval < 50ms) {
135 // round to even
136 // round towards multiples of 50 ms
137 bool roundUp = (fracCount % 50) >= 25;
138 fracCount >>= 1;
139 fracCount |= roundUp;
140 fracCount <<= 1;
141 } else {
142 // round to multiple of 4
143 // round towards multiples of 100 ms
144 bool roundUp = (fracCount % 100) >= 50;
145 fracCount >>= 2;
146 fracCount |= roundUp;
147 fracCount <<= 2;
148 }
149 fracMsec = milliseconds{fracCount};
150 recalculate(fracMsec);
151 return;
152 }
153
154 milliseconds min = std::max(0ms, fracMsec - absMaxRounding);
155 milliseconds max = std::min(1000ms, fracMsec + absMaxRounding);
156
157 // find the boundary that we want, according to the rules above
158 // extra rules:
159 // 1) whatever the interval, we'll take any round-to-the-second timeout
160 if (min == 0ms) {
161 fracMsec = 0ms;
162 recalculate(fracMsec);
163 return;
164 } else if (max == 1000ms) {
165 fracMsec = 1000ms;
166 recalculate(fracMsec);
167 return;
168 }
169
170 milliseconds wantedBoundaryMultiple{25};
171
172 // 2) if the interval is a multiple of 500 ms and > 5000 ms, we'll always round
173 // towards a round-to-the-second
174 // 3) if the interval is a multiple of 500 ms, we'll round towards the nearest
175 // multiple of 500 ms
176 if ((t->interval % 500) == 0ms) {
177 if (t->interval >= 5s) {
178 fracMsec = fracMsec >= 500ms ? max : min;
179 recalculate(fracMsec);
180 return;
181 } else {
182 wantedBoundaryMultiple = 500ms;
183 }
184 } else if ((t->interval % 50) == 0ms) {
185 // 4) same for multiples of 250, 200, 100, 50
186 milliseconds mult50 = t->interval / 50;
187 if ((mult50 % 4) == 0ms) {
188 // multiple of 200
189 wantedBoundaryMultiple = 200ms;
190 } else if ((mult50 % 2) == 0ms) {
191 // multiple of 100
192 wantedBoundaryMultiple = 100ms;
193 } else if ((mult50 % 5) == 0ms) {
194 // multiple of 250
195 wantedBoundaryMultiple = 250ms;
196 } else {
197 // multiple of 50
198 wantedBoundaryMultiple = 50ms;
199 }
200 }
201
202 milliseconds base = (fracMsec / wantedBoundaryMultiple) * wantedBoundaryMultiple;
203 milliseconds middlepoint = base + wantedBoundaryMultiple / 2;
204 if (fracMsec < middlepoint)
205 fracMsec = qMax(base, min);
206 else
207 fracMsec = qMin(base + wantedBoundaryMultiple, max);
208
209 recalculate(fracMsec);
210}
211
212static void calculateNextTimeout(QTimerInfo *t, steady_clock::time_point now)
213{
214 switch (t->timerType) {
215 case Qt::PreciseTimer:
216 case Qt::CoarseTimer:
217 t->timeout += t->interval;
218 if (t->timeout < now) {
219 t->timeout = now;
220 t->timeout += t->interval;
221 }
222 if (t->timerType == Qt::CoarseTimer)
224 return;
225
227 // t->interval already rounded to full seconds in registerTimer()
228 t->timeout += t->interval;
229 if (t->timeout <= now)
230 t->timeout = time_point_cast<seconds>(now + t->interval);
231 break;
232 }
233}
234
236{
237 steady_clock::time_point now = updateCurrentTime();
238
239 auto isWaiting = [](QTimerInfo *tinfo) { return !tinfo->activateRef; };
240 // Find first waiting timer not already active
241 auto it = std::find_if(cbegin(), cend(), isWaiting);
242 if (it == cend())
243 return false;
244
245 QTimerInfo *t = *it;
246 nanoseconds timeToWait = t->timeout - now;
247 if (timeToWait > 0ns)
248 tm = durationToTimespec(roundToMillisecond(timeToWait));
249 else
250 tm = {0, 0};
251
252 return true;
253}
254
255/*
256 Returns the timer's remaining time in milliseconds with the given timerId.
257 If the timer id is not found in the list, the returned value will be -1.
258 If the timer is overdue, the returned value will be 0.
259*/
261{
262 return remainingDuration(timerId).count();
263}
264
265milliseconds QTimerInfoList::remainingDuration(int timerId)
266{
267 const steady_clock::time_point now = updateCurrentTime();
268
269 auto it = findTimerById(timerId);
270 if (it == cend()) {
271#ifndef QT_NO_DEBUG
272 qWarning("QTimerInfoList::timerRemainingTime: timer id %i not found", timerId);
273#endif
274 return -1ms;
275 }
276
277 const QTimerInfo *t = *it;
278 if (now < t->timeout) // time to wait
279 return roundToMillisecond(t->timeout - now);
280 return 0ms;
281}
282
283void QTimerInfoList::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
284{
285 registerTimer(timerId, milliseconds{interval}, timerType, object);
286}
287
288void QTimerInfoList::registerTimer(int timerId, milliseconds interval,
289 Qt::TimerType timerType, QObject *object)
290{
291 QTimerInfo *t = new QTimerInfo;
292 t->id = timerId;
293 t->interval = interval;
294 t->timerType = timerType;
295 t->obj = object;
296 t->activateRef = nullptr;
297
298 steady_clock::time_point expected = updateCurrentTime() + interval;
299
300 switch (timerType) {
301 case Qt::PreciseTimer:
302 // high precision timer is based on millisecond precision
303 // so no adjustment is necessary
304 t->timeout = expected;
305 break;
306
307 case Qt::CoarseTimer:
308 // this timer has up to 5% coarseness
309 // so our boundaries are 20 ms and 20 s
310 // below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision
311 // above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer
312 if (interval >= 20s) {
313 t->timerType = Qt::VeryCoarseTimer;
314 } else {
315 t->timeout = expected;
316 if (interval <= 20ms) {
317 t->timerType = Qt::PreciseTimer;
318 // no adjustment is necessary
319 } else if (interval <= 20s) {
321 }
322 break;
323 }
326 t->interval = roundToSecs(t->interval);
327 const auto currentTimeInSecs = floor<seconds>(currentTime);
328 t->timeout = currentTimeInSecs + t->interval;
329 // If we're past the half-second mark, increase the timeout again
330 if (currentTime - currentTimeInSecs > 500ms)
331 t->timeout += 1s;
332 }
333
334 timerInsert(t);
335}
336
338{
339 auto it = findTimerById(timerId);
340 if (it == cend())
341 return false; // id not found
342
343 // set timer inactive
344 QTimerInfo *t = *it;
345 if (t == firstTimerInfo)
346 firstTimerInfo = nullptr;
347 if (t->activateRef)
348 *(t->activateRef) = nullptr;
349 delete t;
350 erase(it);
351 return true;
352}
353
355{
356 if (isEmpty())
357 return false;
358 for (int i = 0; i < size(); ++i) {
359 QTimerInfo *t = at(i);
360 if (t->obj == object) {
361 // object found
362 removeAt(i);
363 if (t == firstTimerInfo)
364 firstTimerInfo = nullptr;
365 if (t->activateRef)
366 *(t->activateRef) = nullptr;
367 delete t;
368 // move back one so that we don't skip the new current item
369 --i;
370 }
371 }
372 return true;
373}
374
376{
378 for (const QTimerInfo *const t : std::as_const(*this)) {
379 if (t->obj == object)
380 list.emplaceBack(t->id, t->interval.count(), t->timerType);
381 }
382 return list;
383}
384
385/*
386 Activate pending timers, returning how many where activated.
387*/
389{
391 return 0; // nothing to do
392
393 firstTimerInfo = nullptr;
394
395 const steady_clock::time_point now = updateCurrentTime();
396 // qDebug() << "Thread" << QThread::currentThreadId() << "woken up at" << now;
397 // Find out how many timer have expired
398 auto stillActive = [&now](const QTimerInfo *t) { return now < t->timeout; };
399 // Find first one still active (list is sorted by timeout)
400 auto it = std::find_if(cbegin(), cend(), stillActive);
401 auto maxCount = it - cbegin();
402
403 int n_act = 0;
404 //fire the timers.
405 while (maxCount--) {
406 if (isEmpty())
407 break;
408
409 QTimerInfo *currentTimerInfo = constFirst();
410 if (now < currentTimerInfo->timeout)
411 break; // no timer has expired
412
413 if (!firstTimerInfo) {
414 firstTimerInfo = currentTimerInfo;
415 } else if (firstTimerInfo == currentTimerInfo) {
416 // avoid sending the same timer multiple times
417 break;
418 } else if (currentTimerInfo->interval < firstTimerInfo->interval
419 || currentTimerInfo->interval == firstTimerInfo->interval) {
420 firstTimerInfo = currentTimerInfo;
421 }
422
423 // remove from list
424 removeFirst();
425
426 // determine next timeout time
427 calculateNextTimeout(currentTimerInfo, now);
428
429 // reinsert timer
430 timerInsert(currentTimerInfo);
431 if (currentTimerInfo->interval > 0ms)
432 n_act++;
433
434 // Send event, but don't allow it to recurse:
435 if (!currentTimerInfo->activateRef) {
436 currentTimerInfo->activateRef = &currentTimerInfo;
437
438 QTimerEvent e(currentTimerInfo->id);
439 QCoreApplication::sendEvent(currentTimerInfo->obj, &e);
440
441 // Storing currentTimerInfo's address in its activateRef allows the
442 // handling of that event to clear this local variable on deletion
443 // of the object it points to - if it didn't, clear activateRef:
444 if (currentTimerInfo)
445 currentTimerInfo->activateRef = nullptr;
446 }
447 }
448
449 firstTimerInfo = nullptr;
450 // qDebug() << "Thread" << QThread::currentThreadId() << "activated" << n_act << "timers";
451 return n_act;
452}
453
static bool sendEvent(QObject *receiver, QEvent *event)
Sends event event directly to receiver receiver, using the notify() function.
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
void removeFirst() noexcept
Definition qlist.h:800
bool isEmpty() const noexcept
Definition qlist.h:390
void removeAt(qsizetype i)
Definition qlist.h:573
iterator erase(const_iterator begin, const_iterator end)
Definition qlist.h:882
iterator insert(qsizetype i, parameter_type t)
Definition qlist.h:471
reference emplaceBack(Args &&... args)
Definition qlist.h:875
const QTimerInfo * & constFirst() const noexcept
Definition qlist.h:630
const_iterator cend() const noexcept
Definition qlist.h:614
const_iterator cbegin() const noexcept
Definition qlist.h:613
\inmodule QtCore
Definition qobject.h:90
\inmodule QtCore
Definition qcoreevent.h:359
std::chrono::milliseconds remainingDuration(int timerId)
std::chrono::steady_clock::time_point currentTime
bool timerWait(timespec &)
void timerInsert(QTimerInfo *)
qint64 timerRemainingTime(int timerId)
bool unregisterTimers(QObject *object)
void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object)
QList::const_iterator findTimerById(int timerId) const
QList< QAbstractEventDispatcher::TimerInfo > registeredTimers(QObject *object) const
bool unregisterTimer(int timerId)
QJSValue expected
Definition qjsengine.cpp:12
double e
QSet< QString >::iterator it
Combined button and popup list for selecting options.
TimerType
@ CoarseTimer
@ VeryCoarseTimer
@ PreciseTimer
#define Q_FALLTHROUGH()
timespec durationToTimespec(std::chrono::nanoseconds timeout) noexcept
static ULONG calculateNextTimeout(WinTimerInfo *t, quint64 currentTime)
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint index
[2]
GLuint object
[3]
GLbitfield GLuint64 timeout
[4]
GLuint GLfloat * val
GLsizei maxCount
Definition qopenglext.h:677
GLdouble GLdouble t
Definition qopenglext.h:243
GLdouble s
[6]
Definition qopenglext.h:235
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static constexpr seconds roundToSecs(milliseconds msecs)
QT_BEGIN_NAMESPACE Q_CORE_EXPORT bool qt_disable_lowpriority_timers
static constexpr milliseconds roundToMillisecond(nanoseconds val)
static void calculateNextTimeout(QTimerInfo *t, steady_clock::time_point now)
static void calculateCoarseTimerTimeout(QTimerInfo *t, steady_clock::time_point now)
long long qint64
Definition qtypes.h:55
QList< int > list
[14]
QAction * at
std::chrono::steady_clock::time_point timeout
QTimerInfo ** activateRef
std::chrono::milliseconds interval