MediaTimer.cpp (6531B)
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 <math.h> 10 11 #include "mozilla/AwakeTimeStamp.h" 12 #include "mozilla/DebugOnly.h" 13 #include "mozilla/RefPtr.h" 14 #include "mozilla/SharedThreadPool.h" 15 #include "nsComponentManagerUtils.h" 16 #include "nsThreadUtils.h" 17 18 namespace mozilla { 19 20 template <typename T> 21 MediaTimer<T>::MediaTimer(bool aFuzzy) 22 : mMonitor("MediaTimer Monitor"), 23 mCreationTimeStamp(T::Now()), 24 mUpdateScheduled(false), 25 mFuzzy(aFuzzy) { 26 TIMER_LOG("MediaTimer::MediaTimer"); 27 28 // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one 29 // thread, which is equivalent to an nsIThread for our purposes. 30 RefPtr<SharedThreadPool> threadPool( 31 SharedThreadPool::Get("MediaTimer"_ns, 1)); 32 mThread = threadPool.get(); 33 mTimer = NS_NewTimer(mThread); 34 } 35 36 template <typename T> 37 void MediaTimer<T>::DispatchDestroy() { 38 // Hold a strong reference to the thread so that it doesn't get deleted in 39 // Destroy(), which may run completely before the stack if Dispatch() begins 40 // to unwind. 41 nsCOMPtr<nsIEventTarget> thread = mThread; 42 nsresult rv = 43 thread->Dispatch(NewNonOwningRunnableMethod("MediaTimer::Destroy", this, 44 &MediaTimer::Destroy), 45 NS_DISPATCH_NORMAL); 46 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); 47 (void)rv; 48 (void)rv; 49 } 50 51 // Runs on the timer thread. 52 template <typename T> 53 void MediaTimer<T>::Destroy() { 54 MOZ_ASSERT(OnMediaTimerThread()); 55 TIMER_LOG("MediaTimer::Destroy"); 56 57 // Reject any outstanding entries. 58 { 59 MonitorAutoLock lock(mMonitor); 60 Reject(); 61 62 // Cancel the timer if necessary. 63 CancelTimerIfArmed(); 64 } 65 66 delete this; 67 } 68 69 template <typename T> 70 bool MediaTimer<T>::OnMediaTimerThread() { 71 bool rv = false; 72 mThread->IsOnCurrentThread(&rv); 73 return rv; 74 } 75 76 template <typename T> 77 RefPtr<MediaTimerPromise> MediaTimer<T>::WaitFor( 78 const typename T::DurationType& aDuration, StaticString aCallSite) { 79 return WaitUntil(T::Now() + aDuration, aCallSite); 80 } 81 82 template <typename T> 83 RefPtr<MediaTimerPromise> MediaTimer<T>::WaitUntil(const T& aTimeStamp, 84 StaticString aCallSite) { 85 MonitorAutoLock mon(mMonitor); 86 TIMER_LOG("MediaTimer::WaitUntil %" PRId64, RelativeMicroseconds(aTimeStamp)); 87 Entry e(aTimeStamp, aCallSite); 88 RefPtr<MediaTimerPromise> p = e.mPromise.get(); 89 mEntries.push(e); 90 ScheduleUpdate(); 91 return p; 92 } 93 94 template <typename T> 95 void MediaTimer<T>::Cancel() { 96 MonitorAutoLock mon(mMonitor); 97 TIMER_LOG("MediaTimer::Cancel"); 98 Reject(); 99 } 100 101 template <typename T> 102 void MediaTimer<T>::ScheduleUpdate() { 103 mMonitor.AssertCurrentThreadOwns(); 104 if (mUpdateScheduled) { 105 return; 106 } 107 mUpdateScheduled = true; 108 109 nsresult rv = mThread->Dispatch( 110 NewRunnableMethod("MediaTimer::Update", this, &MediaTimer::Update), 111 NS_DISPATCH_NORMAL); 112 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); 113 (void)rv; 114 (void)rv; 115 } 116 117 template <typename T> 118 void MediaTimer<T>::Update() { 119 MonitorAutoLock mon(mMonitor); 120 UpdateLocked(); 121 } 122 123 template <typename T> 124 bool MediaTimer<T>::IsExpired(const T& aTarget, const T& aNow) { 125 MOZ_ASSERT(OnMediaTimerThread()); 126 mMonitor.AssertCurrentThreadOwns(); 127 // Treat this timer as expired in fuzzy mode even if it is fired 128 // slightly (< 1ms) before the schedule target. So we don't need to schedule 129 // a timer with very small timeout again when the client doesn't need a 130 // high-res timer. 131 T t = mFuzzy ? aTarget - T::DurationType::FromMilliseconds(1) : aTarget; 132 return t <= aNow; 133 } 134 135 template <typename T> 136 void MediaTimer<T>::UpdateLocked() { 137 MOZ_ASSERT(OnMediaTimerThread()); 138 mMonitor.AssertCurrentThreadOwns(); 139 mUpdateScheduled = false; 140 141 TIMER_LOG("MediaTimer::UpdateLocked"); 142 143 // Resolve all the promises whose time is up. 144 T now = T::Now(); 145 while (!mEntries.empty() && IsExpired(mEntries.top().mTimeStamp, now)) { 146 mEntries.top().mPromise->Resolve(true, __func__); 147 DebugOnly<T> poppedTimeStamp = mEntries.top().mTimeStamp; 148 mEntries.pop(); 149 MOZ_ASSERT_IF(!mEntries.empty(), 150 *&poppedTimeStamp <= mEntries.top().mTimeStamp); 151 } 152 153 // If we've got no more entries, cancel any pending timer and bail out. 154 if (mEntries.empty()) { 155 CancelTimerIfArmed(); 156 return; 157 } 158 159 // We've got more entries - (re)arm the timer for the soonest one. 160 if (!TimerIsArmed() || 161 mEntries.top().mTimeStamp < mCurrentTimerTarget.value()) { 162 CancelTimerIfArmed(); 163 ArmTimer(mEntries.top().mTimeStamp, now); 164 } 165 } 166 167 template <typename T> 168 void MediaTimer<T>::Reject() { 169 mMonitor.AssertCurrentThreadOwns(); 170 while (!mEntries.empty()) { 171 mEntries.top().mPromise->Reject(false, __func__); 172 mEntries.pop(); 173 } 174 } 175 176 template <typename T> 177 /* static */ void MediaTimer<T>::TimerCallback(nsITimer* aTimer, 178 void* aClosure) { 179 static_cast<MediaTimer<T>*>(aClosure)->TimerFired(); 180 } 181 182 template <typename T> 183 void MediaTimer<T>::TimerFired() { 184 MonitorAutoLock mon(mMonitor); 185 MOZ_ASSERT(OnMediaTimerThread()); 186 mCurrentTimerTarget = Nothing(); 187 UpdateLocked(); 188 } 189 190 template <typename T> 191 void MediaTimer<T>::ArmTimer(const T& aTarget, const T& aNow) { 192 MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed()); 193 MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow); 194 195 const typename T::DurationType delay = aTarget - aNow; 196 TIMER_LOG("MediaTimer::ArmTimer delay=%.3fms", delay.ToMilliseconds()); 197 mCurrentTimerTarget.emplace(aTarget); 198 TimeDuration duration = 199 TimeDuration::FromMicroseconds(delay.ToMicroseconds()); 200 MOZ_ALWAYS_SUCCEEDS(mTimer->InitHighResolutionWithNamedFuncCallback( 201 &TimerCallback, this, duration, nsITimer::TYPE_ONE_SHOT, 202 "MediaTimer::TimerCallback"_ns)); 203 } 204 205 template <typename T> 206 bool MediaTimer<T>::TimerIsArmed() { 207 return mCurrentTimerTarget.isSome(); 208 } 209 210 template <typename T> 211 void MediaTimer<T>::CancelTimerIfArmed() { 212 MOZ_ASSERT(OnMediaTimerThread()); 213 if (TimerIsArmed()) { 214 TIMER_LOG("MediaTimer::CancelTimerIfArmed canceling timer"); 215 mTimer->Cancel(); 216 mCurrentTimerTarget = Nothing(); 217 } 218 } 219 220 template class MediaTimer<AwakeTimeStamp>; 221 template class MediaTimer<TimeStamp>; 222 223 } // namespace mozilla