diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /widget/SystemTimeConverter.h | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | widget/SystemTimeConverter.h | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/widget/SystemTimeConverter.h b/widget/SystemTimeConverter.h new file mode 100644 index 0000000000..4a074d573f --- /dev/null +++ b/widget/SystemTimeConverter.h @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SystemTimeConverter_h +#define SystemTimeConverter_h + +#include <limits> +#include <type_traits> +#include "mozilla/TimeStamp.h" + +namespace mozilla { + +// Utility class that converts time values represented as an unsigned integral +// number of milliseconds from one time source (e.g. a native event time) to +// corresponding mozilla::TimeStamp objects. +// +// This class handles wrapping of integer values and skew between the time +// source and mozilla::TimeStamp values. +// +// It does this by using an historical reference time recorded in both time +// scales (i.e. both as a numerical time value and as a TimeStamp). +// +// For performance reasons, this class is careful to minimize calls to the +// native "current time" function (e.g. gdk_x11_server_get_time) since this can +// be slow. +template <typename Time, typename TimeStampNowProvider = TimeStamp> +class SystemTimeConverter { + public: + SystemTimeConverter() + : mReferenceTime(Time(0)), + mReferenceTimeStamp() // Initializes to the null timestamp + , + mLastBackwardsSkewCheck(Time(0)), + kTimeRange(std::numeric_limits<Time>::max()), + kTimeHalfRange(kTimeRange / 2), + kBackwardsSkewCheckInterval(Time(2000)) { + static_assert(!std::is_signed_v<Time>, "Expected Time to be unsigned"); + } + + template <typename CurrentTimeGetter> + mozilla::TimeStamp GetTimeStampFromSystemTime( + Time aTime, CurrentTimeGetter& aCurrentTimeGetter) { + TimeStamp roughlyNow = TimeStampNowProvider::Now(); + + // If the reference time is not set, use the current time value to fill + // it in. + if (mReferenceTimeStamp.IsNull()) { + // This sometimes happens when ::GetMessageTime returns 0 for the first + // message on Windows. + if (!aTime) return roughlyNow; + UpdateReferenceTime(aTime, aCurrentTimeGetter); + } + + // Check for skew between the source of Time values and TimeStamp values. + // We do this by comparing two durations (both in ms): + // + // i. The duration from the reference time to the passed-in time. + // (timeDelta in the diagram below) + // ii. The duration from the reference timestamp to the current time + // based on TimeStamp::Now. + // (timeStampDelta in the diagram below) + // + // Normally, we'd expect (ii) to be slightly larger than (i) to account + // for the time taken between generating the event and processing it. + // + // If (ii) - (i) is negative then the source of Time values is getting + // "ahead" of TimeStamp. We call this "forwards" skew below. + // + // For the reverse case, if (ii) - (i) is positive (and greater than some + // tolerance factor), then we may have "backwards" skew. This is often + // the case when we have a backlog of events and by the time we process + // them, the time given by the system is comparatively "old". + // + // The IsNewerThanTimestamp function computes the equivalent of |aTime| in + // the TimeStamp scale and returns that in |timeAsTimeStamp|. + // + // Graphically: + // + // mReferenceTime aTime + // Time scale: ........+.......................*........ + // |--------timeDelta------| + // + // mReferenceTimeStamp roughlyNow + // TimeStamp scale: ........+...........................*.... + // |------timeStampDelta-------| + // + // |---| + // roughlyNow-timeAsTimeStamp + // + TimeStamp timeAsTimeStamp; + bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &timeAsTimeStamp); + + // Tolerance when detecting clock skew. + static const TimeDuration kTolerance = TimeDuration::FromMilliseconds(30.0); + + // Check for forwards skew + if (newer) { + // Make aTime correspond to roughlyNow + UpdateReferenceTime(aTime, roughlyNow); + + // We didn't have backwards skew so don't bother checking for + // backwards skew again for a little while. + mLastBackwardsSkewCheck = aTime; + + return roughlyNow; + } + + if (roughlyNow - timeAsTimeStamp <= kTolerance) { + // If the time between event times and TimeStamp values is within + // the tolerance then assume we don't have clock skew so we can + // avoid checking for backwards skew for a while. + mLastBackwardsSkewCheck = aTime; + } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) { + aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow); + mLastBackwardsSkewCheck = aTime; + } + + // Finally, calculate the timestamp + return timeAsTimeStamp; + } + + void CompensateForBackwardsSkew(Time aReferenceTime, + const TimeStamp& aLowerBound) { + // Check if we actually have backwards skew. Backwards skew looks like + // the following: + // + // mReferenceTime + // Time: ..+...a...b...c.......................... + // + // mReferenceTimeStamp + // TimeStamp: ..+.....a.....b.....c.................... + // + // Converted + // time: ......a'..b'..c'......................... + // + // What we need to do is bring mReferenceTime "forwards". + // + // Suppose when we get (c), we detect possible backwards skew and trigger + // an async request for the current time (which is passed in here as + // aReferenceTime). + // + // We end up with something like the following: + // + // mReferenceTime aReferenceTime + // Time: ..+...a...b...c...v...................... + // + // mReferenceTimeStamp + // TimeStamp: ..+.....a.....b.....c..........x......... + // ^ ^ + // aLowerBound TimeStamp::Now() + // + // If the duration (aLowerBound - mReferenceTimeStamp) is greater than + // (aReferenceTime - mReferenceTime) then we know we have backwards skew. + // + // If that's not the case, then we probably just got caught behind + // temporarily. + if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, nullptr)) { + return; + } + + // We have backwards skew; the equivalent TimeStamp for aReferenceTime lies + // somewhere between aLowerBound (which was the TimeStamp when we triggered + // the async request for the current time) and TimeStamp::Now(). + // + // If aReferenceTime was waiting in the event queue for a long time, the + // equivalent TimeStamp might be much closer to aLowerBound than + // TimeStamp::Now() so for now we just set it to aLowerBound. That's + // guaranteed to be at least somewhat of an improvement. + UpdateReferenceTime(aReferenceTime, aLowerBound); + } + + private: + template <typename CurrentTimeGetter> + void UpdateReferenceTime(Time aReferenceTime, + const CurrentTimeGetter& aCurrentTimeGetter) { + Time currentTime = aCurrentTimeGetter.GetCurrentTime(); + TimeStamp currentTimeStamp = TimeStampNowProvider::Now(); + Time timeSinceReference = currentTime - aReferenceTime; + TimeStamp referenceTimeStamp = + currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference); + UpdateReferenceTime(aReferenceTime, referenceTimeStamp); + } + + void UpdateReferenceTime(Time aReferenceTime, + const TimeStamp& aReferenceTimeStamp) { + mReferenceTime = aReferenceTime; + mReferenceTimeStamp = aReferenceTimeStamp; + } + + bool IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp, + TimeStamp* aTimeAsTimeStamp) { + Time timeDelta = aTime - mReferenceTime; + + // Cast the result to signed 64-bit integer first since that should be + // enough to hold the range of values returned by ToMilliseconds() and + // the result of converting from double to an integer-type when the value + // is outside the integer range is undefined. + // Then we do an implicit cast to Time (typically an unsigned 32-bit + // integer) which wraps times outside that range. + TimeDuration timeStampDelta = (aTimeStamp - mReferenceTimeStamp); + int64_t wholeMillis = static_cast<int64_t>(timeStampDelta.ToMilliseconds()); + Time wrappedTimeStampDelta = wholeMillis; // truncate to unsigned + + Time timeToTimeStamp = wrappedTimeStampDelta - timeDelta; + bool isNewer = false; + if (timeToTimeStamp == 0) { + // wholeMillis needs no adjustment + } else if (timeToTimeStamp < kTimeHalfRange) { + wholeMillis -= timeToTimeStamp; + } else { + isNewer = true; + wholeMillis += (-timeToTimeStamp); + } + if (aTimeAsTimeStamp) { + *aTimeAsTimeStamp = + mReferenceTimeStamp + TimeDuration::FromMilliseconds(wholeMillis); + } + + return isNewer; + } + + Time mReferenceTime; + TimeStamp mReferenceTimeStamp; + Time mLastBackwardsSkewCheck; + + const Time kTimeRange; + const Time kTimeHalfRange; + const Time kBackwardsSkewCheckInterval; +}; + +} // namespace mozilla + +#endif /* SystemTimeConverter_h */ |