Bug 1667155 [wpt PR 25777] - [AspectRatio] Fix bug in flex-aspect-ratio-024 test...
[gecko.git] / image / DecodePool.cpp
blob657f70f70dadf39ddb6cca5cd600471a9510453e
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 = std::move(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 /// dav1d (used for AVIF decoding) crashes when decoding some images if the
302 /// stack is too small. See related issue for AV1 decoding: bug 1474684.
303 static constexpr uint32_t DECODE_POOL_STACK_SIZE =
304 std::max(nsIThreadManager::kThreadPoolStackSize, 512u * 1024u);
306 bool DecodePoolImpl::CreateThread() {
307 mMonitor.AssertCurrentThreadOwns();
308 MOZ_ASSERT(mAvailableThreads > 0);
310 bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads;
311 nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle);
312 nsCOMPtr<nsIThread> thread;
313 nsresult rv =
314 NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
315 getter_AddRefs(thread), worker, DECODE_POOL_STACK_SIZE);
317 if (NS_FAILED(rv) || !thread) {
318 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
319 return false;
321 thread->SetNameForWakeupTelemetry("ImgDecoder (all)"_ns);
323 mThreads.AppendElement(std::move(thread));
324 --mAvailableThreads;
325 ++mIdleThreads;
326 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
327 return true;
330 /* static */
331 void DecodePool::Initialize() {
332 MOZ_ASSERT(NS_IsMainThread());
333 sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
334 DecodePool::Singleton();
337 /* static */
338 DecodePool* DecodePool::Singleton() {
339 if (!sSingleton) {
340 MOZ_ASSERT(NS_IsMainThread());
341 sSingleton = new DecodePool();
342 ClearOnShutdown(&sSingleton);
345 return sSingleton;
348 /* static */
349 uint32_t DecodePool::NumberOfCores() { return sNumCores; }
351 #if defined(XP_WIN)
352 class IOThreadIniter final : public Runnable {
353 public:
354 explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
356 NS_IMETHOD Run() override {
357 MOZ_ASSERT(!NS_IsMainThread());
359 CoInitialize(nullptr);
361 return NS_OK;
364 #endif
366 DecodePool::DecodePool() : mMutex("image::IOThread") {
367 // Determine the number of threads we want.
368 int32_t prefLimit =
369 StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
370 uint32_t limit;
371 if (prefLimit <= 0) {
372 int32_t numCores = NumberOfCores();
373 if (numCores <= 1) {
374 limit = 1;
375 } else if (numCores == 2) {
376 // On an otherwise mostly idle system, having two image decoding threads
377 // doubles decoding performance, so it's worth doing on dual-core devices,
378 // even if under load we can't actually get that level of parallelism.
379 limit = 2;
380 } else {
381 limit = numCores - 1;
383 } else {
384 limit = static_cast<uint32_t>(prefLimit);
386 if (limit > 32) {
387 limit = 32;
389 // The parent process where there are content processes doesn't need as many
390 // threads for decoding images.
391 if (limit > 4 && XRE_IsE10sParentProcess()) {
392 limit = 4;
395 // The maximum number of idle threads allowed.
396 uint32_t idleLimit;
398 // The timeout period before shutting down idle threads.
399 int32_t prefIdleTimeout =
400 StaticPrefs::image_multithreaded_decoding_idle_timeout_AtStartup();
401 TimeDuration idleTimeout;
402 if (prefIdleTimeout <= 0) {
403 idleTimeout = TimeDuration::Forever();
404 idleLimit = limit;
405 } else {
406 idleTimeout = TimeDuration::FromMilliseconds(prefIdleTimeout);
407 idleLimit = (limit + 1) / 2;
410 // Initialize the thread pool.
411 mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout);
413 // Initialize the I/O thread.
414 #if defined(XP_WIN)
415 // On Windows we use the io thread to get icons from the system. Any thread
416 // that makes system calls needs to call CoInitialize. And these system calls
417 // (SHGetFileInfo) should only be called from one thread at a time, in case
418 // we ever create more than on io thread.
419 nsCOMPtr<nsIRunnable> initer = new IOThreadIniter();
420 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread), initer);
421 #else
422 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
423 #endif
424 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
425 "Should successfully create image I/O thread");
427 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
428 if (obsSvc) {
429 obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
433 DecodePool::~DecodePool() {
434 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
437 NS_IMETHODIMP
438 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
439 MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
441 nsCOMPtr<nsIThread> ioThread;
444 MutexAutoLock lock(mMutex);
445 ioThread.swap(mIOThread);
448 mImpl->Shutdown();
450 if (ioThread) {
451 ioThread->Shutdown();
454 return NS_OK;
457 bool DecodePool::IsShuttingDown() const { return mImpl->IsShuttingDown(); }
459 void DecodePool::AsyncRun(IDecodingTask* aTask) {
460 MOZ_ASSERT(aTask);
461 mImpl->PushWork(aTask);
464 bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
465 const nsCString& aURI) {
466 MOZ_ASSERT(NS_IsMainThread());
467 MOZ_ASSERT(aTask);
469 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
470 GRAPHICS, aURI);
472 if (aTask->ShouldPreferSyncRun()) {
473 aTask->Run();
474 return true;
477 AsyncRun(aTask);
478 return false;
481 void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
482 const nsCString& aURI) {
483 MOZ_ASSERT(NS_IsMainThread());
484 MOZ_ASSERT(aTask);
486 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
487 GRAPHICS, aURI);
489 aTask->Run();
492 already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() {
493 MutexAutoLock threadPoolLock(mMutex);
494 nsCOMPtr<nsIEventTarget> target = mIOThread;
495 return target.forget();
498 } // namespace image
499 } // namespace mozilla