Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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// Qt-Security score:significant reason:default
5
6#include <qelapsedtimer.h>
7#include <qcoreapplication.h>
8
9#include "private/qcore_unix_p.h"
10#include "private/qtimerinfo_unix_p.h"
11#include "private/qobject_p.h"
12#include "private/qabstracteventdispatcher_p.h"
13
14#include <sys/times.h>
15
16using namespace std::chrono;
17// Implied by "using namespace std::chrono", but be explicit about it, for grep-ability
18using namespace std::chrono_literals;
19
20QT_BEGIN_NAMESPACE
21
22Q_CORE_EXPORT bool qt_disable_lowpriority_timers=false;
23
24/*
25 * Internal functions for manipulating timer data structures. The
26 * timerBitVec array is used for keeping track of timer identifiers.
27 */
28
29QTimerInfoList::QTimerInfoList() = default;
30
31steady_clock::time_point QTimerInfoList::updateCurrentTime() const
32{
33 currentTime = steady_clock::now();
34 return currentTime;
35}
36
37/*! \internal
38 Updates the currentTime member to the current time, and returns \c true if
39 the first timer's timeout is in the future (after currentTime).
40
41 The list is sorted by timeout, thus it's enough to check the first timer only.
42*/
43bool QTimerInfoList::hasPendingTimers()
44{
45 if (timers.isEmpty())
46 return false;
47 return updateCurrentTime() < timers.at(0)->timeout;
48}
49
50static bool byTimeout(const QTimerInfo *a, const QTimerInfo *b)
51{ return a->timeout < b->timeout; };
52
53/*
54 insert timer info into list
55*/
56void QTimerInfoList::timerInsert(QTimerInfo *ti)
57{
58 timers.insert(std::upper_bound(timers.cbegin(), timers.cend(), ti, byTimeout),
59 ti);
60}
61
62static constexpr milliseconds roundToMillisecond(nanoseconds val)
63{
64 // always round up
65 // worst case scenario is that the first trigger of a 1-ms timer is 0.999 ms late
66 return ceil<milliseconds>(val);
67}
68
69static_assert(roundToMillisecond(0ns) == 0ms);
70static_assert(roundToMillisecond(1ns) == 1ms);
71static_assert(roundToMillisecond(999'999ns) == 1ms);
72static_assert(roundToMillisecond(1'000'000ns) == 1ms);
73static_assert(roundToMillisecond(999'000'000ns) == 999ms);
74static_assert(roundToMillisecond(999'000'001ns) == 1000ms);
75static_assert(roundToMillisecond(999'999'999ns) == 1000ms);
76static_assert(roundToMillisecond(1s) == 1s);
77
78static constexpr seconds roundToSecs(nanoseconds interval)
79{
80 // The very coarse timer is based on full second precision, so we want to
81 // round the interval to the closest second, rounding 500ms up to 1s.
82 //
83 // std::chrono::round() wouldn't work with all multiples of 500 because for the
84 // middle point it would round to even:
85 // value round() wanted
86 // 500 0 1
87 // 1500 2 2
88 // 2500 2 3
89
90 auto secs = duration_cast<seconds>(interval);
91 const nanoseconds frac = interval - secs;
92 if (frac >= 500ms)
93 ++secs;
94 return secs;
95}
96
97static void calculateCoarseTimerTimeout(QTimerInfo *t, steady_clock::time_point now)
98{
99 // The coarse timer works like this:
100 // - interval under 40 ms: round to even
101 // - between 40 and 99 ms: round to multiple of 4
102 // - otherwise: try to wake up at a multiple of 25 ms, with a maximum error of 5%
103 //
104 // We try to wake up at the following second-fraction, in order of preference:
105 // 0 ms
106 // 500 ms
107 // 250 ms or 750 ms
108 // 200, 400, 600, 800 ms
109 // other multiples of 100
110 // other multiples of 50
111 // other multiples of 25
112 //
113 // The objective is to make most timers wake up at the same time, thereby reducing CPU wakeups.
114
115 Q_ASSERT(t->interval >= 20ms);
116
117 const auto timeoutInSecs = time_point_cast<seconds>(t->timeout);
118
119 auto recalculate = [&](const milliseconds frac) {
120 t->timeout = timeoutInSecs + frac;
121 if (t->timeout < now)
122 t->timeout += t->interval;
123 };
124
125 // Calculate how much we can round and still keep within 5% error
126 milliseconds interval = roundToMillisecond(t->interval);
127 const milliseconds absMaxRounding = interval / 20;
128
129 auto fracMsec = duration_cast<milliseconds>(t->timeout - timeoutInSecs);
130
131 if (interval < 100ms && interval != 25ms && interval != 50ms && interval != 75ms) {
132 auto fracCount = fracMsec.count();
133 // special mode for timers of less than 100 ms
134 if (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 ((interval % 500) == 0ms) {
177 if (interval >= 5s) {
178 fracMsec = fracMsec >= 500ms ? max : min;
179 recalculate(fracMsec);
180 return;
181 } else {
182 wantedBoundaryMultiple = 500ms;
183 }
184 } else if ((interval % 50) == 0ms) {
185 // 4) same for multiples of 250, 200, 100, 50
186 milliseconds mult50 = 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)
223 calculateCoarseTimerTimeout(t, now);
224 return;
225
226 case Qt::VeryCoarseTimer:
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
235/*
236 Returns the time to wait for the first timer that has not been activated yet,
237 otherwise returns std::nullopt.
238 */
239std::optional<QTimerInfoList::Duration> QTimerInfoList::timerWait()
240{
241 steady_clock::time_point now = updateCurrentTime();
242
243 auto isWaiting = [](QTimerInfo *tinfo) { return !tinfo->activateRef; };
244 // Find first waiting timer not already active
245 auto it = std::find_if(timers.cbegin(), timers.cend(), isWaiting);
246 if (it == timers.cend())
247 return std::nullopt;
248
249 Duration timeToWait = (*it)->timeout - now;
250 if (timeToWait > 0ns)
251 return roundToMillisecond(timeToWait);
252 return 0ms;
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 \c{Duration::min()}.
258 If the timer is overdue, the returned value will be 0.
259*/
260QTimerInfoList::Duration QTimerInfoList::remainingDuration(Qt::TimerId timerId) const
261{
262 const steady_clock::time_point now = updateCurrentTime();
263
264 auto it = findTimerById(timerId);
265 if (it == timers.cend()) {
266#ifndef QT_NO_DEBUG
267 qWarning("QTimerInfoList::timerRemainingTime: timer id %i not found", int(timerId));
268#endif
269 return Duration::min();
270 }
271
272 const QTimerInfo *t = *it;
273 if (now < t->timeout) // time to wait
274 return t->timeout - now;
275 return 0ms;
276}
277
278void QTimerInfoList::registerTimer(Qt::TimerId timerId, QTimerInfoList::Duration interval,
279 Qt::TimerType timerType, QObject *object)
280{
281 // correct the timer type first
282 if (timerType == Qt::CoarseTimer) {
283 // this timer has up to 5% coarseness
284 // so our boundaries are 20 ms and 20 s
285 // below 20 ms, 5% inaccuracy is below 1 ms, so we convert to high precision
286 // above 20 s, 5% inaccuracy is above 1 s, so we convert to VeryCoarseTimer
287 if (interval >= 20s)
288 timerType = Qt::VeryCoarseTimer;
289 else if (interval <= 20ms)
290 timerType = Qt::PreciseTimer;
291 }
292
293 QTimerInfo *t = new QTimerInfo(timerId, interval, timerType, object);
294 QTimerInfo::TimePoint expected = updateCurrentTime() + interval;
295
296 switch (timerType) {
297 case Qt::PreciseTimer:
298 // high precision timer is based on millisecond precision
299 // so no adjustment is necessary
300 t->timeout = expected;
301 break;
302
303 case Qt::CoarseTimer:
304 t->timeout = expected;
305 t->interval = roundToMillisecond(interval);
306 calculateCoarseTimerTimeout(t, currentTime);
307 break;
308
309 case Qt::VeryCoarseTimer:
310 t->interval = roundToSecs(t->interval);
311 const auto currentTimeInSecs = floor<seconds>(currentTime);
312 t->timeout = currentTimeInSecs + t->interval;
313 // If we're past the half-second mark, increase the timeout again
314 if (currentTime - currentTimeInSecs > 500ms)
315 t->timeout += 1s;
316 }
317
318 timerInsert(t);
319}
320
321bool QTimerInfoList::unregisterTimer(Qt::TimerId timerId)
322{
323 auto it = findTimerById(timerId);
324 if (it == timers.cend())
325 return false; // id not found
326
327 // set timer inactive
328 QTimerInfo *t = *it;
329 if (t == firstTimerInfo)
330 firstTimerInfo = nullptr;
331 if (t->activateRef)
332 *(t->activateRef) = nullptr;
333 delete t;
334 timers.erase(it);
335 return true;
336}
337
338bool QTimerInfoList::unregisterTimers(QObject *object)
339{
340 if (timers.isEmpty())
341 return false;
342
343 auto associatedWith = [this](QObject *o) {
344 return [this, o](auto &t) {
345 if (t->obj == o) {
346 if (t == firstTimerInfo)
347 firstTimerInfo = nullptr;
348 if (t->activateRef)
349 *(t->activateRef) = nullptr;
350 delete t;
351 return true;
352 }
353 return false;
354 };
355 };
356
357 qsizetype count = timers.removeIf(associatedWith(object));
358 return count > 0;
359}
360
361auto QTimerInfoList::registeredTimers(QObject *object) const -> QList<TimerInfo>
362{
363 QList<TimerInfo> list;
364 for (const auto &t : timers) {
365 if (t->obj == object)
366 list.emplaceBack(TimerInfo{t->interval, t->id, t->timerType});
367 }
368 return list;
369}
370
371/*
372 Activate pending timers, returning how many where activated.
373*/
374int QTimerInfoList::activateTimers()
375{
376 if (qt_disable_lowpriority_timers || timers.isEmpty())
377 return 0; // nothing to do
378
379 firstTimerInfo = nullptr;
380
381 const steady_clock::time_point now = updateCurrentTime();
382 // qDebug() << "Thread" << QThread::currentThreadId() << "woken up at" << now;
383 // Find out how many timer have expired
384 auto stillActive = [&now](const QTimerInfo *t) { return now < t->timeout; };
385 // Find first one still active (list is sorted by timeout)
386 auto it = std::find_if(timers.cbegin(), timers.cend(), stillActive);
387 auto maxCount = it - timers.cbegin();
388
389 int n_act = 0;
390 //fire the timers.
391 while (maxCount--) {
392 if (timers.isEmpty())
393 break;
394
395 QTimerInfo *currentTimerInfo = timers.constFirst();
396 if (now < currentTimerInfo->timeout)
397 break; // no timer has expired
398
399 if (!firstTimerInfo) {
400 firstTimerInfo = currentTimerInfo;
401 } else if (firstTimerInfo == currentTimerInfo) {
402 // avoid sending the same timer multiple times
403 break;
404 } else if (currentTimerInfo->interval < firstTimerInfo->interval
405 || currentTimerInfo->interval == firstTimerInfo->interval) {
406 firstTimerInfo = currentTimerInfo;
407 }
408
409 // determine next timeout time
410 calculateNextTimeout(currentTimerInfo, now);
411 if (timers.size() > 1) {
412 // Find where "currentTimerInfo" should be in the list so as
413 // to keep the list ordered by timeout
414 auto afterCurrentIt = timers.begin() + 1;
415 auto iter = std::upper_bound(afterCurrentIt, timers.end(), currentTimerInfo, byTimeout);
416 currentTimerInfo = *std::rotate(timers.begin(), afterCurrentIt, iter);
417 }
418
419 if (currentTimerInfo->interval > 0ms)
420 n_act++;
421
422 // Send event, but don't allow it to recurse:
423 if (!currentTimerInfo->activateRef) {
424 currentTimerInfo->activateRef = &currentTimerInfo;
425
426 QTimerEvent e(currentTimerInfo->id);
427 QCoreApplication::sendEvent(currentTimerInfo->obj, &e);
428
429 // Storing currentTimerInfo's address in its activateRef allows the
430 // handling of that event to clear this local variable on deletion
431 // of the object it points to - if it didn't, clear activateRef:
432 if (currentTimerInfo)
433 currentTimerInfo->activateRef = nullptr;
434 }
435 }
436
437 firstTimerInfo = nullptr;
438 // qDebug() << "Thread" << QThread::currentThreadId() << "activated" << n_act << "timers";
439 return n_act;
440}
441
442QT_END_NAMESPACE
QT_BEGIN_NAMESPACE Q_CORE_EXPORT bool qt_disable_lowpriority_timers
static constexpr milliseconds roundToMillisecond(nanoseconds val)
static bool byTimeout(const QTimerInfo *a, const QTimerInfo *b)
static void calculateNextTimeout(QTimerInfo *t, steady_clock::time_point now)
static void calculateCoarseTimerTimeout(QTimerInfo *t, steady_clock::time_point now)
static constexpr seconds roundToSecs(nanoseconds interval)