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/. */
7 #include "MediaTimer.h"
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"
19 MediaTimer::MediaTimer(bool aFuzzy
)
20 : mMonitor("MediaTimer Monitor"),
21 mCreationTimeStamp(TimeStamp::Now()),
22 mUpdateScheduled(false),
24 TIMER_LOG("MediaTimer::MediaTimer");
26 // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
27 // thread, which is equivalent to an nsIThread for our purposes.
28 RefPtr
<SharedThreadPool
> threadPool(
29 SharedThreadPool::Get("MediaTimer"_ns
, 1));
30 mThread
= threadPool
.get();
31 mTimer
= NS_NewTimer(mThread
);
34 void MediaTimer::DispatchDestroy() {
35 // Hold a strong reference to the thread so that it doesn't get deleted in
36 // Destroy(), which may run completely before the stack if Dispatch() begins
38 nsCOMPtr
<nsIEventTarget
> thread
= mThread
;
40 thread
->Dispatch(NewNonOwningRunnableMethod("MediaTimer::Destroy", this,
41 &MediaTimer::Destroy
),
43 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
48 void MediaTimer::Destroy() {
49 MOZ_ASSERT(OnMediaTimerThread());
50 TIMER_LOG("MediaTimer::Destroy");
52 // Reject any outstanding entries.
54 MonitorAutoLock
lock(mMonitor
);
57 // Cancel the timer if necessary.
64 bool MediaTimer::OnMediaTimerThread() {
66 mThread
->IsOnCurrentThread(&rv
);
70 RefPtr
<MediaTimerPromise
> MediaTimer::WaitFor(const TimeDuration
& aDuration
,
71 const char* aCallSite
) {
72 return WaitUntil(TimeStamp::Now() + aDuration
, aCallSite
);
75 RefPtr
<MediaTimerPromise
> MediaTimer::WaitUntil(const TimeStamp
& aTimeStamp
,
76 const char* aCallSite
) {
77 MonitorAutoLock
mon(mMonitor
);
78 TIMER_LOG("MediaTimer::WaitUntil %" PRId64
, RelativeMicroseconds(aTimeStamp
));
79 Entry
e(aTimeStamp
, aCallSite
);
80 RefPtr
<MediaTimerPromise
> p
= e
.mPromise
.get();
86 void MediaTimer::Cancel() {
87 MonitorAutoLock
mon(mMonitor
);
88 TIMER_LOG("MediaTimer::Cancel");
92 void MediaTimer::ScheduleUpdate() {
93 mMonitor
.AssertCurrentThreadOwns();
94 if (mUpdateScheduled
) {
97 mUpdateScheduled
= true;
99 nsresult rv
= mThread
->Dispatch(
100 NewRunnableMethod("MediaTimer::Update", this, &MediaTimer::Update
),
102 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
107 void MediaTimer::Update() {
108 MonitorAutoLock
mon(mMonitor
);
112 bool MediaTimer::IsExpired(const TimeStamp
& aTarget
, const TimeStamp
& aNow
) {
113 MOZ_ASSERT(OnMediaTimerThread());
114 mMonitor
.AssertCurrentThreadOwns();
115 // Treat this timer as expired in fuzzy mode even if it is fired
116 // slightly (< 1ms) before the schedule target. So we don't need to schedule a
117 // timer with very small timeout again when the client doesn't need a high-res
119 TimeStamp t
= mFuzzy
? aTarget
- TimeDuration::FromMilliseconds(1) : aTarget
;
123 void MediaTimer::UpdateLocked() {
124 MOZ_ASSERT(OnMediaTimerThread());
125 mMonitor
.AssertCurrentThreadOwns();
126 mUpdateScheduled
= false;
128 TIMER_LOG("MediaTimer::UpdateLocked");
130 // Resolve all the promises whose time is up.
131 TimeStamp now
= TimeStamp::Now();
132 while (!mEntries
.empty() && IsExpired(mEntries
.top().mTimeStamp
, now
)) {
133 mEntries
.top().mPromise
->Resolve(true, __func__
);
134 DebugOnly
<TimeStamp
> poppedTimeStamp
= mEntries
.top().mTimeStamp
;
136 MOZ_ASSERT_IF(!mEntries
.empty(),
137 *&poppedTimeStamp
<= mEntries
.top().mTimeStamp
);
140 // If we've got no more entries, cancel any pending timer and bail out.
141 if (mEntries
.empty()) {
142 CancelTimerIfArmed();
146 // We've got more entries - (re)arm the timer for the soonest one.
147 if (!TimerIsArmed() || mEntries
.top().mTimeStamp
< mCurrentTimerTarget
) {
148 CancelTimerIfArmed();
149 ArmTimer(mEntries
.top().mTimeStamp
, now
);
153 void MediaTimer::Reject() {
154 mMonitor
.AssertCurrentThreadOwns();
155 while (!mEntries
.empty()) {
156 mEntries
.top().mPromise
->Reject(false, __func__
);
162 * We use a callback function, rather than a callback method, to ensure that
163 * the nsITimer does not artifically keep the refcount of the MediaTimer above
164 * zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so
165 * that we never fire against a dangling closure.
169 void MediaTimer::TimerCallback(nsITimer
* aTimer
, void* aClosure
) {
170 static_cast<MediaTimer
*>(aClosure
)->TimerFired();
173 void MediaTimer::TimerFired() {
174 MonitorAutoLock
mon(mMonitor
);
175 MOZ_ASSERT(OnMediaTimerThread());
176 mCurrentTimerTarget
= TimeStamp();
180 void MediaTimer::ArmTimer(const TimeStamp
& aTarget
, const TimeStamp
& aNow
) {
181 MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
182 MOZ_DIAGNOSTIC_ASSERT(aTarget
> aNow
);
184 const TimeDuration delay
= aTarget
- aNow
;
185 TIMER_LOG("MediaTimer::ArmTimer delay=%.3fms", delay
.ToMilliseconds());
186 mCurrentTimerTarget
= aTarget
;
187 MOZ_ALWAYS_SUCCEEDS(mTimer
->InitHighResolutionWithNamedFuncCallback(
188 &TimerCallback
, this, delay
, nsITimer::TYPE_ONE_SHOT
,
189 "MediaTimer::TimerCallback"));
192 } // namespace mozilla