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/. */
5
6 #include "DecodePool.h"
7
8 #include <algorithm>
9
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/Monitor.h"
13 #include "nsCOMPtr.h"
14 #include "nsIObserverService.h"
15 #include "nsIThreadPool.h"
16 #include "nsThreadManager.h"
17 #include "nsThreadUtils.h"
18 #include "nsXPCOMCIDInternal.h"
19 #include "prsystem.h"
20 #include "nsIXULRuntime.h"
21
22 #include "gfxPrefs.h"
23
24 #include "Decoder.h"
25 #include "IDecodingTask.h"
26 #include "RasterImage.h"
27
28 using std::max;
29 using std::min;
30
31 namespace mozilla {
32 namespace image {
33
34 ///////////////////////////////////////////////////////////////////////////////
35 // DecodePool implementation.
36 ///////////////////////////////////////////////////////////////////////////////
37
38 /* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
39 /* static */ uint32_t DecodePool::sNumCores = 0;
40
41 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
42
43 struct Work {
44 enum class Type { TASK, SHUTDOWN } mType;
45
46 RefPtr<IDecodingTask> mTask;
47 };
48
49 class DecodePoolImpl {
50 public:
51 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)52 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
53
54 DecodePoolImpl(uint8_t aMaxThreads, uint8_t aMaxIdleThreads,
55 PRIntervalTime aIdleTimeout)
56 : mMonitor("DecodePoolImpl"),
57 mThreads(aMaxThreads),
58 mIdleTimeout(aIdleTimeout),
59 mMaxIdleThreads(aMaxIdleThreads),
60 mAvailableThreads(aMaxThreads),
61 mIdleThreads(0),
62 mShuttingDown(false) {
63 MonitorAutoLock lock(mMonitor);
64 bool success = CreateThread();
65 MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!");
66 }
67
68 /// Shut down the provided decode pool thread.
ShutdownThread(nsIThread * aThisThread,bool aShutdownIdle)69 void ShutdownThread(nsIThread* aThisThread, bool aShutdownIdle) {
70 {
71 // If this is an idle thread shutdown, then we need to remove it from the
72 // worker array. Process shutdown will move the entire array.
73 MonitorAutoLock lock(mMonitor);
74 if (!mShuttingDown) {
75 ++mAvailableThreads;
76 DebugOnly<bool> removed = mThreads.RemoveElement(aThisThread);
77 MOZ_ASSERT(aShutdownIdle);
78 MOZ_ASSERT(mAvailableThreads < mThreads.Capacity());
79 MOZ_ASSERT(removed);
80 }
81 }
82
83 // Threads have to be shut down from another thread, so we'll ask the
84 // main thread to do it for us.
85 SystemGroup::Dispatch(TaskCategory::Other,
86 NewRunnableMethod("DecodePoolImpl::ShutdownThread",
87 aThisThread, &nsIThread::Shutdown));
88 }
89
90 /**
91 * Requests shutdown. New work items will be dropped on the floor, and all
92 * decode pool threads will be shut down once existing work items have been
93 * processed.
94 */
Shutdown()95 void Shutdown() {
96 nsTArray<nsCOMPtr<nsIThread>> threads;
97
98 {
99 MonitorAutoLock lock(mMonitor);
100 mShuttingDown = true;
101 mAvailableThreads = 0;
102 threads.SwapElements(mThreads);
103 mMonitor.NotifyAll();
104 }
105
106 for (uint32_t i = 0; i < threads.Length(); ++i) {
107 threads[i]->Shutdown();
108 }
109 }
110
IsShuttingDown() const111 bool IsShuttingDown() const {
112 MonitorAutoLock lock(mMonitor);
113 return mShuttingDown;
114 }
115
116 /// Pushes a new decode work item.
PushWork(IDecodingTask * aTask)117 void PushWork(IDecodingTask* aTask) {
118 MOZ_ASSERT(aTask);
119 RefPtr<IDecodingTask> task(aTask);
120
121 MonitorAutoLock lock(mMonitor);
122
123 if (mShuttingDown) {
124 // Drop any new work on the floor if we're shutting down.
125 return;
126 }
127
128 if (task->Priority() == TaskPriority::eHigh) {
129 mHighPriorityQueue.AppendElement(Move(task));
130 } else {
131 mLowPriorityQueue.AppendElement(Move(task));
132 }
133
134 // If there are pending tasks, create more workers if and only if we have
135 // not exceeded the capacity, and any previously created workers are ready.
136 if (mAvailableThreads) {
137 size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length();
138 if (pending > mIdleThreads) {
139 CreateThread();
140 }
141 }
142
143 mMonitor.Notify();
144 }
145
StartWork(bool aShutdownIdle)146 Work StartWork(bool aShutdownIdle) {
147 MonitorAutoLock lock(mMonitor);
148
149 // The thread was already marked as idle when it was created. Once it gets
150 // its first work item, it is assumed it is busy performing that work until
151 // it blocks on the monitor once again.
152 MOZ_ASSERT(mIdleThreads > 0);
153 --mIdleThreads;
154 return PopWorkLocked(aShutdownIdle);
155 }
156
PopWork(bool aShutdownIdle)157 Work PopWork(bool aShutdownIdle) {
158 MonitorAutoLock lock(mMonitor);
159 return PopWorkLocked(aShutdownIdle);
160 }
161
162 private:
163 /// Pops a new work item, blocking if necessary.
PopWorkLocked(bool aShutdownIdle)164 Work PopWorkLocked(bool aShutdownIdle) {
165 mMonitor.AssertCurrentThreadOwns();
166
167 PRIntervalTime timeout = mIdleTimeout;
168 do {
169 if (!mHighPriorityQueue.IsEmpty()) {
170 return PopWorkFromQueue(mHighPriorityQueue);
171 }
172
173 if (!mLowPriorityQueue.IsEmpty()) {
174 return PopWorkFromQueue(mLowPriorityQueue);
175 }
176
177 if (mShuttingDown) {
178 return CreateShutdownWork();
179 }
180
181 // Nothing to do; block until some work is available.
182 if (!aShutdownIdle) {
183 // This thread was created before we hit the idle thread maximum. It
184 // will never shutdown until the process itself is torn down.
185 ++mIdleThreads;
186 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
187 mMonitor.Wait();
188 } else {
189 // This thread should shutdown if it is idle. If we have waited longer
190 // than the timeout period without having done any work, then we should
191 // shutdown the thread.
192 if (timeout == 0) {
193 return CreateShutdownWork();
194 }
195
196 ++mIdleThreads;
197 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
198
199 PRIntervalTime now = PR_IntervalNow();
200 mMonitor.Wait(timeout);
201 PRIntervalTime delta = PR_IntervalNow() - now;
202 if (delta > timeout) {
203 timeout = 0;
204 } else {
205 timeout -= delta;
206 }
207 }
208
209 MOZ_ASSERT(mIdleThreads > 0);
210 --mIdleThreads;
211 } while (true);
212 }
213
~DecodePoolImpl()214 ~DecodePoolImpl() {}
215
216 bool CreateThread();
217
PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>> & aQueue)218 Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue) {
219 Work work;
220 work.mType = Work::Type::TASK;
221 work.mTask = aQueue.LastElement().forget();
222 aQueue.RemoveElementAt(aQueue.Length() - 1);
223
224 return work;
225 }
226
CreateShutdownWork() const227 Work CreateShutdownWork() const {
228 Work work;
229 work.mType = Work::Type::SHUTDOWN;
230 return work;
231 }
232
233 nsThreadPoolNaming mThreadNaming;
234
235 // mMonitor guards everything below.
236 mutable Monitor mMonitor;
237 nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
238 nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
239 nsTArray<nsCOMPtr<nsIThread>> mThreads;
240 PRIntervalTime mIdleTimeout;
241 uint8_t mMaxIdleThreads; // Maximum number of workers when idle.
242 uint8_t mAvailableThreads; // How many new threads can be created.
243 uint8_t mIdleThreads; // How many created threads are waiting.
244 bool mShuttingDown;
245 };
246
247 class DecodePoolWorker final : public Runnable {
248 public:
DecodePoolWorker(DecodePoolImpl * aImpl,bool aShutdownIdle)249 explicit DecodePoolWorker(DecodePoolImpl* aImpl, bool aShutdownIdle)
250 : Runnable("image::DecodePoolWorker"),
251 mImpl(aImpl),
252 mShutdownIdle(aShutdownIdle) {}
253
Run()254 NS_IMETHOD Run() override {
255 MOZ_ASSERT(!NS_IsMainThread());
256
257 nsCOMPtr<nsIThread> thisThread;
258 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
259
260 Work work = mImpl->StartWork(mShutdownIdle);
261 do {
262 switch (work.mType) {
263 case Work::Type::TASK:
264 work.mTask->Run();
265 work.mTask = nullptr;
266 break;
267
268 case Work::Type::SHUTDOWN:
269 mImpl->ShutdownThread(thisThread, mShutdownIdle);
270 PROFILER_UNREGISTER_THREAD();
271 return NS_OK;
272
273 default:
274 MOZ_ASSERT_UNREACHABLE("Unknown work type");
275 }
276
277 work = mImpl->PopWork(mShutdownIdle);
278 } while (true);
279
280 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
281 return NS_OK;
282 }
283
284 private:
285 RefPtr<DecodePoolImpl> mImpl;
286 bool mShutdownIdle;
287 };
288
CreateThread()289 bool DecodePoolImpl::CreateThread() {
290 mMonitor.AssertCurrentThreadOwns();
291 MOZ_ASSERT(mAvailableThreads > 0);
292
293 bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads;
294 nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle);
295 nsCOMPtr<nsIThread> thread;
296 nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
297 getter_AddRefs(thread), worker,
298 nsIThreadManager::kThreadPoolStackSize);
299 if (NS_FAILED(rv) || !thread) {
300 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
301 return false;
302 }
303
304 mThreads.AppendElement(Move(thread));
305 --mAvailableThreads;
306 ++mIdleThreads;
307 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
308 return true;
309 }
310
Initialize()311 /* static */ void DecodePool::Initialize() {
312 MOZ_ASSERT(NS_IsMainThread());
313 sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
314 DecodePool::Singleton();
315 }
316
Singleton()317 /* static */ DecodePool* DecodePool::Singleton() {
318 if (!sSingleton) {
319 MOZ_ASSERT(NS_IsMainThread());
320 sSingleton = new DecodePool();
321 ClearOnShutdown(&sSingleton);
322 }
323
324 return sSingleton;
325 }
326
NumberOfCores()327 /* static */ uint32_t DecodePool::NumberOfCores() { return sNumCores; }
328
DecodePool()329 DecodePool::DecodePool() : mMutex("image::DecodePool") {
330 // Determine the number of threads we want.
331 int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
332 uint32_t limit;
333 if (prefLimit <= 0) {
334 int32_t numCores = NumberOfCores();
335 if (numCores <= 1) {
336 limit = 1;
337 } else if (numCores == 2) {
338 // On an otherwise mostly idle system, having two image decoding threads
339 // doubles decoding performance, so it's worth doing on dual-core devices,
340 // even if under load we can't actually get that level of parallelism.
341 limit = 2;
342 } else {
343 limit = numCores - 1;
344 }
345 } else {
346 limit = static_cast<uint32_t>(prefLimit);
347 }
348 if (limit > 32) {
349 limit = 32;
350 }
351 // The parent process where there are content processes doesn't need as many
352 // threads for decoding images.
353 if (limit > 4 && XRE_IsE10sParentProcess()) {
354 limit = 4;
355 }
356
357 // The maximum number of idle threads allowed.
358 uint32_t idleLimit;
359
360 // The timeout period before shutting down idle threads.
361 int32_t prefIdleTimeout = gfxPrefs::ImageMTDecodingIdleTimeout();
362 PRIntervalTime idleTimeout;
363 if (prefIdleTimeout <= 0) {
364 idleTimeout = PR_INTERVAL_NO_TIMEOUT;
365 idleLimit = limit;
366 } else {
367 idleTimeout =
368 PR_MillisecondsToInterval(static_cast<uint32_t>(prefIdleTimeout));
369 idleLimit = (limit + 1) / 2;
370 }
371
372 // Initialize the thread pool.
373 mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout);
374
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");
379
380 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
381 if (obsSvc) {
382 obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
383 }
384 }
385
~DecodePool()386 DecodePool::~DecodePool() {
387 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
388 }
389
390 NS_IMETHODIMP
Observe(nsISupports *,const char * aTopic,const char16_t *)391 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
392 MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
393
394 nsCOMPtr<nsIThread> ioThread;
395
396 {
397 MutexAutoLock lock(mMutex);
398 ioThread.swap(mIOThread);
399 }
400
401 mImpl->Shutdown();
402
403 if (ioThread) {
404 ioThread->Shutdown();
405 }
406
407 return NS_OK;
408 }
409
IsShuttingDown() const410 bool DecodePool::IsShuttingDown() const { return mImpl->IsShuttingDown(); }
411
AsyncRun(IDecodingTask * aTask)412 void DecodePool::AsyncRun(IDecodingTask* aTask) {
413 MOZ_ASSERT(aTask);
414 mImpl->PushWork(aTask);
415 }
416
SyncRunIfPreferred(IDecodingTask * aTask,const nsCString & aURI)417 bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
418 const nsCString& aURI) {
419 MOZ_ASSERT(NS_IsMainThread());
420 MOZ_ASSERT(aTask);
421
422 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
423 GRAPHICS, aURI);
424
425 if (aTask->ShouldPreferSyncRun()) {
426 aTask->Run();
427 return true;
428 }
429
430 AsyncRun(aTask);
431 return false;
432 }
433
SyncRunIfPossible(IDecodingTask * aTask,const nsCString & aURI)434 void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
435 const nsCString& aURI) {
436 MOZ_ASSERT(NS_IsMainThread());
437 MOZ_ASSERT(aTask);
438
439 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
440 GRAPHICS, aURI);
441
442 aTask->Run();
443 }
444
GetIOEventTarget()445 already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() {
446 MutexAutoLock threadPoolLock(mMutex);
447 nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
448 return target.forget();
449 }
450
451 } // namespace image
452 } // namespace mozilla
453