Line data Source code
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 "nsComponentManagerUtils.h"
13 : #include "nsThreadUtils.h"
14 : #include <math.h>
15 :
16 : namespace mozilla {
17 :
18 0 : NS_IMPL_ADDREF(MediaTimer)
19 0 : NS_IMPL_RELEASE_WITH_DESTROY(MediaTimer, DispatchDestroy())
20 :
21 0 : MediaTimer::MediaTimer()
22 : : mMonitor("MediaTimer Monitor")
23 0 : , mTimer(do_CreateInstance("@mozilla.org/timer;1"))
24 : , mCreationTimeStamp(TimeStamp::Now())
25 0 : , mUpdateScheduled(false)
26 : {
27 0 : TIMER_LOG("MediaTimer::MediaTimer");
28 :
29 : // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
30 : // thread, which is equivalent to an nsIThread for our purposes.
31 : RefPtr<SharedThreadPool> threadPool(
32 0 : SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaTimer"), 1));
33 0 : mThread = threadPool.get();
34 0 : mTimer->SetTarget(mThread);
35 0 : }
36 :
37 : void
38 0 : MediaTimer::DispatchDestroy()
39 : {
40 : // Hold a strong reference to the thread so that it doesn't get deleted in
41 : // Destroy(), which may run completely before the stack if Dispatch() begins
42 : // to unwind.
43 0 : nsCOMPtr<nsIEventTarget> thread = mThread;
44 : nsresult rv =
45 0 : thread->Dispatch(NewNonOwningRunnableMethod(
46 : "MediaTimer::Destroy", this, &MediaTimer::Destroy),
47 0 : NS_DISPATCH_NORMAL);
48 0 : MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
49 : (void) rv;
50 0 : }
51 :
52 : void
53 0 : MediaTimer::Destroy()
54 : {
55 0 : MOZ_ASSERT(OnMediaTimerThread());
56 0 : TIMER_LOG("MediaTimer::Destroy");
57 :
58 : // Reject any outstanding entries. There's no need to acquire the monitor
59 : // here, because we're on the timer thread and all other references to us
60 : // must be gone.
61 0 : while (!mEntries.empty()) {
62 0 : mEntries.top().mPromise->Reject(false, __func__);
63 0 : mEntries.pop();
64 : }
65 :
66 : // Cancel the timer if necessary.
67 0 : CancelTimerIfArmed();
68 :
69 0 : delete this;
70 0 : }
71 :
72 : bool
73 0 : MediaTimer::OnMediaTimerThread()
74 : {
75 0 : bool rv = false;
76 0 : mThread->IsOnCurrentThread(&rv);
77 0 : return rv;
78 : }
79 :
80 : RefPtr<MediaTimerPromise>
81 0 : MediaTimer::WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite)
82 : {
83 0 : MonitorAutoLock mon(mMonitor);
84 0 : TIMER_LOG("MediaTimer::WaitUntil %" PRId64, RelativeMicroseconds(aTimeStamp));
85 0 : Entry e(aTimeStamp, aCallSite);
86 0 : RefPtr<MediaTimerPromise> p = e.mPromise.get();
87 0 : mEntries.push(e);
88 0 : ScheduleUpdate();
89 0 : return p;
90 : }
91 :
92 : void
93 0 : MediaTimer::ScheduleUpdate()
94 : {
95 0 : mMonitor.AssertCurrentThreadOwns();
96 0 : if (mUpdateScheduled) {
97 0 : return;
98 : }
99 0 : mUpdateScheduled = true;
100 :
101 0 : nsresult rv = mThread->Dispatch(
102 0 : NewRunnableMethod("MediaTimer::Update", this, &MediaTimer::Update),
103 0 : NS_DISPATCH_NORMAL);
104 0 : MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
105 : (void) rv;
106 : }
107 :
108 : void
109 0 : MediaTimer::Update()
110 : {
111 0 : MonitorAutoLock mon(mMonitor);
112 0 : UpdateLocked();
113 0 : }
114 :
115 : void
116 0 : MediaTimer::UpdateLocked()
117 : {
118 0 : MOZ_ASSERT(OnMediaTimerThread());
119 0 : mMonitor.AssertCurrentThreadOwns();
120 0 : mUpdateScheduled = false;
121 :
122 0 : TIMER_LOG("MediaTimer::UpdateLocked");
123 :
124 : // Resolve all the promises whose time is up.
125 0 : TimeStamp now = TimeStamp::Now();
126 0 : while (!mEntries.empty() && mEntries.top().mTimeStamp <= now) {
127 0 : mEntries.top().mPromise->Resolve(true, __func__);
128 0 : DebugOnly<TimeStamp> poppedTimeStamp = mEntries.top().mTimeStamp;
129 0 : mEntries.pop();
130 0 : MOZ_ASSERT_IF(!mEntries.empty(), *&poppedTimeStamp <= mEntries.top().mTimeStamp);
131 : }
132 :
133 : // If we've got no more entries, cancel any pending timer and bail out.
134 0 : if (mEntries.empty()) {
135 0 : CancelTimerIfArmed();
136 0 : return;
137 : }
138 :
139 : // We've got more entries - (re)arm the timer for the soonest one.
140 0 : if (!TimerIsArmed() || mEntries.top().mTimeStamp < mCurrentTimerTarget) {
141 0 : CancelTimerIfArmed();
142 0 : ArmTimer(mEntries.top().mTimeStamp, now);
143 : }
144 : }
145 :
146 : /*
147 : * We use a callback function, rather than a callback method, to ensure that
148 : * the nsITimer does not artifically keep the refcount of the MediaTimer above
149 : * zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so that
150 : * we never fire against a dangling closure.
151 : */
152 :
153 : /* static */ void
154 0 : MediaTimer::TimerCallback(nsITimer* aTimer, void* aClosure)
155 : {
156 0 : static_cast<MediaTimer*>(aClosure)->TimerFired();
157 0 : }
158 :
159 : void
160 0 : MediaTimer::TimerFired()
161 : {
162 0 : MonitorAutoLock mon(mMonitor);
163 0 : MOZ_ASSERT(OnMediaTimerThread());
164 0 : mCurrentTimerTarget = TimeStamp();
165 0 : UpdateLocked();
166 0 : }
167 :
168 : void
169 0 : MediaTimer::ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow)
170 : {
171 0 : MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
172 0 : MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow);
173 :
174 : // XPCOM timer resolution is in milliseconds. It's important to never resolve
175 : // a timer when mTarget might compare < now (even if very close), so round up.
176 0 : unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds());
177 0 : TIMER_LOG("MediaTimer::ArmTimer delay=%lu", delay);
178 0 : mCurrentTimerTarget = aTarget;
179 0 : nsresult rv = mTimer->InitWithNamedFuncCallback(&TimerCallback, this, delay,
180 : nsITimer::TYPE_ONE_SHOT,
181 0 : "MediaTimer::TimerCallback");
182 0 : MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
183 : (void) rv;
184 0 : }
185 :
186 : } // namespace mozilla
|