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);
}
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()
{
if (m_timerInfoList.size() > 0) {
// We have Qt timers registered, so create or reschedule CF timer to match
timespec tv = { -1, -1 };
CFAbsoluteTime timeToFire = m_timerInfoList.timerWait(tv) ?
using namespace std::chrono_literals;
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
CFAbsoluteTimeGetCurrent() + timespecToSeconds(tv)
secs = DoubleSeconds{*opt};
timeToFire = CFAbsoluteTimeGetCurrent() + secs.count();
} else {
// We have timers, but they are all currently blocked by callbacks
: kCFTimeIntervalDistantFuture;
timeToFire = kCFTimeIntervalDistantFuture;
}
if (!m_runLoopTimer) {
m_runLoopTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault,
@ -587,9 +589,9 @@ void QEventDispatcherCoreFoundation::updateTimers()
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 {
// 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_unix_p.h"
#include <private/qnumeric_p.h>
#include <private/qthread_p.h>
#include "qcoreapplication.h"
@ -14,6 +15,8 @@
#include <glib.h>
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
struct GPollFDWithQSocketNotifier
@ -95,11 +98,13 @@ struct GTimerSource
static gboolean timerSourcePrepareHelper(GTimerSource *src, gint *timeout)
{
timespec tv = { 0l, 0l };
if (!(src->processEventsFlags & QEventLoop::X11ExcludeTimers) && src->timerList.timerWait(tv))
*timeout = (tv.tv_sec * 1000) + ((tv.tv_nsec + 999999) / 1000 / 1000);
else
if (src->processEventsFlags & QEventLoop::X11ExcludeTimers) {
*timeout = -1;
return true;
}
auto msecs = src->timerList.timerWait().value_or(-1ms);
*timeout = qt_saturate<gint>(msecs.count());
return (*timeout == 0);
}

View File

@ -27,6 +27,8 @@
# include <pipeDrv.h>
#endif
using namespace std::chrono_literals;
QT_BEGIN_NAMESPACE
static const char *socketType(QSocketNotifier::Type type)
@ -427,8 +429,15 @@ bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
timespec *tm = nullptr;
timespec wait_tm = { 0, 0 };
if (!canWait || (include_timers && d->timerList.timerWait(wait_tm)))
if (!canWait) {
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.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
// wakeup time on the main thread.
using namespace std::chrono;
auto timespecToMsec = [](timespec ts) -> milliseconds {
return duration_cast<milliseconds>(seconds{ts.tv_sec} + nanoseconds{ts.tv_nsec});
};
timespec toWait;
bool hasTimer = m_timerInfo->timerWait(toWait);
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]() {
const std::optional<std::chrono::milliseconds> wait = m_timerInfo->timerWait();
const auto toWaitDuration = wait.value_or(0ms);
const auto newTargetTimePoint = m_timerInfo->currentTime + toWaitDuration;
auto epochNsecs = newTargetTimePoint.time_since_epoch();
auto newTargetTime = std::chrono::duration_cast<std::chrono::milliseconds>(epochNsecs);
auto maintainNativeTimer = [this, wait, toWaitDuration, newTargetTime]() {
Q_ASSERT(emscripten_is_main_runtime_thread());
if (!hasTimer) {
if (!wait) {
if (m_timerId > 0) {
emscripten_clear_timeout(m_timerId);
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();
@ -238,15 +242,12 @@ bool QTimerInfoList::timerWait(timespec &tm)
// Find first waiting timer not already active
auto it = std::find_if(timers.cbegin(), timers.cend(), isWaiting);
if (it == timers.cend())
return false;
return std::nullopt;
nanoseconds timeToWait = (*it)->timeout - now;
if (timeToWait > 0ns)
tm = durationToTimespec(roundToMillisecond(timeToWait));
else
tm = {0, 0};
return true;
return roundToMillisecond(timeToWait);
return 0ms;
}
/*

View File

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

View File

@ -115,6 +115,7 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
return;
}
using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>;
if (!runLoopTimerRef) {
// start the CFRunLoopTimer
CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
@ -122,10 +123,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
// Q: when should the CFRunLoopTimer fire for the first time?
struct timespec tv;
if (timerInfoList.timerWait(tv)) {
if (auto opt = timerInfoList.timerWait()) {
// 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 {
// this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
interval = oneyear;
@ -145,10 +146,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval interval;
// Q: when should the timer first next?
struct timespec tv;
if (timerInfoList.timerWait(tv)) {
if (auto opt = timerInfoList.timerWait()) {
// 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 {
// 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)