Bug 1635342 [wpt PR 23400] - [EventTiming] Add a test for auxclick, a=testonly
[gecko.git] / image / DecodePool.cpp
blob749cd76a03848e17096e2cb86695a4d657679aaa
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/SchedulerGroup.h"
14 #include "mozilla/StaticPrefs_image.h"
15 #include "mozilla/TimeStamp.h"
16 #include "nsCOMPtr.h"
17 #include "nsIObserverService.h"
18 #include "nsThreadManager.h"
19 #include "nsThreadUtils.h"
20 #include "nsXPCOMCIDInternal.h"
21 #include "prsystem.h"
23 #include "Decoder.h"
24 #include "IDecodingTask.h"
25 #include "RasterImage.h"
27 #if defined(XP_WIN)
28 # include <objbase.h>
29 #endif
31 using std::max;
32 using std::min;
34 namespace mozilla {
35 namespace image {
37 ///////////////////////////////////////////////////////////////////////////////
38 // DecodePool implementation.
39 ///////////////////////////////////////////////////////////////////////////////
41 /* static */
42 StaticRefPtr<DecodePool> DecodePool::sSingleton;
43 /* static */
44 uint32_t DecodePool::sNumCores = 0;
46 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
48 struct Work {
49 enum class Type { TASK, SHUTDOWN } mType;
51 RefPtr<IDecodingTask> mTask;
54 class DecodePoolImpl {
55 public:
56 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
57 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
59 DecodePoolImpl(uint8_t aMaxThreads, uint8_t aMaxIdleThreads,
60 TimeDuration aIdleTimeout)
61 : mMonitor("DecodePoolImpl"),
62 mThreads(aMaxThreads),
63 mIdleTimeout(aIdleTimeout),
64 mMaxIdleThreads(aMaxIdleThreads),
65 mAvailableThreads(aMaxThreads),
66 mIdleThreads(0),
67 mShuttingDown(false) {
68 MonitorAutoLock lock(mMonitor);
69 bool success = CreateThread();
70 MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!");
73 /// Shut down the provided decode pool thread.
74 void ShutdownThread(nsIThread* aThisThread, bool aShutdownIdle) {
75 bool removed = false;
78 // If this is an idle thread shutdown, then we need to remove it from the
79 // worker array. Process shutdown will move the entire array.
80 MonitorAutoLock lock(mMonitor);
81 if (!mShuttingDown) {
82 ++mAvailableThreads;
83 removed = mThreads.RemoveElement(aThisThread);
84 MOZ_ASSERT(aShutdownIdle);
85 MOZ_ASSERT(mAvailableThreads < mThreads.Capacity());
86 MOZ_ASSERT(removed);
90 // Threads have to be shut down from another thread, so we'll ask the
91 // main thread to do it for us, but only if we removed it from our thread
92 // pool explicitly. Otherwise we could try to shut down the same thread
93 // twice.
94 if (removed) {
95 SchedulerGroup::Dispatch(
96 TaskCategory::Other,
97 NewRunnableMethod("DecodePoolImpl::ShutdownThread", aThisThread,
98 &nsIThread::AsyncShutdown));
103 * Requests shutdown. New work items will be dropped on the floor, and all
104 * decode pool threads will be shut down once existing work items have been
105 * processed.
107 void Shutdown() {
108 nsTArray<nsCOMPtr<nsIThread>> threads;
111 MonitorAutoLock lock(mMonitor);
112 mShuttingDown = true;
113 mAvailableThreads = 0;
114 threads.SwapElements(mThreads);
115 mMonitor.NotifyAll();
118 for (uint32_t i = 0; i < threads.Length(); ++i) {
119 threads[i]->Shutdown();
123 bool IsShuttingDown() const {
124 MonitorAutoLock lock(mMonitor);
125 return mShuttingDown;
128 /// Pushes a new decode work item.
129 void PushWork(IDecodingTask* aTask) {
130 MOZ_ASSERT(aTask);
131 RefPtr<IDecodingTask> task(aTask);
133 MonitorAutoLock lock(mMonitor);
135 if (mShuttingDown) {
136 // Drop any new work on the floor if we're shutting down.
137 return;
140 if (task->Priority() == TaskPriority::eHigh) {
141 mHighPriorityQueue.AppendElement(std::move(task));
142 } else {
143 mLowPriorityQueue.AppendElement(std::move(task));
146 // If there are pending tasks, create more workers if and only if we have
147 // not exceeded the capacity, and any previously created workers are ready.
148 if (mAvailableThreads) {
149 size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length();
150 if (pending > mIdleThreads) {
151 CreateThread();
155 mMonitor.Notify();
158 Work StartWork(bool aShutdownIdle) {
159 MonitorAutoLock lock(mMonitor);
161 // The thread was already marked as idle when it was created. Once it gets
162 // its first work item, it is assumed it is busy performing that work until
163 // it blocks on the monitor once again.
164 MOZ_ASSERT(mIdleThreads > 0);
165 --mIdleThreads;
166 return PopWorkLocked(aShutdownIdle);
169 Work PopWork(bool aShutdownIdle) {
170 MonitorAutoLock lock(mMonitor);
171 return PopWorkLocked(aShutdownIdle);
174 private:
175 /// Pops a new work item, blocking if necessary.
176 Work PopWorkLocked(bool aShutdownIdle) {
177 mMonitor.AssertCurrentThreadOwns();
179 TimeDuration timeout = mIdleTimeout;
180 do {
181 if (!mHighPriorityQueue.IsEmpty()) {
182 return PopWorkFromQueue(mHighPriorityQueue);
185 if (!mLowPriorityQueue.IsEmpty()) {
186 return PopWorkFromQueue(mLowPriorityQueue);
189 if (mShuttingDown) {
190 return CreateShutdownWork();
193 // Nothing to do; block until some work is available.
194 AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE);
195 if (!aShutdownIdle) {
196 // This thread was created before we hit the idle thread maximum. It
197 // will never shutdown until the process itself is torn down.
198 ++mIdleThreads;
199 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
200 mMonitor.Wait();
201 } else {
202 // This thread should shutdown if it is idle. If we have waited longer
203 // than the timeout period without having done any work, then we should
204 // shutdown the thread.
205 if (timeout.IsZero()) {
206 return CreateShutdownWork();
209 ++mIdleThreads;
210 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
212 TimeStamp now = TimeStamp::Now();
213 mMonitor.Wait(timeout);
214 TimeDuration delta = TimeStamp::Now() - now;
215 if (delta > timeout) {
216 timeout = 0;
217 } else if (timeout != TimeDuration::Forever()) {
218 timeout -= delta;
222 MOZ_ASSERT(mIdleThreads > 0);
223 --mIdleThreads;
224 } while (true);
227 ~DecodePoolImpl() {}
229 bool CreateThread();
231 Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue) {
232 Work work;
233 work.mType = Work::Type::TASK;
234 work.mTask = aQueue.PopLastElement();
236 return work;
239 Work CreateShutdownWork() const {
240 Work work;
241 work.mType = Work::Type::SHUTDOWN;
242 return work;
245 nsThreadPoolNaming mThreadNaming;
247 // mMonitor guards everything below.
248 mutable Monitor mMonitor;
249 nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
250 nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
251 nsTArray<nsCOMPtr<nsIThread>> mThreads;
252 TimeDuration mIdleTimeout;
253 uint8_t mMaxIdleThreads; // Maximum number of workers when idle.
254 uint8_t mAvailableThreads; // How many new threads can be created.
255 uint8_t mIdleThreads; // How many created threads are waiting.
256 bool mShuttingDown;
259 class DecodePoolWorker final : public Runnable {
260 public:
261 explicit DecodePoolWorker(DecodePoolImpl* aImpl, bool aShutdownIdle)
262 : Runnable("image::DecodePoolWorker"),
263 mImpl(aImpl),
264 mShutdownIdle(aShutdownIdle) {}
266 NS_IMETHOD Run() override {
267 MOZ_ASSERT(!NS_IsMainThread());
269 nsCOMPtr<nsIThread> thisThread;
270 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
272 Work work = mImpl->StartWork(mShutdownIdle);
273 do {
274 switch (work.mType) {
275 case Work::Type::TASK:
276 work.mTask->Run();
277 work.mTask = nullptr;
278 break;
280 case Work::Type::SHUTDOWN:
281 mImpl->ShutdownThread(thisThread, mShutdownIdle);
282 PROFILER_UNREGISTER_THREAD();
283 return NS_OK;
285 default:
286 MOZ_ASSERT_UNREACHABLE("Unknown work type");
289 work = mImpl->PopWork(mShutdownIdle);
290 } while (true);
292 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
293 return NS_OK;
296 private:
297 RefPtr<DecodePoolImpl> mImpl;
298 bool mShutdownIdle;
301 bool DecodePoolImpl::CreateThread() {
302 mMonitor.AssertCurrentThreadOwns();
303 MOZ_ASSERT(mAvailableThreads > 0);
305 bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads;
306 nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle);
307 nsCOMPtr<nsIThread> thread;
308 nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
309 getter_AddRefs(thread), worker,
310 nsIThreadManager::kThreadPoolStackSize);
311 if (NS_FAILED(rv) || !thread) {
312 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
313 return false;
315 thread->SetNameForWakeupTelemetry(NS_LITERAL_CSTRING("ImgDecoder (all)"));
317 mThreads.AppendElement(std::move(thread));
318 --mAvailableThreads;
319 ++mIdleThreads;
320 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
321 return true;
324 /* static */
325 void DecodePool::Initialize() {
326 MOZ_ASSERT(NS_IsMainThread());
327 sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
328 DecodePool::Singleton();
331 /* static */
332 DecodePool* DecodePool::Singleton() {
333 if (!sSingleton) {
334 MOZ_ASSERT(NS_IsMainThread());
335 sSingleton = new DecodePool();
336 ClearOnShutdown(&sSingleton);
339 return sSingleton;
342 /* static */
343 uint32_t DecodePool::NumberOfCores() { return sNumCores; }
345 #if defined(XP_WIN)
346 class IOThreadIniter final : public Runnable {
347 public:
348 explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
350 NS_IMETHOD Run() override {
351 MOZ_ASSERT(!NS_IsMainThread());
353 CoInitialize(nullptr);
355 return NS_OK;
358 #endif
360 DecodePool::DecodePool() : mMutex("image::IOThread") {
361 // Determine the number of threads we want.
362 int32_t prefLimit =
363 StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
364 uint32_t limit;
365 if (prefLimit <= 0) {
366 int32_t numCores = NumberOfCores();
367 if (numCores <= 1) {
368 limit = 1;
369 } else if (numCores == 2) {
370 // On an otherwise mostly idle system, having two image decoding threads
371 // doubles decoding performance, so it's worth doing on dual-core devices,
372 // even if under load we can't actually get that level of parallelism.
373 limit = 2;
374 } else {
375 limit = numCores - 1;
377 } else {
378 limit = static_cast<uint32_t>(prefLimit);
380 if (limit > 32) {
381 limit = 32;
383 // The parent process where there are content processes doesn't need as many
384 // threads for decoding images.
385 if (limit > 4 && XRE_IsE10sParentProcess()) {
386 limit = 4;
389 // The maximum number of idle threads allowed.
390 uint32_t idleLimit;
392 // The timeout period before shutting down idle threads.
393 int32_t prefIdleTimeout =
394 StaticPrefs::image_multithreaded_decoding_idle_timeout_AtStartup();
395 TimeDuration idleTimeout;
396 if (prefIdleTimeout <= 0) {
397 idleTimeout = TimeDuration::Forever();
398 idleLimit = limit;
399 } else {
400 idleTimeout = TimeDuration::FromMilliseconds(prefIdleTimeout);
401 idleLimit = (limit + 1) / 2;
404 // Initialize the thread pool.
405 mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout);
407 // Initialize the I/O thread.
408 #if defined(XP_WIN)
409 // On Windows we use the io thread to get icons from the system. Any thread
410 // that makes system calls needs to call CoInitialize. And these system calls
411 // (SHGetFileInfo) should only be called from one thread at a time, in case
412 // we ever create more than on io thread.
413 nsCOMPtr<nsIRunnable> initer = new IOThreadIniter();
414 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread), initer);
415 #else
416 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
417 #endif
418 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
419 "Should successfully create image I/O thread");
421 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
422 if (obsSvc) {
423 obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
427 DecodePool::~DecodePool() {
428 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
431 NS_IMETHODIMP
432 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
433 MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
435 nsCOMPtr<nsIThread> ioThread;
438 MutexAutoLock lock(mMutex);
439 ioThread.swap(mIOThread);
442 mImpl->Shutdown();
444 if (ioThread) {
445 ioThread->Shutdown();
448 return NS_OK;
451 bool DecodePool::IsShuttingDown() const { return mImpl->IsShuttingDown(); }
453 void DecodePool::AsyncRun(IDecodingTask* aTask) {
454 MOZ_ASSERT(aTask);
455 mImpl->PushWork(aTask);
458 bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
459 const nsCString& aURI) {
460 MOZ_ASSERT(NS_IsMainThread());
461 MOZ_ASSERT(aTask);
463 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
464 GRAPHICS, aURI);
466 if (aTask->ShouldPreferSyncRun()) {
467 aTask->Run();
468 return true;
471 AsyncRun(aTask);
472 return false;
475 void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
476 const nsCString& aURI) {
477 MOZ_ASSERT(NS_IsMainThread());
478 MOZ_ASSERT(aTask);
480 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
481 GRAPHICS, aURI);
483 aTask->Run();
486 already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() {
487 MutexAutoLock threadPoolLock(mMutex);
488 nsCOMPtr<nsIEventTarget> target = mIOThread;
489 return target.forget();
492 } // namespace image
493 } // namespace mozilla