QTimerInfoList: change timerWait() to return std::optional<ms>

QEventDispatcherGlib: it used the out-parameter timespec to set a
timeout in milliseconds, making timerWait() return milliseconds is more
straightforward. Also timerWait() returns the time rounded to
milliseconds, so the code in QEventDispatcherGlib that rounded up the
timespec::tv_nsec part was no-op, tv_nsec was always 0 IIUC.

In QEventDispatcherGlib, guard against overflow with qt_saturate.

Change-Id: Ie6f78374d00cbe8a6adf7b50ed67c8c86ab4d29d
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Ahmad Samir 2023-04-24 18:06:41 +02:00
parent 99c6190bdf
commit 3ca6e7e6c7
7 changed files with 56 additions and 42 deletions

View File

@ -555,23 +555,25 @@ int QEventDispatcherCoreFoundation::remainingTime(int timerId)
return m_timerInfoList.timerRemainingTime(timerId); return m_timerInfoList.timerRemainingTime(timerId);
} }
static double timespecToSeconds(const timespec &spec)
{
static double nanosecondsPerSecond = 1.0 * 1000 * 1000 * 1000;
return spec.tv_sec + (spec.tv_nsec / nanosecondsPerSecond);
}
void QEventDispatcherCoreFoundation::updateTimers() void QEventDispatcherCoreFoundation::updateTimers()
{ {
if (m_timerInfoList.size() > 0) { if (m_timerInfoList.size() > 0) {
// We have Qt timers registered, so create or reschedule CF timer to match // We have Qt timers registered, so create or reschedule CF timer to match
timespec tv = { -1, -1 }; using namespace std::chrono_literals;
CFAbsoluteTime timeToFire = m_timerInfoList.timerWait(tv) ? using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>;
CFAbsoluteTime timeToFire;
auto opt = m_timerInfoList.timerWait();
DoubleSeconds secs{};
if (opt) {
// We have a timer ready to fire right now, or some time in the future // We have a timer ready to fire right now, or some time in the future
CFAbsoluteTimeGetCurrent() + timespecToSeconds(tv) secs = DoubleSeconds{*opt};
timeToFire = CFAbsoluteTimeGetCurrent() + secs.count();
} else {
// We have timers, but they are all currently blocked by callbacks // We have timers, but they are all currently blocked by callbacks
: kCFTimeIntervalDistantFuture; timeToFire = kCFTimeIntervalDistantFuture;
}
if (!m_runLoopTimer) { if (!m_runLoopTimer) {
m_runLoopTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, m_runLoopTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
@ -587,9 +589,9 @@ void QEventDispatcherCoreFoundation::updateTimers()
qCDebug(lcEventDispatcherTimers) << "Re-scheduled CFRunLoopTimer" << m_runLoopTimer; qCDebug(lcEventDispatcherTimers) << "Re-scheduled CFRunLoopTimer" << m_runLoopTimer;
} }
m_overdueTimerScheduled = !timespecToSeconds(tv); m_overdueTimerScheduled = secs > 0s;
qCDebug(lcEventDispatcherTimers) << "Next timeout in" << tv << "seconds"; qCDebug(lcEventDispatcherTimers) << "Next timeout in" << secs;
} else { } else {
// No Qt timers are registered, so make sure we're not running any CF timers // No Qt timers are registered, so make sure we're not running any CF timers

View File

@ -4,6 +4,7 @@
#include "qeventdispatcher_glib_p.h" #include "qeventdispatcher_glib_p.h"
#include "qeventdispatcher_unix_p.h" #include "qeventdispatcher_unix_p.h"
#include <private/qnumeric_p.h>
#include <private/qthread_p.h> #include <private/qthread_p.h>
#include "qcoreapplication.h" #include "qcoreapplication.h"
@ -14,6 +15,8 @@
#include <glib.h> #include <glib.h>
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
struct GPollFDWithQSocketNotifier struct GPollFDWithQSocketNotifier
@ -95,11 +98,13 @@ struct GTimerSource
static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout) static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout)
{ {
timespec tv = { 0l, 0l }; if (src->processEventsFlags & QEventLoop::X11ExcludeTimers) {
if (!(src->processEventsFlags & QEventLoop::X11ExcludeTimers) && src->timerList.timerWait(tv))
*timeout = (tv.tv_sec * 1000) + ((tv.tv_nsec + 999999) / 1000 / 1000);
else
*timeout = -1; *timeout = -1;
return true;
}
auto msecs = src->timerList.timerWait().value_or(-1ms);
*timeout = qt_saturate<gint>(msecs.count());
return (*timeout == 0); return (*timeout == 0);
} }

View File

@ -27,6 +27,8 @@
# include <pipeDrv.h> # include <pipeDrv.h>
#endif #endif
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
static const char *socketType(QSocketNotifier::Type type) static const char *socketType(QSocketNotifier::Type type)
@ -427,8 +429,15 @@ bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
timespec *tm = nullptr; timespec *tm = nullptr;
timespec wait_tm = { 0, 0 }; timespec wait_tm = { 0, 0 };
if (!canWait || (include_timers && d->timerList.timerWait(wait_tm))) if (!canWait) {
tm = &wait_tm; tm = &wait_tm;
} else if (include_timers) {
std::optional<std::chrono::milliseconds> msecs = d->timerList.timerWait();
if (msecs) {
wait_tm = durationToTimespec(*msecs);
tm = &wait_tm;
}
}
d->pollfds.clear(); d->pollfds.clear();
d->pollfds.reserve(1 + (include_notifiers ? d->socketNotifiers.size() : 0)); d->pollfds.reserve(1 + (include_notifiers ? d->socketNotifiers.size() : 0));

View File

@ -583,19 +583,15 @@ void QEventDispatcherWasm::updateNativeTimer()
// access to m_timerInfo), and then call native API to set the new // access to m_timerInfo), and then call native API to set the new
// wakeup time on the main thread. // wakeup time on the main thread.
using namespace std::chrono; const std::optional<std::chrono::milliseconds> wait = m_timerInfo->timerWait();
auto timespecToMsec = [](timespec ts) -> milliseconds { const auto toWaitDuration = wait.value_or(0ms);
return duration_cast<milliseconds>(seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec}); const auto newTargetTimePoint = m_timerInfo->currentTime + toWaitDuration;
}; auto epochNsecs = newTargetTimePoint.time_since_epoch();
timespec toWait; auto newTargetTime = std::chrono::duration_cast<std::chrono::milliseconds>(epochNsecs);
bool hasTimer = m_timerInfo->timerWait(toWait); auto maintainNativeTimer = [this, wait, toWaitDuration, newTargetTime]() {
const milliseconds toWaitDuration = timespecToMsec(toWait);
const time_point newTargetTimePoint = m_timerInfo->currentTime + toWaitDuration;
auto newTargetTime = duration_cast<milliseconds>(newTargetTimePoint.time_since_epoch());
auto maintainNativeTimer = [this, hasTimer, toWaitDuration, newTargetTime]() {
Q_ASSERT(emscripten_is_main_runtime_thread()); Q_ASSERT(emscripten_is_main_runtime_thread());
if (!hasTimer) { if (!wait) {
if (m_timerId > 0) { if (m_timerId > 0) {
emscripten_clear_timeout(m_timerId); emscripten_clear_timeout(m_timerId);
m_timerId = 0; m_timerId = 0;

View File

@ -230,7 +230,11 @@ static void calculateNextTimeout(QTimerInfo *t, steady_clock::time_point now)
} }
} }
bool QTimerInfoList::timerWait(timespec &tm) /*
Returns the time to wait for the first timer that has not been activated yet,
otherwise returns std::nullopt.
*/
std::optional<std::chrono::milliseconds> QTimerInfoList::timerWait()
{ {
steady_clock::time_point now = updateCurrentTime(); steady_clock::time_point now = updateCurrentTime();
@ -238,15 +242,12 @@ bool QTimerInfoList::timerWait(timespec &tm)
// Find first waiting timer not already active // Find first waiting timer not already active
auto it = std::find_if(timers.cbegin(), timers.cend(), isWaiting); auto it = std::find_if(timers.cbegin(), timers.cend(), isWaiting);
if (it == timers.cend()) if (it == timers.cend())
return false; return std::nullopt;
nanoseconds timeToWait = (*it)->timeout - now; nanoseconds timeToWait = (*it)->timeout - now;
if (timeToWait > 0ns) if (timeToWait > 0ns)
tm = durationToTimespec(roundToMillisecond(timeToWait)); return roundToMillisecond(timeToWait);
else return 0ms;
tm = {0, 0};
return true;
} }
/* /*

View File

@ -46,7 +46,7 @@ public:
std::chrono::steady_clock::time_point currentTime; std::chrono::steady_clock::time_point currentTime;
bool timerWait(timespec &); std::optional<std::chrono::milliseconds> timerWait();
void timerInsert(QTimerInfo *); void timerInsert(QTimerInfo *);
qint64 timerRemainingTime(int timerId); qint64 timerRemainingTime(int timerId);

View File

@ -115,6 +115,7 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
return; return;
} }
using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>;
if (!runLoopTimerRef) { if (!runLoopTimerRef) {
// start the CFRunLoopTimer // start the CFRunLoopTimer
CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent(); CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
@ -122,10 +123,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.); CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
// Q: when should the CFRunLoopTimer fire for the first time? // Q: when should the CFRunLoopTimer fire for the first time?
struct timespec tv; if (auto opt = timerInfoList.timerWait()) {
if (timerInfoList.timerWait(tv)) {
// A: when we have timers to fire, of course // A: when we have timers to fire, of course
interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001); DoubleSeconds secs{*opt};
interval = qMax(secs.count(), 0.0000001);
} else { } else {
// this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future // this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
interval = oneyear; interval = oneyear;
@ -145,10 +146,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval interval; CFTimeInterval interval;
// Q: when should the timer first next? // Q: when should the timer first next?
struct timespec tv; if (auto opt = timerInfoList.timerWait()) {
if (timerInfoList.timerWait(tv)) {
// A: when we have timers to fire, of course // A: when we have timers to fire, of course
interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001); DoubleSeconds secs{*opt};
interval = qMax(secs.count(), 0.0000001);
} else { } else {
// no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some // no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
// point in the distant future (the timer interval is one year) // point in the distant future (the timer interval is one year)