Bug 1516095 [wpt PR 14643] - [Resource-Timing] Flakiness reduction, a=testonly
[gecko.git] / image / DecodePool.cpp
blobb3bb83af034725c044f2e0b9710d05ffffa7491a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DecodePool.h"
8 #include <algorithm>
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/Monitor.h"
13 #include "mozilla/TimeStamp.h"
14 #include "nsCOMPtr.h"
15 #include "nsIObserverService.h"
16 #include "nsIThreadPool.h"
17 #include "nsThreadManager.h"
18 #include "nsThreadUtils.h"
19 #include "nsXPCOMCIDInternal.h"
20 #include "prsystem.h"
21 #include "nsIXULRuntime.h"
23 #include "gfxPrefs.h"
25 #include "Decoder.h"
26 #include "IDecodingTask.h"
27 #include "RasterImage.h"
29 using std::max;
30 using std::min;
32 namespace mozilla {
33 namespace image {
35 ///////////////////////////////////////////////////////////////////////////////
36 // DecodePool implementation.
37 ///////////////////////////////////////////////////////////////////////////////
39 /* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
40 /* static */ uint32_t DecodePool::sNumCores = 0;
42 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
44 struct Work {
45 enum class Type { TASK, SHUTDOWN } mType;
47 RefPtr<IDecodingTask> mTask;
50 class DecodePoolImpl {
51 public:
52 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
53 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
55 DecodePoolImpl(uint8_t aMaxThreads, uint8_t aMaxIdleThreads,
56 TimeDuration aIdleTimeout)
57 : mMonitor("DecodePoolImpl"),
58 mThreads(aMaxThreads),
59 mIdleTimeout(aIdleTimeout),
60 mMaxIdleThreads(aMaxIdleThreads),
61 mAvailableThreads(aMaxThreads),
62 mIdleThreads(0),
63 mShuttingDown(false) {
64 MonitorAutoLock lock(mMonitor);
65 bool success = CreateThread();
66 MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!");
69 /// Shut down the provided decode pool thread.
70 void ShutdownThread(nsIThread* aThisThread, bool aShutdownIdle) {
72 // If this is an idle thread shutdown, then we need to remove it from the
73 // worker array. Process shutdown will move the entire array.
74 MonitorAutoLock lock(mMonitor);
75 if (!mShuttingDown) {
76 ++mAvailableThreads;
77 DebugOnly<bool> removed = mThreads.RemoveElement(aThisThread);
78 MOZ_ASSERT(aShutdownIdle);
79 MOZ_ASSERT(mAvailableThreads < mThreads.Capacity());
80 MOZ_ASSERT(removed);
84 // Threads have to be shut down from another thread, so we'll ask the
85 // main thread to do it for us.
86 SystemGroup::Dispatch(TaskCategory::Other,
87 NewRunnableMethod("DecodePoolImpl::ShutdownThread",
88 aThisThread, &nsIThread::Shutdown));
91 /**
92 * Requests shutdown. New work items will be dropped on the floor, and all
93 * decode pool threads will be shut down once existing work items have been
94 * processed.
96 void Shutdown() {
97 nsTArray<nsCOMPtr<nsIThread>> threads;
100 MonitorAutoLock lock(mMonitor);
101 mShuttingDown = true;
102 mAvailableThreads = 0;
103 threads.SwapElements(mThreads);
104 mMonitor.NotifyAll();
107 for (uint32_t i = 0; i < threads.Length(); ++i) {
108 threads[i]->Shutdown();
112 bool IsShuttingDown() const {
113 MonitorAutoLock lock(mMonitor);
114 return mShuttingDown;
117 /// Pushes a new decode work item.
118 void PushWork(IDecodingTask* aTask) {
119 MOZ_ASSERT(aTask);
120 RefPtr<IDecodingTask> task(aTask);
122 MonitorAutoLock lock(mMonitor);
124 if (mShuttingDown) {
125 // Drop any new work on the floor if we're shutting down.
126 return;
129 if (task->Priority() == TaskPriority::eHigh) {
130 mHighPriorityQueue.AppendElement(std::move(task));
131 } else {
132 mLowPriorityQueue.AppendElement(std::move(task));
135 // If there are pending tasks, create more workers if and only if we have
136 // not exceeded the capacity, and any previously created workers are ready.
137 if (mAvailableThreads) {
138 size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length();
139 if (pending > mIdleThreads) {
140 CreateThread();
144 mMonitor.Notify();
147 Work StartWork(bool aShutdownIdle) {
148 MonitorAutoLock lock(mMonitor);
150 // The thread was already marked as idle when it was created. Once it gets
151 // its first work item, it is assumed it is busy performing that work until
152 // it blocks on the monitor once again.
153 MOZ_ASSERT(mIdleThreads > 0);
154 --mIdleThreads;
155 return PopWorkLocked(aShutdownIdle);
158 Work PopWork(bool aShutdownIdle) {
159 MonitorAutoLock lock(mMonitor);
160 return PopWorkLocked(aShutdownIdle);
163 private:
164 /// Pops a new work item, blocking if necessary.
165 Work PopWorkLocked(bool aShutdownIdle) {
166 mMonitor.AssertCurrentThreadOwns();
168 TimeDuration timeout = mIdleTimeout;
169 do {
170 if (!mHighPriorityQueue.IsEmpty()) {
171 return PopWorkFromQueue(mHighPriorityQueue);
174 if (!mLowPriorityQueue.IsEmpty()) {
175 return PopWorkFromQueue(mLowPriorityQueue);
178 if (mShuttingDown) {
179 return CreateShutdownWork();
182 // Nothing to do; block until some work is available.
183 AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE);
184 if (!aShutdownIdle) {
185 // This thread was created before we hit the idle thread maximum. It
186 // will never shutdown until the process itself is torn down.
187 ++mIdleThreads;
188 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
189 mMonitor.Wait();
190 } else {
191 // This thread should shutdown if it is idle. If we have waited longer
192 // than the timeout period without having done any work, then we should
193 // shutdown the thread.
194 if (timeout.IsZero()) {
195 return CreateShutdownWork();
198 ++mIdleThreads;
199 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
201 TimeStamp now = TimeStamp::Now();
202 mMonitor.Wait(timeout);
203 TimeDuration delta = TimeStamp::Now() - now;
204 if (delta > timeout) {
205 timeout = 0;
206 } else if (timeout != TimeDuration::Forever()) {
207 timeout -= delta;
211 MOZ_ASSERT(mIdleThreads > 0);
212 --mIdleThreads;
213 } while (true);
216 ~DecodePoolImpl() {}
218 bool CreateThread();
220 Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue) {
221 Work work;
222 work.mType = Work::Type::TASK;
223 work.mTask = aQueue.PopLastElement();
225 return work;
228 Work CreateShutdownWork() const {
229 Work work;
230 work.mType = Work::Type::SHUTDOWN;
231 return work;
234 nsThreadPoolNaming mThreadNaming;
236 // mMonitor guards everything below.
237 mutable Monitor mMonitor;
238 nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
239 nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
240 nsTArray<nsCOMPtr<nsIThread>> mThreads;
241 TimeDuration mIdleTimeout;
242 uint8_t mMaxIdleThreads; // Maximum number of workers when idle.
243 uint8_t mAvailableThreads; // How many new threads can be created.
244 uint8_t mIdleThreads; // How many created threads are waiting.
245 bool mShuttingDown;
248 class DecodePoolWorker final : public Runnable {
249 public:
250 explicit DecodePoolWorker(DecodePoolImpl* aImpl, bool aShutdownIdle)
251 : Runnable("image::DecodePoolWorker"),
252 mImpl(aImpl),
253 mShutdownIdle(aShutdownIdle) {}
255 NS_IMETHOD Run() override {
256 MOZ_ASSERT(!NS_IsMainThread());
258 nsCOMPtr<nsIThread> thisThread;
259 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
261 Work work = mImpl->StartWork(mShutdownIdle);
262 do {
263 switch (work.mType) {
264 case Work::Type::TASK:
265 work.mTask->Run();
266 work.mTask = nullptr;
267 break;
269 case Work::Type::SHUTDOWN:
270 mImpl->ShutdownThread(thisThread, mShutdownIdle);
271 PROFILER_UNREGISTER_THREAD();
272 return NS_OK;
274 default:
275 MOZ_ASSERT_UNREACHABLE("Unknown work type");
278 work = mImpl->PopWork(mShutdownIdle);
279 } while (true);
281 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
282 return NS_OK;
285 private:
286 RefPtr<DecodePoolImpl> mImpl;
287 bool mShutdownIdle;
290 bool DecodePoolImpl::CreateThread() {
291 mMonitor.AssertCurrentThreadOwns();
292 MOZ_ASSERT(mAvailableThreads > 0);
294 bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads;
295 nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle);
296 nsCOMPtr<nsIThread> thread;
297 nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
298 getter_AddRefs(thread), worker,
299 nsIThreadManager::kThreadPoolStackSize);
300 if (NS_FAILED(rv) || !thread) {
301 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
302 return false;
305 mThreads.AppendElement(std::move(thread));
306 --mAvailableThreads;
307 ++mIdleThreads;
308 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
309 return true;
312 /* static */ void DecodePool::Initialize() {
313 MOZ_ASSERT(NS_IsMainThread());
314 sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
315 DecodePool::Singleton();
318 /* static */ DecodePool* DecodePool::Singleton() {
319 if (!sSingleton) {
320 MOZ_ASSERT(NS_IsMainThread());
321 sSingleton = new DecodePool();
322 ClearOnShutdown(&sSingleton);
325 return sSingleton;
328 /* static */ uint32_t DecodePool::NumberOfCores() { return sNumCores; }
330 DecodePool::DecodePool() : mMutex("image::DecodePool") {
331 // Determine the number of threads we want.
332 int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
333 uint32_t limit;
334 if (prefLimit <= 0) {
335 int32_t numCores = NumberOfCores();
336 if (numCores <= 1) {
337 limit = 1;
338 } else if (numCores == 2) {
339 // On an otherwise mostly idle system, having two image decoding threads
340 // doubles decoding performance, so it's worth doing on dual-core devices,
341 // even if under load we can't actually get that level of parallelism.
342 limit = 2;
343 } else {
344 limit = numCores - 1;
346 } else {
347 limit = static_cast<uint32_t>(prefLimit);
349 if (limit > 32) {
350 limit = 32;
352 // The parent process where there are content processes doesn't need as many
353 // threads for decoding images.
354 if (limit > 4 && XRE_IsE10sParentProcess()) {
355 limit = 4;
358 // The maximum number of idle threads allowed.
359 uint32_t idleLimit;
361 // The timeout period before shutting down idle threads.
362 int32_t prefIdleTimeout = gfxPrefs::ImageMTDecodingIdleTimeout();
363 TimeDuration idleTimeout;
364 if (prefIdleTimeout <= 0) {
365 idleTimeout = TimeDuration::Forever();
366 idleLimit = limit;
367 } else {
368 idleTimeout = TimeDuration::FromMilliseconds(prefIdleTimeout);
369 idleLimit = (limit + 1) / 2;
372 // Initialize the thread pool.
373 mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout);
375 // Initialize the I/O thread.
376 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
377 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
378 "Should successfully create image I/O thread");
380 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
381 if (obsSvc) {
382 obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
386 DecodePool::~DecodePool() {
387 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
390 NS_IMETHODIMP
391 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
392 MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
394 nsCOMPtr<nsIThread> ioThread;
397 MutexAutoLock lock(mMutex);
398 ioThread.swap(mIOThread);
401 mImpl->Shutdown();
403 if (ioThread) {
404 ioThread->Shutdown();
407 return NS_OK;
410 bool DecodePool::IsShuttingDown() const { return mImpl->IsShuttingDown(); }
412 void DecodePool::AsyncRun(IDecodingTask* aTask) {
413 MOZ_ASSERT(aTask);
414 mImpl->PushWork(aTask);
417 bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
418 const nsCString& aURI) {
419 MOZ_ASSERT(NS_IsMainThread());
420 MOZ_ASSERT(aTask);
422 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
423 GRAPHICS, aURI);
425 if (aTask->ShouldPreferSyncRun()) {
426 aTask->Run();
427 return true;
430 AsyncRun(aTask);
431 return false;
434 void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
435 const nsCString& aURI) {
436 MOZ_ASSERT(NS_IsMainThread());
437 MOZ_ASSERT(aTask);
439 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
440 GRAPHICS, aURI);
442 aTask->Run();
445 already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() {
446 MutexAutoLock threadPoolLock(mMutex);
447 nsCOMPtr<nsIEventTarget> target = mIOThread;
448 return target.forget();
451 } // namespace image
452 } // namespace mozilla