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 "nsIThreadPool.h"
18 #include "nsThreadManager.h"
19 #include "nsThreadUtils.h"
20 #include "nsXPCOMCIDInternal.h"
22 #include "nsIXULRuntime.h"
25 #include "IDecodingTask.h"
26 #include "RasterImage.h"
38 ///////////////////////////////////////////////////////////////////////////////
39 // DecodePool implementation.
40 ///////////////////////////////////////////////////////////////////////////////
43 StaticRefPtr
<DecodePool
> DecodePool::sSingleton
;
45 uint32_t DecodePool::sNumCores
= 0;
47 NS_IMPL_ISUPPORTS(DecodePool
, nsIObserver
)
50 enum class Type
{ TASK
, SHUTDOWN
} mType
;
52 RefPtr
<IDecodingTask
> mTask
;
55 class DecodePoolImpl
{
57 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl
)
58 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl
)
60 DecodePoolImpl(uint8_t aMaxThreads
, uint8_t aMaxIdleThreads
,
61 TimeDuration aIdleTimeout
)
62 : mMonitor("DecodePoolImpl"),
63 mThreads(aMaxThreads
),
64 mIdleTimeout(aIdleTimeout
),
65 mMaxIdleThreads(aMaxIdleThreads
),
66 mAvailableThreads(aMaxThreads
),
68 mShuttingDown(false) {
69 MonitorAutoLock
lock(mMonitor
);
70 bool success
= CreateThread();
71 MOZ_RELEASE_ASSERT(success
, "Must create first image decoder thread!");
74 /// Shut down the provided decode pool thread.
75 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 DebugOnly
<bool> 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.
91 SystemGroup::Dispatch(
93 NewRunnableMethod("DecodePoolImpl::ShutdownThread", aThisThread
,
94 &nsIThread::AsyncShutdown
));
98 * Requests shutdown. New work items will be dropped on the floor, and all
99 * decode pool threads will be shut down once existing work items have been
103 nsTArray
<nsCOMPtr
<nsIThread
>> threads
;
106 MonitorAutoLock
lock(mMonitor
);
107 mShuttingDown
= true;
108 mAvailableThreads
= 0;
109 threads
.SwapElements(mThreads
);
110 mMonitor
.NotifyAll();
113 for (uint32_t i
= 0; i
< threads
.Length(); ++i
) {
114 threads
[i
]->Shutdown();
118 bool IsShuttingDown() const {
119 MonitorAutoLock
lock(mMonitor
);
120 return mShuttingDown
;
123 /// Pushes a new decode work item.
124 void PushWork(IDecodingTask
* aTask
) {
126 RefPtr
<IDecodingTask
> task(aTask
);
128 MonitorAutoLock
lock(mMonitor
);
131 // Drop any new work on the floor if we're shutting down.
135 if (task
->Priority() == TaskPriority::eHigh
) {
136 mHighPriorityQueue
.AppendElement(std::move(task
));
138 mLowPriorityQueue
.AppendElement(std::move(task
));
141 // If there are pending tasks, create more workers if and only if we have
142 // not exceeded the capacity, and any previously created workers are ready.
143 if (mAvailableThreads
) {
144 size_t pending
= mHighPriorityQueue
.Length() + mLowPriorityQueue
.Length();
145 if (pending
> mIdleThreads
) {
153 Work
StartWork(bool aShutdownIdle
) {
154 MonitorAutoLock
lock(mMonitor
);
156 // The thread was already marked as idle when it was created. Once it gets
157 // its first work item, it is assumed it is busy performing that work until
158 // it blocks on the monitor once again.
159 MOZ_ASSERT(mIdleThreads
> 0);
161 return PopWorkLocked(aShutdownIdle
);
164 Work
PopWork(bool aShutdownIdle
) {
165 MonitorAutoLock
lock(mMonitor
);
166 return PopWorkLocked(aShutdownIdle
);
170 /// Pops a new work item, blocking if necessary.
171 Work
PopWorkLocked(bool aShutdownIdle
) {
172 mMonitor
.AssertCurrentThreadOwns();
174 TimeDuration timeout
= mIdleTimeout
;
176 if (!mHighPriorityQueue
.IsEmpty()) {
177 return PopWorkFromQueue(mHighPriorityQueue
);
180 if (!mLowPriorityQueue
.IsEmpty()) {
181 return PopWorkFromQueue(mLowPriorityQueue
);
185 return CreateShutdownWork();
188 // Nothing to do; block until some work is available.
189 AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE
);
190 if (!aShutdownIdle
) {
191 // This thread was created before we hit the idle thread maximum. It
192 // will never shutdown until the process itself is torn down.
194 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
197 // This thread should shutdown if it is idle. If we have waited longer
198 // than the timeout period without having done any work, then we should
199 // shutdown the thread.
200 if (timeout
.IsZero()) {
201 return CreateShutdownWork();
205 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
207 TimeStamp now
= TimeStamp::Now();
208 mMonitor
.Wait(timeout
);
209 TimeDuration delta
= TimeStamp::Now() - now
;
210 if (delta
> timeout
) {
212 } else if (timeout
!= TimeDuration::Forever()) {
217 MOZ_ASSERT(mIdleThreads
> 0);
226 Work
PopWorkFromQueue(nsTArray
<RefPtr
<IDecodingTask
>>& aQueue
) {
228 work
.mType
= Work::Type::TASK
;
229 work
.mTask
= aQueue
.PopLastElement();
234 Work
CreateShutdownWork() const {
236 work
.mType
= Work::Type::SHUTDOWN
;
240 nsThreadPoolNaming mThreadNaming
;
242 // mMonitor guards everything below.
243 mutable Monitor mMonitor
;
244 nsTArray
<RefPtr
<IDecodingTask
>> mHighPriorityQueue
;
245 nsTArray
<RefPtr
<IDecodingTask
>> mLowPriorityQueue
;
246 nsTArray
<nsCOMPtr
<nsIThread
>> mThreads
;
247 TimeDuration mIdleTimeout
;
248 uint8_t mMaxIdleThreads
; // Maximum number of workers when idle.
249 uint8_t mAvailableThreads
; // How many new threads can be created.
250 uint8_t mIdleThreads
; // How many created threads are waiting.
254 class DecodePoolWorker final
: public Runnable
{
256 explicit DecodePoolWorker(DecodePoolImpl
* aImpl
, bool aShutdownIdle
)
257 : Runnable("image::DecodePoolWorker"),
259 mShutdownIdle(aShutdownIdle
) {}
261 NS_IMETHOD
Run() override
{
262 MOZ_ASSERT(!NS_IsMainThread());
264 nsCOMPtr
<nsIThread
> thisThread
;
265 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread
));
267 Work work
= mImpl
->StartWork(mShutdownIdle
);
269 switch (work
.mType
) {
270 case Work::Type::TASK
:
272 work
.mTask
= nullptr;
275 case Work::Type::SHUTDOWN
:
276 mImpl
->ShutdownThread(thisThread
, mShutdownIdle
);
277 PROFILER_UNREGISTER_THREAD();
281 MOZ_ASSERT_UNREACHABLE("Unknown work type");
284 work
= mImpl
->PopWork(mShutdownIdle
);
287 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
292 RefPtr
<DecodePoolImpl
> mImpl
;
296 bool DecodePoolImpl::CreateThread() {
297 mMonitor
.AssertCurrentThreadOwns();
298 MOZ_ASSERT(mAvailableThreads
> 0);
300 bool shutdownIdle
= mThreads
.Length() >= mMaxIdleThreads
;
301 nsCOMPtr
<nsIRunnable
> worker
= new DecodePoolWorker(this, shutdownIdle
);
302 nsCOMPtr
<nsIThread
> thread
;
303 nsresult rv
= NS_NewNamedThread(mThreadNaming
.GetNextThreadName("ImgDecoder"),
304 getter_AddRefs(thread
), worker
,
305 nsIThreadManager::kThreadPoolStackSize
);
306 if (NS_FAILED(rv
) || !thread
) {
307 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
310 thread
->SetNameForWakeupTelemetry(NS_LITERAL_CSTRING("ImgDecoder (all)"));
312 mThreads
.AppendElement(std::move(thread
));
315 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
320 void DecodePool::Initialize() {
321 MOZ_ASSERT(NS_IsMainThread());
322 sNumCores
= max
<int32_t>(PR_GetNumberOfProcessors(), 1);
323 DecodePool::Singleton();
327 DecodePool
* DecodePool::Singleton() {
329 MOZ_ASSERT(NS_IsMainThread());
330 sSingleton
= new DecodePool();
331 ClearOnShutdown(&sSingleton
);
338 uint32_t DecodePool::NumberOfCores() { return sNumCores
; }
341 class IOThreadIniter final
: public Runnable
{
343 explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
345 NS_IMETHOD
Run() override
{
346 MOZ_ASSERT(!NS_IsMainThread());
348 CoInitialize(nullptr);
355 DecodePool::DecodePool() : mMutex("image::IOThread") {
356 // Determine the number of threads we want.
358 StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
360 if (prefLimit
<= 0) {
361 int32_t numCores
= NumberOfCores();
364 } else if (numCores
== 2) {
365 // On an otherwise mostly idle system, having two image decoding threads
366 // doubles decoding performance, so it's worth doing on dual-core devices,
367 // even if under load we can't actually get that level of parallelism.
370 limit
= numCores
- 1;
373 limit
= static_cast<uint32_t>(prefLimit
);
378 // The parent process where there are content processes doesn't need as many
379 // threads for decoding images.
380 if (limit
> 4 && XRE_IsE10sParentProcess()) {
384 // The maximum number of idle threads allowed.
387 // The timeout period before shutting down idle threads.
388 int32_t prefIdleTimeout
=
389 StaticPrefs::image_multithreaded_decoding_idle_timeout_AtStartup();
390 TimeDuration idleTimeout
;
391 if (prefIdleTimeout
<= 0) {
392 idleTimeout
= TimeDuration::Forever();
395 idleTimeout
= TimeDuration::FromMilliseconds(prefIdleTimeout
);
396 idleLimit
= (limit
+ 1) / 2;
399 // Initialize the thread pool.
400 mImpl
= new DecodePoolImpl(limit
, idleLimit
, idleTimeout
);
402 // Initialize the I/O thread.
404 // On Windows we use the io thread to get icons from the system. Any thread
405 // that makes system calls needs to call CoInitialize. And these system calls
406 // (SHGetFileInfo) should only be called from one thread at a time, in case
407 // we ever create more than on io thread.
408 nsCOMPtr
<nsIRunnable
> initer
= new IOThreadIniter();
409 nsresult rv
= NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread
), initer
);
411 nsresult rv
= NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread
));
413 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
) && mIOThread
,
414 "Should successfully create image I/O thread");
416 nsCOMPtr
<nsIObserverService
> obsSvc
= services::GetObserverService();
418 obsSvc
->AddObserver(this, "xpcom-shutdown-threads", false);
422 DecodePool::~DecodePool() {
423 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
427 DecodePool::Observe(nsISupports
*, const char* aTopic
, const char16_t
*) {
428 MOZ_ASSERT(strcmp(aTopic
, "xpcom-shutdown-threads") == 0, "Unexpected topic");
430 nsCOMPtr
<nsIThread
> ioThread
;
433 MutexAutoLock
lock(mMutex
);
434 ioThread
.swap(mIOThread
);
440 ioThread
->Shutdown();
446 bool DecodePool::IsShuttingDown() const { return mImpl
->IsShuttingDown(); }
448 void DecodePool::AsyncRun(IDecodingTask
* aTask
) {
450 mImpl
->PushWork(aTask
);
453 bool DecodePool::SyncRunIfPreferred(IDecodingTask
* aTask
,
454 const nsCString
& aURI
) {
455 MOZ_ASSERT(NS_IsMainThread());
458 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
461 if (aTask
->ShouldPreferSyncRun()) {
470 void DecodePool::SyncRunIfPossible(IDecodingTask
* aTask
,
471 const nsCString
& aURI
) {
472 MOZ_ASSERT(NS_IsMainThread());
475 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
481 already_AddRefed
<nsIEventTarget
> DecodePool::GetIOEventTarget() {
482 MutexAutoLock
threadPoolLock(mMutex
);
483 nsCOMPtr
<nsIEventTarget
> target
= mIOThread
;
484 return target
.forget();
488 } // namespace mozilla