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"
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"
17 #include "nsIObserverService.h"
18 #include "nsThreadManager.h"
19 #include "nsThreadUtils.h"
20 #include "nsXPCOMCIDInternal.h"
24 #include "IDecodingTask.h"
25 #include "RasterImage.h"
37 ///////////////////////////////////////////////////////////////////////////////
38 // DecodePool implementation.
39 ///////////////////////////////////////////////////////////////////////////////
42 StaticRefPtr
<DecodePool
> DecodePool::sSingleton
;
44 uint32_t DecodePool::sNumCores
= 0;
46 NS_IMPL_ISUPPORTS(DecodePool
, nsIObserver
)
49 enum class Type
{ TASK
, SHUTDOWN
} mType
;
51 RefPtr
<IDecodingTask
> mTask
;
54 class DecodePoolImpl
{
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
),
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
) {
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
);
83 removed
= mThreads
.RemoveElement(aThisThread
);
84 MOZ_ASSERT(aShutdownIdle
);
85 MOZ_ASSERT(mAvailableThreads
< mThreads
.Capacity());
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
95 SchedulerGroup::Dispatch(
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
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
) {
131 RefPtr
<IDecodingTask
> task(aTask
);
133 MonitorAutoLock
lock(mMonitor
);
136 // Drop any new work on the floor if we're shutting down.
140 if (task
->Priority() == TaskPriority::eHigh
) {
141 mHighPriorityQueue
.AppendElement(std::move(task
));
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
) {
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);
166 return PopWorkLocked(aShutdownIdle
);
169 Work
PopWork(bool aShutdownIdle
) {
170 MonitorAutoLock
lock(mMonitor
);
171 return PopWorkLocked(aShutdownIdle
);
175 /// Pops a new work item, blocking if necessary.
176 Work
PopWorkLocked(bool aShutdownIdle
) {
177 mMonitor
.AssertCurrentThreadOwns();
179 TimeDuration timeout
= mIdleTimeout
;
181 if (!mHighPriorityQueue
.IsEmpty()) {
182 return PopWorkFromQueue(mHighPriorityQueue
);
185 if (!mLowPriorityQueue
.IsEmpty()) {
186 return PopWorkFromQueue(mLowPriorityQueue
);
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.
199 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
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();
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
) {
217 } else if (timeout
!= TimeDuration::Forever()) {
222 MOZ_ASSERT(mIdleThreads
> 0);
231 Work
PopWorkFromQueue(nsTArray
<RefPtr
<IDecodingTask
>>& aQueue
) {
233 work
.mType
= Work::Type::TASK
;
234 work
.mTask
= aQueue
.PopLastElement();
239 Work
CreateShutdownWork() const {
241 work
.mType
= Work::Type::SHUTDOWN
;
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.
259 class DecodePoolWorker final
: public Runnable
{
261 explicit DecodePoolWorker(DecodePoolImpl
* aImpl
, bool aShutdownIdle
)
262 : Runnable("image::DecodePoolWorker"),
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
);
274 switch (work
.mType
) {
275 case Work::Type::TASK
:
277 work
.mTask
= nullptr;
280 case Work::Type::SHUTDOWN
:
281 mImpl
->ShutdownThread(thisThread
, mShutdownIdle
);
282 PROFILER_UNREGISTER_THREAD();
286 MOZ_ASSERT_UNREACHABLE("Unknown work type");
289 work
= mImpl
->PopWork(mShutdownIdle
);
292 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
297 RefPtr
<DecodePoolImpl
> mImpl
;
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
;
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");
321 thread
->SetNameForWakeupTelemetry("ImgDecoder (all)"_ns
);
323 mThreads
.AppendElement(std::move(thread
));
326 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
331 void DecodePool::Initialize() {
332 MOZ_ASSERT(NS_IsMainThread());
333 sNumCores
= max
<int32_t>(PR_GetNumberOfProcessors(), 1);
334 DecodePool::Singleton();
338 DecodePool
* DecodePool::Singleton() {
340 MOZ_ASSERT(NS_IsMainThread());
341 sSingleton
= new DecodePool();
342 ClearOnShutdown(&sSingleton
);
349 uint32_t DecodePool::NumberOfCores() { return sNumCores
; }
352 class IOThreadIniter final
: public Runnable
{
354 explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
356 NS_IMETHOD
Run() override
{
357 MOZ_ASSERT(!NS_IsMainThread());
359 CoInitialize(nullptr);
366 DecodePool::DecodePool() : mMutex("image::IOThread") {
367 // Determine the number of threads we want.
369 StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
371 if (prefLimit
<= 0) {
372 int32_t numCores
= NumberOfCores();
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.
381 limit
= numCores
- 1;
384 limit
= static_cast<uint32_t>(prefLimit
);
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()) {
395 // The maximum number of idle threads allowed.
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();
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.
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
);
422 nsresult rv
= NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread
));
424 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
) && mIOThread
,
425 "Should successfully create image I/O thread");
427 nsCOMPtr
<nsIObserverService
> obsSvc
= services::GetObserverService();
429 obsSvc
->AddObserver(this, "xpcom-shutdown-threads", false);
433 DecodePool::~DecodePool() {
434 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
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
);
451 ioThread
->Shutdown();
457 bool DecodePool::IsShuttingDown() const { return mImpl
->IsShuttingDown(); }
459 void DecodePool::AsyncRun(IDecodingTask
* aTask
) {
461 mImpl
->PushWork(aTask
);
464 bool DecodePool::SyncRunIfPreferred(IDecodingTask
* aTask
,
465 const nsCString
& aURI
) {
466 MOZ_ASSERT(NS_IsMainThread());
469 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
472 if (aTask
->ShouldPreferSyncRun()) {
481 void DecodePool::SyncRunIfPossible(IDecodingTask
* aTask
,
482 const nsCString
& aURI
) {
483 MOZ_ASSERT(NS_IsMainThread());
486 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
492 already_AddRefed
<nsIEventTarget
> DecodePool::GetIOEventTarget() {
493 MutexAutoLock
threadPoolLock(mMutex
);
494 nsCOMPtr
<nsIEventTarget
> target
= mIOThread
;
495 return target
.forget();
499 } // namespace mozilla