Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef SystemTimeConverter_h
7 : #define SystemTimeConverter_h
8 :
9 : #include <limits>
10 : #include "mozilla/TimeStamp.h"
11 : #include "mozilla/TypeTraits.h"
12 :
13 : namespace mozilla {
14 :
15 : // Utility class that converts time values represented as an unsigned integral
16 : // number of milliseconds from one time source (e.g. a native event time) to
17 : // corresponding mozilla::TimeStamp objects.
18 : //
19 : // This class handles wrapping of integer values and skew between the time
20 : // source and mozilla::TimeStamp values.
21 : //
22 : // It does this by using an historical reference time recorded in both time
23 : // scales (i.e. both as a numerical time value and as a TimeStamp).
24 : //
25 : // For performance reasons, this class is careful to minimize calls to the
26 : // native "current time" function (e.g. gdk_x11_server_get_time) since this can
27 : // be slow.
28 : template <typename Time>
29 : class SystemTimeConverter {
30 : public:
31 1 : SystemTimeConverter()
32 : : mReferenceTime(Time(0))
33 : , mReferenceTimeStamp() // Initializes to the null timestamp
34 : , mLastBackwardsSkewCheck(Time(0))
35 1 : , kTimeRange(std::numeric_limits<Time>::max())
36 1 : , kTimeHalfRange(kTimeRange / 2)
37 3 : , kBackwardsSkewCheckInterval(Time(2000))
38 : {
39 : static_assert(!IsSigned<Time>::value, "Expected Time to be unsigned");
40 1 : }
41 :
42 : template <typename CurrentTimeGetter>
43 : mozilla::TimeStamp
44 6 : GetTimeStampFromSystemTime(Time aTime,
45 : CurrentTimeGetter& aCurrentTimeGetter) {
46 : // If the reference time is not set, use the current time value to fill
47 : // it in.
48 6 : if (mReferenceTimeStamp.IsNull()) {
49 1 : UpdateReferenceTime(aTime, aCurrentTimeGetter);
50 : }
51 6 : TimeStamp roughlyNow = TimeStamp::Now();
52 :
53 : // Check for skew between the source of Time values and TimeStamp values.
54 : // We do this by comparing two durations (both in ms):
55 : //
56 : // i. The duration from the reference time to the passed-in time.
57 : // (timeDelta in the diagram below)
58 : // ii. The duration from the reference timestamp to the current time
59 : // based on TimeStamp::Now.
60 : // (timeStampDelta in the diagram below)
61 : //
62 : // Normally, we'd expect (ii) to be slightly larger than (i) to account
63 : // for the time taken between generating the event and processing it.
64 : //
65 : // If (ii) - (i) is negative then the source of Time values is getting
66 : // "ahead" of TimeStamp. We call this "forwards" skew below.
67 : //
68 : // For the reverse case, if (ii) - (i) is positive (and greater than some
69 : // tolerance factor), then we may have "backwards" skew. This is often
70 : // the case when we have a backlog of events and by the time we process
71 : // them, the time given by the system is comparatively "old".
72 : //
73 : // We call the absolute difference between (i) and (ii), "deltaFromNow".
74 : //
75 : // Graphically:
76 : //
77 : // mReferenceTime aTime
78 : // Time scale: ........+.......................*........
79 : // |--------timeDelta------|
80 : //
81 : // mReferenceTimeStamp roughlyNow
82 : // TimeStamp scale: ........+...........................*....
83 : // |------timeStampDelta-------|
84 : //
85 : // |---|
86 : // deltaFromNow
87 : //
88 : Time deltaFromNow;
89 6 : bool newer = IsTimeNewerThanTimestamp(aTime, roughlyNow, &deltaFromNow);
90 :
91 : // Tolerance when detecting clock skew.
92 : static const Time kTolerance = 30;
93 :
94 : // Check for forwards skew
95 6 : if (newer) {
96 : // Make aTime correspond to roughlyNow
97 0 : UpdateReferenceTime(aTime, roughlyNow);
98 :
99 : // We didn't have backwards skew so don't bother checking for
100 : // backwards skew again for a little while.
101 0 : mLastBackwardsSkewCheck = aTime;
102 :
103 0 : return roughlyNow;
104 : }
105 :
106 6 : if (deltaFromNow <= kTolerance) {
107 : // If the time between event times and TimeStamp values is within
108 : // the tolerance then assume we don't have clock skew so we can
109 : // avoid checking for backwards skew for a while.
110 2 : mLastBackwardsSkewCheck = aTime;
111 4 : } else if (aTime - mLastBackwardsSkewCheck > kBackwardsSkewCheckInterval) {
112 2 : aCurrentTimeGetter.GetTimeAsyncForPossibleBackwardsSkew(roughlyNow);
113 2 : mLastBackwardsSkewCheck = aTime;
114 : }
115 :
116 : // Finally, calculate the timestamp
117 6 : return roughlyNow - TimeDuration::FromMilliseconds(deltaFromNow);
118 : }
119 :
120 : void
121 1 : CompensateForBackwardsSkew(Time aReferenceTime,
122 : const TimeStamp &aLowerBound) {
123 : // Check if we actually have backwards skew. Backwards skew looks like
124 : // the following:
125 : //
126 : // mReferenceTime
127 : // Time: ..+...a...b...c..........................
128 : //
129 : // mReferenceTimeStamp
130 : // TimeStamp: ..+.....a.....b.....c....................
131 : //
132 : // Converted
133 : // time: ......a'..b'..c'.........................
134 : //
135 : // What we need to do is bring mReferenceTime "forwards".
136 : //
137 : // Suppose when we get (c), we detect possible backwards skew and trigger
138 : // an async request for the current time (which is passed in here as
139 : // aReferenceTime).
140 : //
141 : // We end up with something like the following:
142 : //
143 : // mReferenceTime aReferenceTime
144 : // Time: ..+...a...b...c...v......................
145 : //
146 : // mReferenceTimeStamp
147 : // TimeStamp: ..+.....a.....b.....c..........x.........
148 : // ^ ^
149 : // aLowerBound TimeStamp::Now()
150 : //
151 : // If the duration (aLowerBound - mReferenceTimeStamp) is greater than
152 : // (aReferenceTime - mReferenceTime) then we know we have backwards skew.
153 : //
154 : // If that's not the case, then we probably just got caught behind
155 : // temporarily.
156 : Time delta;
157 1 : if (IsTimeNewerThanTimestamp(aReferenceTime, aLowerBound, &delta)) {
158 0 : return;
159 : }
160 :
161 : // We have backwards skew; the equivalent TimeStamp for aReferenceTime lies
162 : // somewhere between aLowerBound (which was the TimeStamp when we triggered
163 : // the async request for the current time) and TimeStamp::Now().
164 : //
165 : // If aReferenceTime was waiting in the event queue for a long time, the
166 : // equivalent TimeStamp might be much closer to aLowerBound than
167 : // TimeStamp::Now() so for now we just set it to aLowerBound. That's
168 : // guaranteed to be at least somewhat of an improvement.
169 1 : UpdateReferenceTime(aReferenceTime, aLowerBound);
170 : }
171 :
172 : private:
173 : template <typename CurrentTimeGetter>
174 : void
175 1 : UpdateReferenceTime(Time aReferenceTime,
176 : const CurrentTimeGetter& aCurrentTimeGetter) {
177 1 : Time currentTime = aCurrentTimeGetter.GetCurrentTime();
178 1 : TimeStamp currentTimeStamp = TimeStamp::Now();
179 1 : Time timeSinceReference = currentTime - aReferenceTime;
180 : TimeStamp referenceTimeStamp =
181 1 : currentTimeStamp - TimeDuration::FromMilliseconds(timeSinceReference);
182 1 : UpdateReferenceTime(aReferenceTime, referenceTimeStamp);
183 1 : }
184 :
185 : void
186 2 : UpdateReferenceTime(Time aReferenceTime,
187 : const TimeStamp& aReferenceTimeStamp) {
188 2 : mReferenceTime = aReferenceTime;
189 2 : mReferenceTimeStamp = aReferenceTimeStamp;
190 2 : }
191 :
192 : bool
193 7 : IsTimeNewerThanTimestamp(Time aTime, TimeStamp aTimeStamp, Time* aDelta)
194 : {
195 7 : Time timeDelta = aTime - mReferenceTime;
196 :
197 : // Cast the result to signed 64-bit integer first since that should be
198 : // enough to hold the range of values returned by ToMilliseconds() and
199 : // the result of converting from double to an integer-type when the value
200 : // is outside the integer range is undefined.
201 : // Then we do an implicit cast to Time (typically an unsigned 32-bit
202 : // integer) which wraps times outside that range.
203 : Time timeStampDelta =
204 7 : static_cast<int64_t>((aTimeStamp - mReferenceTimeStamp).ToMilliseconds());
205 :
206 7 : Time timeToTimeStamp = timeStampDelta - timeDelta;
207 7 : bool isNewer = false;
208 7 : if (timeToTimeStamp == 0) {
209 2 : *aDelta = 0;
210 5 : } else if (timeToTimeStamp < kTimeHalfRange) {
211 5 : *aDelta = timeToTimeStamp;
212 : } else {
213 0 : isNewer = true;
214 0 : *aDelta = timeDelta - timeStampDelta;
215 : }
216 :
217 7 : return isNewer;
218 : }
219 :
220 : Time mReferenceTime;
221 : TimeStamp mReferenceTimeStamp;
222 : Time mLastBackwardsSkewCheck;
223 :
224 : const Time kTimeRange;
225 : const Time kTimeHalfRange;
226 : const Time kBackwardsSkewCheckInterval;
227 : };
228 :
229 : } // namespace mozilla
230 :
231 : #endif /* SystemTimeConverter_h */
|