Bug 1856663 - Add more chunks for Android mochitest-plain. r=jmaher,taskgraph-reviewe...
[gecko.git] / dom / media / MediaTimer.cpp
blobc34eb816fac323a60065ec681b96886ab22aa17c
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"
15 #include <math.h>
17 namespace mozilla {
19 MediaTimer::MediaTimer(bool aFuzzy)
20 : mMonitor("MediaTimer Monitor"),
21 mCreationTimeStamp(TimeStamp::Now()),
22 mUpdateScheduled(false),
23 mFuzzy(aFuzzy) {
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
37 // to unwind.
38 nsCOMPtr<nsIEventTarget> thread = mThread;
39 nsresult rv =
40 thread->Dispatch(NewNonOwningRunnableMethod("MediaTimer::Destroy", this,
41 &MediaTimer::Destroy),
42 NS_DISPATCH_NORMAL);
43 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
44 Unused << rv;
45 (void)rv;
48 void MediaTimer::Destroy() {
49 MOZ_ASSERT(OnMediaTimerThread());
50 TIMER_LOG("MediaTimer::Destroy");
52 // Reject any outstanding entries.
54 MonitorAutoLock lock(mMonitor);
55 Reject();
57 // Cancel the timer if necessary.
58 CancelTimerIfArmed();
61 delete this;
64 bool MediaTimer::OnMediaTimerThread() {
65 bool rv = false;
66 mThread->IsOnCurrentThread(&rv);
67 return 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();
81 mEntries.push(e);
82 ScheduleUpdate();
83 return p;
86 void MediaTimer::Cancel() {
87 MonitorAutoLock mon(mMonitor);
88 TIMER_LOG("MediaTimer::Cancel");
89 Reject();
92 void MediaTimer::ScheduleUpdate() {
93 mMonitor.AssertCurrentThreadOwns();
94 if (mUpdateScheduled) {
95 return;
97 mUpdateScheduled = true;
99 nsresult rv = mThread->Dispatch(
100 NewRunnableMethod("MediaTimer::Update", this, &MediaTimer::Update),
101 NS_DISPATCH_NORMAL);
102 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
103 Unused << rv;
104 (void)rv;
107 void MediaTimer::Update() {
108 MonitorAutoLock mon(mMonitor);
109 UpdateLocked();
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
118 // timer.
119 TimeStamp t = mFuzzy ? aTarget - TimeDuration::FromMilliseconds(1) : aTarget;
120 return t <= aNow;
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;
135 mEntries.pop();
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();
143 return;
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__);
157 mEntries.pop();
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.
168 /* static */
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();
177 UpdateLocked();
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