Coverage Report

Created: 2018-09-25 14:53

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