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