mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1026803 part 6 - Make SystemTimeConverter detect clock skew; r=karlt
This patch revises the logic in SystemTimeConverter to detect backwards and forwards skew between the two time sources: the native time source (represented by the Time type) and the time source used to generate TimeStamp objects.
This commit is contained in:
parent
363121843f
commit
aa9ff80d0f
@ -12,11 +12,14 @@
|
|||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
|
||||||
// Utility class that takes a time value representing an integral number of
|
// Utility class that converts time values represented as an unsigned integral
|
||||||
// milliseconds (e.g. a native event time) that wraps within a fixed range (e.g.
|
// number of milliseconds from one time source (e.g. a native event time) to
|
||||||
// unsigned 32-bit range) and converts it to a TimeStamp.
|
// corresponding mozilla::TimeStamp objects.
|
||||||
//
|
//
|
||||||
// It does this by using a historical reference time recorded in both time
|
// 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).
|
// 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
|
// For performance reasons, this class is careful to minimize calls to the
|
||||||
@ -29,8 +32,10 @@ public:
|
|||||||
SystemTimeConverter()
|
SystemTimeConverter()
|
||||||
: mReferenceTime(Time(0))
|
: mReferenceTime(Time(0))
|
||||||
, mReferenceTimeStamp() // Initializes to the null timestamp
|
, mReferenceTimeStamp() // Initializes to the null timestamp
|
||||||
|
, mLastBackwardsSkewCheck(Time(0))
|
||||||
, kTimeRange(std::numeric_limits<Time>::max())
|
, kTimeRange(std::numeric_limits<Time>::max())
|
||||||
, kTimeHalfRange(kTimeRange / 2)
|
, kTimeHalfRange(kTimeRange / 2)
|
||||||
|
, kBackwardsSkewCheckInterval(Time(2000))
|
||||||
{
|
{
|
||||||
static_assert(!IsSigned<Time>::value, "Expected Time to be unsigned");
|
static_assert(!IsSigned<Time>::value, "Expected Time to be unsigned");
|
||||||
}
|
}
|
||||||
@ -46,84 +51,164 @@ public:
|
|||||||
}
|
}
|
||||||
TimeStamp roughlyNow = TimeStamp::NowLoRes();
|
TimeStamp roughlyNow = TimeStamp::NowLoRes();
|
||||||
|
|
||||||
// If the time is before the reference time there are two possibilities:
|
// Check for skew between the source of Time values and TimeStamp values.
|
||||||
|
// We do this by comparing two durations (both in ms):
|
||||||
//
|
//
|
||||||
// a) Time has wrapped
|
// i. The duration from the reference time to the passed-in time.
|
||||||
// b) The initial times were not delivered strictly in time order
|
// (Calculated below as timeSinceReference)
|
||||||
|
// ii. The duration from the reference timestamp to the current time
|
||||||
|
// based on TimeStamp::NowLoRes.
|
||||||
|
// (Calculated below as timeToNowByTimeStamp)
|
||||||
//
|
//
|
||||||
// I'm not sure if (b) ever happens but even if it doesn't currently occur,
|
// If (ii) - (i) is negative (and greater in magnitude than some tolerance
|
||||||
// it could come about if we change the way we fetch events, for example.
|
// to account for the inaccuracy in NowLoRes), 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".
|
||||||
|
//
|
||||||
|
// We call (ii) - (i), "deltaFromNow".
|
||||||
|
//
|
||||||
|
// Graphically:
|
||||||
|
//
|
||||||
|
// mReferenceTime aTime
|
||||||
|
// Time scale: ........+.......................*........
|
||||||
|
// |---timeSinceReference--|
|
||||||
|
//
|
||||||
|
// mReferenceTimeStamp roughlyNow
|
||||||
|
// TimeStamp scale: ........+...........................*....
|
||||||
|
// |----timeToNowByTimeStamp---|
|
||||||
|
//
|
||||||
|
// |---|
|
||||||
|
// deltaFromNow
|
||||||
//
|
//
|
||||||
// We can tell we have (b) if the time is a little bit before the reference
|
|
||||||
// time (i.e. less than half the range) and the timestamp is only a little
|
|
||||||
// bit past the reference timestamp (again less than half the range).
|
|
||||||
// In that case we should adjust the reference time so it is the
|
|
||||||
// earliest time.
|
|
||||||
Time timeSinceReference = aTime - mReferenceTime;
|
Time timeSinceReference = aTime - mReferenceTime;
|
||||||
if (timeSinceReference > kTimeHalfRange &&
|
|
||||||
roughlyNow - mReferenceTimeStamp <
|
// Cast the result to signed 64-bit integer first since that should be
|
||||||
TimeDuration::FromMilliseconds(kTimeHalfRange)) {
|
// enough to hold the range of values returned by ToMilliseconds() and
|
||||||
UpdateReferenceTime(aTime, aCurrentTimeGetter);
|
// the result of converting from double to an integer-type when the value is
|
||||||
timeSinceReference = aTime - mReferenceTime;
|
// 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.
|
||||||
|
Time timeToNowByTimeStamp =
|
||||||
|
static_cast<int64_t>((roughlyNow - mReferenceTimeStamp).ToMilliseconds());
|
||||||
|
Time deltaFromNow = timeToNowByTimeStamp - timeSinceReference;
|
||||||
|
|
||||||
|
// TimeStamp::NowLoRes should be accurate to within 15.6ms so we need to
|
||||||
|
// be at least that generous when detecting clock skew.
|
||||||
|
static const Time kTolerance = 30;
|
||||||
|
|
||||||
|
// Check for forwards skew (since deltaFromNow is an unsigned integer, we
|
||||||
|
// detect the minus case by seeing if it has underflowed).
|
||||||
|
if (deltaFromNow > kTimeHalfRange) {
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeStamp timestamp =
|
if (deltaFromNow <= kTolerance) {
|
||||||
mReferenceTimeStamp + TimeDuration::FromMilliseconds(timeSinceReference);
|
// If the time between event times and TimeStamp values is within
|
||||||
|
// the tolerance then assume we don't have clock skew so we can
|
||||||
// Time may have wrapped several times since we recorded the reference
|
// avoid checking for backwards skew for a while.
|
||||||
// time so we extend timestamp as needed.
|
mLastBackwardsSkewCheck = aTime;
|
||||||
double timesWrapped =
|
} else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
|
||||||
(roughlyNow - mReferenceTimeStamp).ToMilliseconds() / kTimeRange;
|
aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
|
||||||
int32_t cyclesToAdd =
|
mLastBackwardsSkewCheck = aTime;
|
||||||
static_cast<int32_t>(timesWrapped); // Round towards zero
|
|
||||||
|
|
||||||
// There is some imprecision in the above calculation since we are using
|
|
||||||
// TimeStamp::NowLoRes and mReferenceTime and mReferenceTimeStamp may not
|
|
||||||
// be *exactly* the same moment. It is possible we think that time
|
|
||||||
// has *just* wrapped based on comparing timestamps, but actually the
|
|
||||||
// time is *just about* to wrap; or vice versa.
|
|
||||||
// In the following, we detect this situation and adjust cyclesToAdd as
|
|
||||||
// necessary.
|
|
||||||
double intervalFraction = fmod(timesWrapped, 1.0);
|
|
||||||
|
|
||||||
// If our rough calculation of how many times we've wrapped based on
|
|
||||||
// comparing timestamps says we've just wrapped (specifically, less 10% past
|
|
||||||
// the wrap point), but the time is just before the wrap point (again,
|
|
||||||
// within 10%), then we need to reduce the number of wraps by 1.
|
|
||||||
if (intervalFraction < 0.1 && timeSinceReference > kTimeRange * 0.9) {
|
|
||||||
cyclesToAdd--;
|
|
||||||
// Likewise, if our rough calculation says we've just wrapped but actually
|
|
||||||
// the time is just after the wrap point, we need to add an extra wrap.
|
|
||||||
} else if (intervalFraction > 0.9 &&
|
|
||||||
timeSinceReference < kTimeRange * 0.1) {
|
|
||||||
cyclesToAdd++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cyclesToAdd > 0) {
|
// Finally, calculate the timestamp
|
||||||
timestamp += TimeDuration::FromMilliseconds(kTimeRange * cyclesToAdd);
|
return roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
MOZ_ASSERT(mReferenceTime - aReferenceTime < kTimeHalfRange,
|
||||||
|
"Expected aReferenceTime to be more recent than mReferenceTime");
|
||||||
|
if (aReferenceTime - mReferenceTime
|
||||||
|
> (aLowerBound - mReferenceTimeStamp).ToMilliseconds()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return timestamp;
|
// 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:
|
private:
|
||||||
template <typename CurrentTimeGetter>
|
template <typename CurrentTimeGetter>
|
||||||
void
|
void
|
||||||
UpdateReferenceTime(Time aTime,
|
UpdateReferenceTime(Time aReferenceTime,
|
||||||
const CurrentTimeGetter& aCurrentTimeGetter) {
|
const CurrentTimeGetter& aCurrentTimeGetter) {
|
||||||
mReferenceTime = aTime;
|
|
||||||
Time currentTime = aCurrentTimeGetter.GetCurrentTime();
|
Time currentTime = aCurrentTimeGetter.GetCurrentTime();
|
||||||
TimeStamp currentTimeStamp = TimeStamp::Now();
|
TimeStamp currentTimeStamp = TimeStamp::Now();
|
||||||
Time timeSinceReference = currentTime - aTime;
|
Time timeSinceReference = currentTime - aReferenceTime;
|
||||||
mReferenceTimeStamp =
|
TimeStamp referenceTimeStamp =
|
||||||
currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
|
currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
|
||||||
|
UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UpdateReferenceTime(Time aReferenceTime,
|
||||||
|
const TimeStamp& aReferenceTimeStamp) {
|
||||||
|
mReferenceTime = aReferenceTime;
|
||||||
|
mReferenceTimeStamp = aReferenceTimeStamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
Time mReferenceTime;
|
Time mReferenceTime;
|
||||||
TimeStamp mReferenceTimeStamp;
|
TimeStamp mReferenceTimeStamp;
|
||||||
|
Time mLastBackwardsSkewCheck;
|
||||||
|
|
||||||
const Time kTimeRange;
|
const Time kTimeRange;
|
||||||
const Time kTimeHalfRange;
|
const Time kTimeHalfRange;
|
||||||
|
const Time kBackwardsSkewCheckInterval;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
Loading…
Reference in New Issue
Block a user