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/StaticPrefs_image.h"
14 #include "mozilla/TimeStamp.h"
16 #include "nsIObserverService.h"
17 #include "nsThreadManager.h"
18 #include "nsThreadUtils.h"
19 #include "nsXPCOMCIDInternal.h"
23 #include "IDecodingTask.h"
24 #include "RasterImage.h"
36 ///////////////////////////////////////////////////////////////////////////////
37 // DecodePool implementation.
38 ///////////////////////////////////////////////////////////////////////////////
41 StaticRefPtr
<DecodePool
> DecodePool::sSingleton
;
43 uint32_t DecodePool::sNumCores
= 0;
45 NS_IMPL_ISUPPORTS(DecodePool
, nsIObserver
)
48 enum class Type
{ TASK
, SHUTDOWN
} mType
;
50 RefPtr
<IDecodingTask
> mTask
;
53 class DecodePoolImpl
{
55 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl
)
56 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl
)
58 DecodePoolImpl(uint8_t aMaxThreads
, uint8_t aMaxIdleThreads
,
59 TimeDuration aIdleTimeout
)
60 : mMonitor("DecodePoolImpl"),
61 mThreads(aMaxThreads
),
62 mIdleTimeout(aIdleTimeout
),
63 mMaxIdleThreads(aMaxIdleThreads
),
64 mAvailableThreads(aMaxThreads
),
66 mShuttingDown(false) {
67 MonitorAutoLock
lock(mMonitor
);
68 bool success
= CreateThread();
69 MOZ_RELEASE_ASSERT(success
, "Must create first image decoder thread!");
72 /// Shut down the provided decode pool thread.
73 void ShutdownThread(nsIThread
* aThisThread
, bool aShutdownIdle
) {
77 // If this is an idle thread shutdown, then we need to remove it from the
78 // worker array. Process shutdown will move the entire array.
79 MonitorAutoLock
lock(mMonitor
);
82 removed
= mThreads
.RemoveElement(aThisThread
);
83 MOZ_ASSERT(aShutdownIdle
);
84 MOZ_ASSERT(mAvailableThreads
< mThreads
.Capacity());
89 // Threads have to be shut down from another thread, so we'll ask the
90 // main thread to do it for us, but only if we removed it from our thread
91 // pool explicitly. Otherwise we could try to shut down the same thread
94 SystemGroup::Dispatch(
96 NewRunnableMethod("DecodePoolImpl::ShutdownThread", aThisThread
,
97 &nsIThread::AsyncShutdown
));
102 * Requests shutdown. New work items will be dropped on the floor, and all
103 * decode pool threads will be shut down once existing work items have been
107 nsTArray
<nsCOMPtr
<nsIThread
>> threads
;
110 MonitorAutoLock
lock(mMonitor
);
111 mShuttingDown
= true;
112 mAvailableThreads
= 0;
113 threads
.SwapElements(mThreads
);
114 mMonitor
.NotifyAll();
117 for (uint32_t i
= 0; i
< threads
.Length(); ++i
) {
118 threads
[i
]->Shutdown();
122 bool IsShuttingDown() const {
123 MonitorAutoLock
lock(mMonitor
);
124 return mShuttingDown
;
127 /// Pushes a new decode work item.
128 void PushWork(IDecodingTask
* aTask
) {
130 RefPtr
<IDecodingTask
> task(aTask
);
132 MonitorAutoLock
lock(mMonitor
);
135 // Drop any new work on the floor if we're shutting down.
139 if (task
->Priority() == TaskPriority::eHigh
) {
140 mHighPriorityQueue
.AppendElement(std::move(task
));
142 mLowPriorityQueue
.AppendElement(std::move(task
));
145 // If there are pending tasks, create more workers if and only if we have
146 // not exceeded the capacity, and any previously created workers are ready.
147 if (mAvailableThreads
) {
148 size_t pending
= mHighPriorityQueue
.Length() + mLowPriorityQueue
.Length();
149 if (pending
> mIdleThreads
) {
157 Work
StartWork(bool aShutdownIdle
) {
158 MonitorAutoLock
lock(mMonitor
);
160 // The thread was already marked as idle when it was created. Once it gets
161 // its first work item, it is assumed it is busy performing that work until
162 // it blocks on the monitor once again.
163 MOZ_ASSERT(mIdleThreads
> 0);
165 return PopWorkLocked(aShutdownIdle
);
168 Work
PopWork(bool aShutdownIdle
) {
169 MonitorAutoLock
lock(mMonitor
);
170 return PopWorkLocked(aShutdownIdle
);
174 /// Pops a new work item, blocking if necessary.
175 Work
PopWorkLocked(bool aShutdownIdle
) {
176 mMonitor
.AssertCurrentThreadOwns();
178 TimeDuration timeout
= mIdleTimeout
;
180 if (!mHighPriorityQueue
.IsEmpty()) {
181 return PopWorkFromQueue(mHighPriorityQueue
);
184 if (!mLowPriorityQueue
.IsEmpty()) {
185 return PopWorkFromQueue(mLowPriorityQueue
);
189 return CreateShutdownWork();
192 // Nothing to do; block until some work is available.
193 AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE
);
194 if (!aShutdownIdle
) {
195 // This thread was created before we hit the idle thread maximum. It
196 // will never shutdown until the process itself is torn down.
198 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
201 // This thread should shutdown if it is idle. If we have waited longer
202 // than the timeout period without having done any work, then we should
203 // shutdown the thread.
204 if (timeout
.IsZero()) {
205 return CreateShutdownWork();
209 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
211 TimeStamp now
= TimeStamp::Now();
212 mMonitor
.Wait(timeout
);
213 TimeDuration delta
= TimeStamp::Now() - now
;
214 if (delta
> timeout
) {
216 } else if (timeout
!= TimeDuration::Forever()) {
221 MOZ_ASSERT(mIdleThreads
> 0);
230 Work
PopWorkFromQueue(nsTArray
<RefPtr
<IDecodingTask
>>& aQueue
) {
232 work
.mType
= Work::Type::TASK
;
233 work
.mTask
= aQueue
.PopLastElement();
238 Work
CreateShutdownWork() const {
240 work
.mType
= Work::Type::SHUTDOWN
;
244 nsThreadPoolNaming mThreadNaming
;
246 // mMonitor guards everything below.
247 mutable Monitor mMonitor
;
248 nsTArray
<RefPtr
<IDecodingTask
>> mHighPriorityQueue
;
249 nsTArray
<RefPtr
<IDecodingTask
>> mLowPriorityQueue
;
250 nsTArray
<nsCOMPtr
<nsIThread
>> mThreads
;
251 TimeDuration mIdleTimeout
;
252 uint8_t mMaxIdleThreads
; // Maximum number of workers when idle.
253 uint8_t mAvailableThreads
; // How many new threads can be created.
254 uint8_t mIdleThreads
; // How many created threads are waiting.
258 class DecodePoolWorker final
: public Runnable
{
260 explicit DecodePoolWorker(DecodePoolImpl
* aImpl
, bool aShutdownIdle
)
261 : Runnable("image::DecodePoolWorker"),
263 mShutdownIdle(aShutdownIdle
) {}
265 NS_IMETHOD
Run() override
{
266 MOZ_ASSERT(!NS_IsMainThread());
268 nsCOMPtr
<nsIThread
> thisThread
;
269 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread
));
271 Work work
= mImpl
->StartWork(mShutdownIdle
);
273 switch (work
.mType
) {
274 case Work::Type::TASK
:
276 work
.mTask
= nullptr;
279 case Work::Type::SHUTDOWN
:
280 mImpl
->ShutdownThread(thisThread
, mShutdownIdle
);
281 PROFILER_UNREGISTER_THREAD();
285 MOZ_ASSERT_UNREACHABLE("Unknown work type");
288 work
= mImpl
->PopWork(mShutdownIdle
);
291 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
296 RefPtr
<DecodePoolImpl
> mImpl
;
300 bool DecodePoolImpl::CreateThread() {
301 mMonitor
.AssertCurrentThreadOwns();
302 MOZ_ASSERT(mAvailableThreads
> 0);
304 bool shutdownIdle
= mThreads
.Length() >= mMaxIdleThreads
;
305 nsCOMPtr
<nsIRunnable
> worker
= new DecodePoolWorker(this, shutdownIdle
);
306 nsCOMPtr
<nsIThread
> thread
;
307 nsresult rv
= NS_NewNamedThread(mThreadNaming
.GetNextThreadName("ImgDecoder"),
308 getter_AddRefs(thread
), worker
,
309 nsIThreadManager::kThreadPoolStackSize
);
310 if (NS_FAILED(rv
) || !thread
) {
311 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
314 thread
->SetNameForWakeupTelemetry(NS_LITERAL_CSTRING("ImgDecoder (all)"));
316 mThreads
.AppendElement(std::move(thread
));
319 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
324 void DecodePool::Initialize() {
325 MOZ_ASSERT(NS_IsMainThread());
326 sNumCores
= max
<int32_t>(PR_GetNumberOfProcessors(), 1);
327 DecodePool::Singleton();
331 DecodePool
* DecodePool::Singleton() {
333 MOZ_ASSERT(NS_IsMainThread());
334 sSingleton
= new DecodePool();
335 ClearOnShutdown(&sSingleton
);
342 uint32_t DecodePool::NumberOfCores() { return sNumCores
; }
345 class IOThreadIniter final
: public Runnable
{
347 explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
349 NS_IMETHOD
Run() override
{
350 MOZ_ASSERT(!NS_IsMainThread());
352 CoInitialize(nullptr);
359 DecodePool::DecodePool() : mMutex("image::IOThread") {
360 // Determine the number of threads we want.
362 StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
364 if (prefLimit
<= 0) {
365 int32_t numCores
= NumberOfCores();
368 } else if (numCores
== 2) {
369 // On an otherwise mostly idle system, having two image decoding threads
370 // doubles decoding performance, so it's worth doing on dual-core devices,
371 // even if under load we can't actually get that level of parallelism.
374 limit
= numCores
- 1;
377 limit
= static_cast<uint32_t>(prefLimit
);
382 // The parent process where there are content processes doesn't need as many
383 // threads for decoding images.
384 if (limit
> 4 && XRE_IsE10sParentProcess()) {
388 // The maximum number of idle threads allowed.
391 // The timeout period before shutting down idle threads.
392 int32_t prefIdleTimeout
=
393 StaticPrefs::image_multithreaded_decoding_idle_timeout_AtStartup();
394 TimeDuration idleTimeout
;
395 if (prefIdleTimeout
<= 0) {
396 idleTimeout
= TimeDuration::Forever();
399 idleTimeout
= TimeDuration::FromMilliseconds(prefIdleTimeout
);
400 idleLimit
= (limit
+ 1) / 2;
403 // Initialize the thread pool.
404 mImpl
= new DecodePoolImpl(limit
, idleLimit
, idleTimeout
);
406 // Initialize the I/O thread.
408 // On Windows we use the io thread to get icons from the system. Any thread
409 // that makes system calls needs to call CoInitialize. And these system calls
410 // (SHGetFileInfo) should only be called from one thread at a time, in case
411 // we ever create more than on io thread.
412 nsCOMPtr
<nsIRunnable
> initer
= new IOThreadIniter();
413 nsresult rv
= NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread
), initer
);
415 nsresult rv
= NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread
));
417 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
) && mIOThread
,
418 "Should successfully create image I/O thread");
420 nsCOMPtr
<nsIObserverService
> obsSvc
= services::GetObserverService();
422 obsSvc
->AddObserver(this, "xpcom-shutdown-threads", false);
426 DecodePool::~DecodePool() {
427 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
431 DecodePool::Observe(nsISupports
*, const char* aTopic
, const char16_t
*) {
432 MOZ_ASSERT(strcmp(aTopic
, "xpcom-shutdown-threads") == 0, "Unexpected topic");
434 nsCOMPtr
<nsIThread
> ioThread
;
437 MutexAutoLock
lock(mMutex
);
438 ioThread
.swap(mIOThread
);
444 ioThread
->Shutdown();
450 bool DecodePool::IsShuttingDown() const { return mImpl
->IsShuttingDown(); }
452 void DecodePool::AsyncRun(IDecodingTask
* aTask
) {
454 mImpl
->PushWork(aTask
);
457 bool DecodePool::SyncRunIfPreferred(IDecodingTask
* aTask
,
458 const nsCString
& aURI
) {
459 MOZ_ASSERT(NS_IsMainThread());
462 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
465 if (aTask
->ShouldPreferSyncRun()) {
474 void DecodePool::SyncRunIfPossible(IDecodingTask
* aTask
,
475 const nsCString
& aURI
) {
476 MOZ_ASSERT(NS_IsMainThread());
479 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
485 already_AddRefed
<nsIEventTarget
> DecodePool::GetIOEventTarget() {
486 MutexAutoLock
threadPoolLock(mMutex
);
487 nsCOMPtr
<nsIEventTarget
> target
= mIOThread
;
488 return target
.forget();
492 } // namespace mozilla