Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/MediaTimer.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim:set ts=2 sw=2 sts=2 et cindent: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "MediaTimer.h"
8
9
#include "mozilla/DebugOnly.h"
10
#include "mozilla/RefPtr.h"
11
#include "mozilla/SharedThreadPool.h"
12
#include "mozilla/Unused.h"
13
#include "nsComponentManagerUtils.h"
14
#include "nsThreadUtils.h"
15
#include <math.h>
16
17
namespace mozilla {
18
19
NS_IMPL_ADDREF(MediaTimer)
20
NS_IMPL_RELEASE_WITH_DESTROY(MediaTimer, DispatchDestroy())
21
22
MediaTimer::MediaTimer(bool aFuzzy)
23
  : mMonitor("MediaTimer Monitor")
24
  , mTimer(NS_NewTimer())
25
  , mCreationTimeStamp(TimeStamp::Now())
26
  , mUpdateScheduled(false)
27
  , mFuzzy(aFuzzy)
28
0
{
29
0
  TIMER_LOG("MediaTimer::MediaTimer");
30
0
31
0
  // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
32
0
  // thread, which is equivalent to an nsIThread for our purposes.
33
0
  RefPtr<SharedThreadPool> threadPool(
34
0
    SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaTimer"), 1));
35
0
  mThread = threadPool.get();
36
0
  mTimer->SetTarget(mThread);
37
0
}
38
39
void
40
MediaTimer::DispatchDestroy()
41
0
{
42
0
  // Hold a strong reference to the thread so that it doesn't get deleted in
43
0
  // Destroy(), which may run completely before the stack if Dispatch() begins
44
0
  // to unwind.
45
0
  nsCOMPtr<nsIEventTarget> thread = mThread;
46
0
  nsresult rv =
47
0
    thread->Dispatch(NewNonOwningRunnableMethod(
48
0
                       "MediaTimer::Destroy", this, &MediaTimer::Destroy),
49
0
                     NS_DISPATCH_NORMAL);
50
0
  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
51
0
  Unused << rv;
52
0
  (void) rv;
53
0
}
54
55
void
56
MediaTimer::Destroy()
57
0
{
58
0
  MOZ_ASSERT(OnMediaTimerThread());
59
0
  TIMER_LOG("MediaTimer::Destroy");
60
0
61
0
  // Reject any outstanding entries.
62
0
  {
63
0
    MonitorAutoLock lock(mMonitor);
64
0
    Reject();
65
0
  }
66
0
67
0
  // Cancel the timer if necessary.
68
0
  CancelTimerIfArmed();
69
0
70
0
  delete this;
71
0
}
72
73
bool
74
MediaTimer::OnMediaTimerThread()
75
0
{
76
0
  bool rv = false;
77
0
  mThread->IsOnCurrentThread(&rv);
78
0
  return rv;
79
0
}
80
81
RefPtr<MediaTimerPromise>
82
MediaTimer::WaitFor(const TimeDuration& aDuration, const char* aCallSite)
83
0
{
84
0
  return WaitUntil(TimeStamp::Now() + aDuration, aCallSite);
85
0
}
86
87
RefPtr<MediaTimerPromise>
88
MediaTimer::WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite)
89
0
{
90
0
  MonitorAutoLock mon(mMonitor);
91
0
  TIMER_LOG("MediaTimer::WaitUntil %" PRId64, RelativeMicroseconds(aTimeStamp));
92
0
  Entry e(aTimeStamp, aCallSite);
93
0
  RefPtr<MediaTimerPromise> p = e.mPromise.get();
94
0
  mEntries.push(e);
95
0
  ScheduleUpdate();
96
0
  return p;
97
0
}
98
99
void
100
MediaTimer::Cancel()
101
0
{
102
0
  MonitorAutoLock mon(mMonitor);
103
0
  TIMER_LOG("MediaTimer::Cancel");
104
0
  Reject();
105
0
}
106
107
void
108
MediaTimer::ScheduleUpdate()
109
0
{
110
0
  mMonitor.AssertCurrentThreadOwns();
111
0
  if (mUpdateScheduled) {
112
0
    return;
113
0
  }
114
0
  mUpdateScheduled = true;
115
0
116
0
  nsresult rv = mThread->Dispatch(
117
0
    NewRunnableMethod("MediaTimer::Update", this, &MediaTimer::Update),
118
0
    NS_DISPATCH_NORMAL);
119
0
  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
120
0
  Unused << rv;
121
0
  (void) rv;
122
0
}
123
124
void
125
MediaTimer::Update()
126
0
{
127
0
  MonitorAutoLock mon(mMonitor);
128
0
  UpdateLocked();
129
0
}
130
131
bool
132
MediaTimer::IsExpired(const TimeStamp& aTarget, const TimeStamp& aNow)
133
0
{
134
0
  MOZ_ASSERT(OnMediaTimerThread());
135
0
  mMonitor.AssertCurrentThreadOwns();
136
0
  // Treat this timer as expired in fuzzy mode even if it is fired
137
0
  // slightly (< 1ms) before the schedule target. So we don't need to schedule a
138
0
  // timer with very small timeout again when the client doesn't need a high-res
139
0
  // timer.
140
0
  TimeStamp t = mFuzzy ? aTarget - TimeDuration::FromMilliseconds(1) : aTarget;
141
0
  return t <= aNow;
142
0
}
143
144
void
145
MediaTimer::UpdateLocked()
146
0
{
147
0
  MOZ_ASSERT(OnMediaTimerThread());
148
0
  mMonitor.AssertCurrentThreadOwns();
149
0
  mUpdateScheduled = false;
150
0
151
0
  TIMER_LOG("MediaTimer::UpdateLocked");
152
0
153
0
  // Resolve all the promises whose time is up.
154
0
  TimeStamp now = TimeStamp::Now();
155
0
  while (!mEntries.empty() && IsExpired(mEntries.top().mTimeStamp, now)) {
156
0
    mEntries.top().mPromise->Resolve(true, __func__);
157
0
    DebugOnly<TimeStamp> poppedTimeStamp = mEntries.top().mTimeStamp;
158
0
    mEntries.pop();
159
0
    MOZ_ASSERT_IF(!mEntries.empty(), *&poppedTimeStamp <= mEntries.top().mTimeStamp);
160
0
  }
161
0
162
0
  // If we've got no more entries, cancel any pending timer and bail out.
163
0
  if (mEntries.empty()) {
164
0
    CancelTimerIfArmed();
165
0
    return;
166
0
  }
167
0
168
0
  // We've got more entries - (re)arm the timer for the soonest one.
169
0
  if (!TimerIsArmed() || mEntries.top().mTimeStamp < mCurrentTimerTarget) {
170
0
    CancelTimerIfArmed();
171
0
    ArmTimer(mEntries.top().mTimeStamp, now);
172
0
  }
173
0
}
174
175
void
176
MediaTimer::Reject()
177
0
{
178
0
  mMonitor.AssertCurrentThreadOwns();
179
0
  while (!mEntries.empty()) {
180
0
    mEntries.top().mPromise->Reject(false, __func__);
181
0
    mEntries.pop();
182
0
  }
183
0
}
184
185
/*
186
 * We use a callback function, rather than a callback method, to ensure that
187
 * the nsITimer does not artifically keep the refcount of the MediaTimer above
188
 * zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so that
189
 * we never fire against a dangling closure.
190
 */
191
192
/* static */ void
193
MediaTimer::TimerCallback(nsITimer* aTimer, void* aClosure)
194
0
{
195
0
  static_cast<MediaTimer*>(aClosure)->TimerFired();
196
0
}
197
198
void
199
MediaTimer::TimerFired()
200
0
{
201
0
  MonitorAutoLock mon(mMonitor);
202
0
  MOZ_ASSERT(OnMediaTimerThread());
203
0
  mCurrentTimerTarget = TimeStamp();
204
0
  UpdateLocked();
205
0
}
206
207
void
208
MediaTimer::ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow)
209
0
{
210
0
  MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
211
0
  MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow);
212
0
213
0
  // XPCOM timer resolution is in milliseconds. It's important to never resolve
214
0
  // a timer when mTarget might compare < now (even if very close), so round up.
215
0
  unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds());
216
0
  TIMER_LOG("MediaTimer::ArmTimer delay=%lu", delay);
217
0
  mCurrentTimerTarget = aTarget;
218
0
  nsresult rv = mTimer->InitWithNamedFuncCallback(&TimerCallback, this, delay,
219
0
                                                  nsITimer::TYPE_ONE_SHOT,
220
0
                                                  "MediaTimer::TimerCallback");
221
0
  MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
222
0
  Unused << rv;
223
0
  (void) rv;
224
0
}
225
226
} // namespace mozilla