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/TimeStamp.h"
15 #include "nsIObserverService.h"
16 #include "nsIThreadPool.h"
17 #include "nsThreadManager.h"
18 #include "nsThreadUtils.h"
19 #include "nsXPCOMCIDInternal.h"
21 #include "nsIXULRuntime.h"
26 #include "IDecodingTask.h"
27 #include "RasterImage.h"
35 ///////////////////////////////////////////////////////////////////////////////
36 // DecodePool implementation.
37 ///////////////////////////////////////////////////////////////////////////////
39 /* static */ StaticRefPtr
<DecodePool
> DecodePool::sSingleton
;
40 /* static */ uint32_t DecodePool::sNumCores
= 0;
42 NS_IMPL_ISUPPORTS(DecodePool
, nsIObserver
)
45 enum class Type
{ TASK
, SHUTDOWN
} mType
;
47 RefPtr
<IDecodingTask
> mTask
;
50 class DecodePoolImpl
{
52 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl
)
53 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl
)
55 DecodePoolImpl(uint8_t aMaxThreads
, uint8_t aMaxIdleThreads
,
56 TimeDuration aIdleTimeout
)
57 : mMonitor("DecodePoolImpl"),
58 mThreads(aMaxThreads
),
59 mIdleTimeout(aIdleTimeout
),
60 mMaxIdleThreads(aMaxIdleThreads
),
61 mAvailableThreads(aMaxThreads
),
63 mShuttingDown(false) {
64 MonitorAutoLock
lock(mMonitor
);
65 bool success
= CreateThread();
66 MOZ_RELEASE_ASSERT(success
, "Must create first image decoder thread!");
69 /// Shut down the provided decode pool thread.
70 void ShutdownThread(nsIThread
* aThisThread
, bool aShutdownIdle
) {
72 // If this is an idle thread shutdown, then we need to remove it from the
73 // worker array. Process shutdown will move the entire array.
74 MonitorAutoLock
lock(mMonitor
);
77 DebugOnly
<bool> removed
= mThreads
.RemoveElement(aThisThread
);
78 MOZ_ASSERT(aShutdownIdle
);
79 MOZ_ASSERT(mAvailableThreads
< mThreads
.Capacity());
84 // Threads have to be shut down from another thread, so we'll ask the
85 // main thread to do it for us.
86 SystemGroup::Dispatch(TaskCategory::Other
,
87 NewRunnableMethod("DecodePoolImpl::ShutdownThread",
88 aThisThread
, &nsIThread::Shutdown
));
92 * Requests shutdown. New work items will be dropped on the floor, and all
93 * decode pool threads will be shut down once existing work items have been
97 nsTArray
<nsCOMPtr
<nsIThread
>> threads
;
100 MonitorAutoLock
lock(mMonitor
);
101 mShuttingDown
= true;
102 mAvailableThreads
= 0;
103 threads
.SwapElements(mThreads
);
104 mMonitor
.NotifyAll();
107 for (uint32_t i
= 0; i
< threads
.Length(); ++i
) {
108 threads
[i
]->Shutdown();
112 bool IsShuttingDown() const {
113 MonitorAutoLock
lock(mMonitor
);
114 return mShuttingDown
;
117 /// Pushes a new decode work item.
118 void PushWork(IDecodingTask
* aTask
) {
120 RefPtr
<IDecodingTask
> task(aTask
);
122 MonitorAutoLock
lock(mMonitor
);
125 // Drop any new work on the floor if we're shutting down.
129 if (task
->Priority() == TaskPriority::eHigh
) {
130 mHighPriorityQueue
.AppendElement(std::move(task
));
132 mLowPriorityQueue
.AppendElement(std::move(task
));
135 // If there are pending tasks, create more workers if and only if we have
136 // not exceeded the capacity, and any previously created workers are ready.
137 if (mAvailableThreads
) {
138 size_t pending
= mHighPriorityQueue
.Length() + mLowPriorityQueue
.Length();
139 if (pending
> mIdleThreads
) {
147 Work
StartWork(bool aShutdownIdle
) {
148 MonitorAutoLock
lock(mMonitor
);
150 // The thread was already marked as idle when it was created. Once it gets
151 // its first work item, it is assumed it is busy performing that work until
152 // it blocks on the monitor once again.
153 MOZ_ASSERT(mIdleThreads
> 0);
155 return PopWorkLocked(aShutdownIdle
);
158 Work
PopWork(bool aShutdownIdle
) {
159 MonitorAutoLock
lock(mMonitor
);
160 return PopWorkLocked(aShutdownIdle
);
164 /// Pops a new work item, blocking if necessary.
165 Work
PopWorkLocked(bool aShutdownIdle
) {
166 mMonitor
.AssertCurrentThreadOwns();
168 TimeDuration timeout
= mIdleTimeout
;
170 if (!mHighPriorityQueue
.IsEmpty()) {
171 return PopWorkFromQueue(mHighPriorityQueue
);
174 if (!mLowPriorityQueue
.IsEmpty()) {
175 return PopWorkFromQueue(mLowPriorityQueue
);
179 return CreateShutdownWork();
182 // Nothing to do; block until some work is available.
183 AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE
);
184 if (!aShutdownIdle
) {
185 // This thread was created before we hit the idle thread maximum. It
186 // will never shutdown until the process itself is torn down.
188 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
191 // This thread should shutdown if it is idle. If we have waited longer
192 // than the timeout period without having done any work, then we should
193 // shutdown the thread.
194 if (timeout
.IsZero()) {
195 return CreateShutdownWork();
199 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
201 TimeStamp now
= TimeStamp::Now();
202 mMonitor
.Wait(timeout
);
203 TimeDuration delta
= TimeStamp::Now() - now
;
204 if (delta
> timeout
) {
206 } else if (timeout
!= TimeDuration::Forever()) {
211 MOZ_ASSERT(mIdleThreads
> 0);
220 Work
PopWorkFromQueue(nsTArray
<RefPtr
<IDecodingTask
>>& aQueue
) {
222 work
.mType
= Work::Type::TASK
;
223 work
.mTask
= aQueue
.PopLastElement();
228 Work
CreateShutdownWork() const {
230 work
.mType
= Work::Type::SHUTDOWN
;
234 nsThreadPoolNaming mThreadNaming
;
236 // mMonitor guards everything below.
237 mutable Monitor mMonitor
;
238 nsTArray
<RefPtr
<IDecodingTask
>> mHighPriorityQueue
;
239 nsTArray
<RefPtr
<IDecodingTask
>> mLowPriorityQueue
;
240 nsTArray
<nsCOMPtr
<nsIThread
>> mThreads
;
241 TimeDuration mIdleTimeout
;
242 uint8_t mMaxIdleThreads
; // Maximum number of workers when idle.
243 uint8_t mAvailableThreads
; // How many new threads can be created.
244 uint8_t mIdleThreads
; // How many created threads are waiting.
248 class DecodePoolWorker final
: public Runnable
{
250 explicit DecodePoolWorker(DecodePoolImpl
* aImpl
, bool aShutdownIdle
)
251 : Runnable("image::DecodePoolWorker"),
253 mShutdownIdle(aShutdownIdle
) {}
255 NS_IMETHOD
Run() override
{
256 MOZ_ASSERT(!NS_IsMainThread());
258 nsCOMPtr
<nsIThread
> thisThread
;
259 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread
));
261 Work work
= mImpl
->StartWork(mShutdownIdle
);
263 switch (work
.mType
) {
264 case Work::Type::TASK
:
266 work
.mTask
= nullptr;
269 case Work::Type::SHUTDOWN
:
270 mImpl
->ShutdownThread(thisThread
, mShutdownIdle
);
271 PROFILER_UNREGISTER_THREAD();
275 MOZ_ASSERT_UNREACHABLE("Unknown work type");
278 work
= mImpl
->PopWork(mShutdownIdle
);
281 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
286 RefPtr
<DecodePoolImpl
> mImpl
;
290 bool DecodePoolImpl::CreateThread() {
291 mMonitor
.AssertCurrentThreadOwns();
292 MOZ_ASSERT(mAvailableThreads
> 0);
294 bool shutdownIdle
= mThreads
.Length() >= mMaxIdleThreads
;
295 nsCOMPtr
<nsIRunnable
> worker
= new DecodePoolWorker(this, shutdownIdle
);
296 nsCOMPtr
<nsIThread
> thread
;
297 nsresult rv
= NS_NewNamedThread(mThreadNaming
.GetNextThreadName("ImgDecoder"),
298 getter_AddRefs(thread
), worker
,
299 nsIThreadManager::kThreadPoolStackSize
);
300 if (NS_FAILED(rv
) || !thread
) {
301 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
305 mThreads
.AppendElement(std::move(thread
));
308 MOZ_ASSERT(mIdleThreads
<= mThreads
.Capacity());
312 /* static */ void DecodePool::Initialize() {
313 MOZ_ASSERT(NS_IsMainThread());
314 sNumCores
= max
<int32_t>(PR_GetNumberOfProcessors(), 1);
315 DecodePool::Singleton();
318 /* static */ DecodePool
* DecodePool::Singleton() {
320 MOZ_ASSERT(NS_IsMainThread());
321 sSingleton
= new DecodePool();
322 ClearOnShutdown(&sSingleton
);
328 /* static */ uint32_t DecodePool::NumberOfCores() { return sNumCores
; }
330 DecodePool::DecodePool() : mMutex("image::DecodePool") {
331 // Determine the number of threads we want.
332 int32_t prefLimit
= gfxPrefs::ImageMTDecodingLimit();
334 if (prefLimit
<= 0) {
335 int32_t numCores
= NumberOfCores();
338 } else if (numCores
== 2) {
339 // On an otherwise mostly idle system, having two image decoding threads
340 // doubles decoding performance, so it's worth doing on dual-core devices,
341 // even if under load we can't actually get that level of parallelism.
344 limit
= numCores
- 1;
347 limit
= static_cast<uint32_t>(prefLimit
);
352 // The parent process where there are content processes doesn't need as many
353 // threads for decoding images.
354 if (limit
> 4 && XRE_IsE10sParentProcess()) {
358 // The maximum number of idle threads allowed.
361 // The timeout period before shutting down idle threads.
362 int32_t prefIdleTimeout
= gfxPrefs::ImageMTDecodingIdleTimeout();
363 TimeDuration idleTimeout
;
364 if (prefIdleTimeout
<= 0) {
365 idleTimeout
= TimeDuration::Forever();
368 idleTimeout
= TimeDuration::FromMilliseconds(prefIdleTimeout
);
369 idleLimit
= (limit
+ 1) / 2;
372 // Initialize the thread pool.
373 mImpl
= new DecodePoolImpl(limit
, idleLimit
, idleTimeout
);
375 // Initialize the I/O thread.
376 nsresult rv
= NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread
));
377 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv
) && mIOThread
,
378 "Should successfully create image I/O thread");
380 nsCOMPtr
<nsIObserverService
> obsSvc
= services::GetObserverService();
382 obsSvc
->AddObserver(this, "xpcom-shutdown-threads", false);
386 DecodePool::~DecodePool() {
387 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
391 DecodePool::Observe(nsISupports
*, const char* aTopic
, const char16_t
*) {
392 MOZ_ASSERT(strcmp(aTopic
, "xpcom-shutdown-threads") == 0, "Unexpected topic");
394 nsCOMPtr
<nsIThread
> ioThread
;
397 MutexAutoLock
lock(mMutex
);
398 ioThread
.swap(mIOThread
);
404 ioThread
->Shutdown();
410 bool DecodePool::IsShuttingDown() const { return mImpl
->IsShuttingDown(); }
412 void DecodePool::AsyncRun(IDecodingTask
* aTask
) {
414 mImpl
->PushWork(aTask
);
417 bool DecodePool::SyncRunIfPreferred(IDecodingTask
* aTask
,
418 const nsCString
& aURI
) {
419 MOZ_ASSERT(NS_IsMainThread());
422 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
425 if (aTask
->ShouldPreferSyncRun()) {
434 void DecodePool::SyncRunIfPossible(IDecodingTask
* aTask
,
435 const nsCString
& aURI
) {
436 MOZ_ASSERT(NS_IsMainThread());
439 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
445 already_AddRefed
<nsIEventTarget
> DecodePool::GetIOEventTarget() {
446 MutexAutoLock
threadPoolLock(mMutex
);
447 nsCOMPtr
<nsIEventTarget
> target
= mIOThread
;
448 return target
.forget();
452 } // namespace mozilla