forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			499 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			499 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "DecodePool.h"
 | |
| 
 | |
| #include <algorithm>
 | |
| 
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "mozilla/DebugOnly.h"
 | |
| #include "mozilla/Monitor.h"
 | |
| #include "mozilla/SchedulerGroup.h"
 | |
| #include "mozilla/StaticPrefs_image.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "nsCOMPtr.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsThreadManager.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsXPCOMCIDInternal.h"
 | |
| #include "prsystem.h"
 | |
| 
 | |
| #include "Decoder.h"
 | |
| #include "IDecodingTask.h"
 | |
| #include "RasterImage.h"
 | |
| 
 | |
| #if defined(XP_WIN)
 | |
| #  include <objbase.h>
 | |
| #endif
 | |
| 
 | |
| using std::max;
 | |
| using std::min;
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace image {
 | |
| 
 | |
| ///////////////////////////////////////////////////////////////////////////////
 | |
| // DecodePool implementation.
 | |
| ///////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| /* static */
 | |
| StaticRefPtr<DecodePool> DecodePool::sSingleton;
 | |
| /* static */
 | |
| uint32_t DecodePool::sNumCores = 0;
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
 | |
| 
 | |
| struct Work {
 | |
|   enum class Type { TASK, SHUTDOWN } mType;
 | |
| 
 | |
|   RefPtr<IDecodingTask> mTask;
 | |
| };
 | |
| 
 | |
| class DecodePoolImpl {
 | |
|  public:
 | |
|   MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
 | |
|   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
 | |
| 
 | |
|   DecodePoolImpl(uint8_t aMaxThreads, uint8_t aMaxIdleThreads,
 | |
|                  TimeDuration aIdleTimeout)
 | |
|       : mMonitor("DecodePoolImpl"),
 | |
|         mThreads(aMaxThreads),
 | |
|         mIdleTimeout(aIdleTimeout),
 | |
|         mMaxIdleThreads(aMaxIdleThreads),
 | |
|         mAvailableThreads(aMaxThreads),
 | |
|         mIdleThreads(0),
 | |
|         mShuttingDown(false) {
 | |
|     MonitorAutoLock lock(mMonitor);
 | |
|     bool success = CreateThread();
 | |
|     MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!");
 | |
|   }
 | |
| 
 | |
|   /// Shut down the provided decode pool thread.
 | |
|   void ShutdownThread(nsIThread* aThisThread, bool aShutdownIdle) {
 | |
|     bool removed = false;
 | |
| 
 | |
|     {
 | |
|       // If this is an idle thread shutdown, then we need to remove it from the
 | |
|       // worker array. Process shutdown will move the entire array.
 | |
|       MonitorAutoLock lock(mMonitor);
 | |
|       if (!mShuttingDown) {
 | |
|         ++mAvailableThreads;
 | |
|         removed = mThreads.RemoveElement(aThisThread);
 | |
|         MOZ_ASSERT(aShutdownIdle);
 | |
|         MOZ_ASSERT(mAvailableThreads < mThreads.Capacity());
 | |
|         MOZ_ASSERT(removed);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Threads have to be shut down from another thread, so we'll ask the
 | |
|     // main thread to do it for us, but only if we removed it from our thread
 | |
|     // pool explicitly. Otherwise we could try to shut down the same thread
 | |
|     // twice.
 | |
|     if (removed) {
 | |
|       SchedulerGroup::Dispatch(
 | |
|           TaskCategory::Other,
 | |
|           NewRunnableMethod("DecodePoolImpl::ShutdownThread", aThisThread,
 | |
|                             &nsIThread::AsyncShutdown));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Requests shutdown. New work items will be dropped on the floor, and all
 | |
|    * decode pool threads will be shut down once existing work items have been
 | |
|    * processed.
 | |
|    */
 | |
|   void Shutdown() {
 | |
|     nsTArray<nsCOMPtr<nsIThread>> threads;
 | |
| 
 | |
|     {
 | |
|       MonitorAutoLock lock(mMonitor);
 | |
|       mShuttingDown = true;
 | |
|       mAvailableThreads = 0;
 | |
|       threads = std::move(mThreads);
 | |
|       mMonitor.NotifyAll();
 | |
|     }
 | |
| 
 | |
|     for (uint32_t i = 0; i < threads.Length(); ++i) {
 | |
|       threads[i]->Shutdown();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool IsShuttingDown() const {
 | |
|     MonitorAutoLock lock(mMonitor);
 | |
|     return mShuttingDown;
 | |
|   }
 | |
| 
 | |
|   /// Pushes a new decode work item.
 | |
|   void PushWork(IDecodingTask* aTask) {
 | |
|     MOZ_ASSERT(aTask);
 | |
|     RefPtr<IDecodingTask> task(aTask);
 | |
| 
 | |
|     MonitorAutoLock lock(mMonitor);
 | |
| 
 | |
|     if (mShuttingDown) {
 | |
|       // Drop any new work on the floor if we're shutting down.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (task->Priority() == TaskPriority::eHigh) {
 | |
|       mHighPriorityQueue.AppendElement(std::move(task));
 | |
|     } else {
 | |
|       mLowPriorityQueue.AppendElement(std::move(task));
 | |
|     }
 | |
| 
 | |
|     // If there are pending tasks, create more workers if and only if we have
 | |
|     // not exceeded the capacity, and any previously created workers are ready.
 | |
|     if (mAvailableThreads) {
 | |
|       size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length();
 | |
|       if (pending > mIdleThreads) {
 | |
|         CreateThread();
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     mMonitor.Notify();
 | |
|   }
 | |
| 
 | |
|   Work StartWork(bool aShutdownIdle) {
 | |
|     MonitorAutoLock lock(mMonitor);
 | |
| 
 | |
|     // The thread was already marked as idle when it was created. Once it gets
 | |
|     // its first work item, it is assumed it is busy performing that work until
 | |
|     // it blocks on the monitor once again.
 | |
|     MOZ_ASSERT(mIdleThreads > 0);
 | |
|     --mIdleThreads;
 | |
|     return PopWorkLocked(aShutdownIdle);
 | |
|   }
 | |
| 
 | |
|   Work PopWork(bool aShutdownIdle) {
 | |
|     MonitorAutoLock lock(mMonitor);
 | |
|     return PopWorkLocked(aShutdownIdle);
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   /// Pops a new work item, blocking if necessary.
 | |
|   Work PopWorkLocked(bool aShutdownIdle) {
 | |
|     mMonitor.AssertCurrentThreadOwns();
 | |
| 
 | |
|     TimeDuration timeout = mIdleTimeout;
 | |
|     do {
 | |
|       if (!mHighPriorityQueue.IsEmpty()) {
 | |
|         return PopWorkFromQueue(mHighPriorityQueue);
 | |
|       }
 | |
| 
 | |
|       if (!mLowPriorityQueue.IsEmpty()) {
 | |
|         return PopWorkFromQueue(mLowPriorityQueue);
 | |
|       }
 | |
| 
 | |
|       if (mShuttingDown) {
 | |
|         return CreateShutdownWork();
 | |
|       }
 | |
| 
 | |
|       // Nothing to do; block until some work is available.
 | |
|       AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE);
 | |
|       if (!aShutdownIdle) {
 | |
|         // This thread was created before we hit the idle thread maximum. It
 | |
|         // will never shutdown until the process itself is torn down.
 | |
|         ++mIdleThreads;
 | |
|         MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
 | |
|         mMonitor.Wait();
 | |
|       } else {
 | |
|         // This thread should shutdown if it is idle. If we have waited longer
 | |
|         // than the timeout period without having done any work, then we should
 | |
|         // shutdown the thread.
 | |
|         if (timeout.IsZero()) {
 | |
|           return CreateShutdownWork();
 | |
|         }
 | |
| 
 | |
|         ++mIdleThreads;
 | |
|         MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
 | |
| 
 | |
|         TimeStamp now = TimeStamp::Now();
 | |
|         mMonitor.Wait(timeout);
 | |
|         TimeDuration delta = TimeStamp::Now() - now;
 | |
|         if (delta > timeout) {
 | |
|           timeout = 0;
 | |
|         } else if (timeout != TimeDuration::Forever()) {
 | |
|           timeout -= delta;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       MOZ_ASSERT(mIdleThreads > 0);
 | |
|       --mIdleThreads;
 | |
|     } while (true);
 | |
|   }
 | |
| 
 | |
|   ~DecodePoolImpl() {}
 | |
| 
 | |
|   bool CreateThread();
 | |
| 
 | |
|   Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue) {
 | |
|     Work work;
 | |
|     work.mType = Work::Type::TASK;
 | |
|     work.mTask = aQueue.PopLastElement();
 | |
| 
 | |
|     return work;
 | |
|   }
 | |
| 
 | |
|   Work CreateShutdownWork() const {
 | |
|     Work work;
 | |
|     work.mType = Work::Type::SHUTDOWN;
 | |
|     return work;
 | |
|   }
 | |
| 
 | |
|   nsThreadPoolNaming mThreadNaming;
 | |
| 
 | |
|   // mMonitor guards everything below.
 | |
|   mutable Monitor mMonitor;
 | |
|   nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
 | |
|   nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
 | |
|   nsTArray<nsCOMPtr<nsIThread>> mThreads;
 | |
|   TimeDuration mIdleTimeout;
 | |
|   uint8_t mMaxIdleThreads;    // Maximum number of workers when idle.
 | |
|   uint8_t mAvailableThreads;  // How many new threads can be created.
 | |
|   uint8_t mIdleThreads;       // How many created threads are waiting.
 | |
|   bool mShuttingDown;
 | |
| };
 | |
| 
 | |
| class DecodePoolWorker final : public Runnable {
 | |
|  public:
 | |
|   explicit DecodePoolWorker(DecodePoolImpl* aImpl, bool aShutdownIdle)
 | |
|       : Runnable("image::DecodePoolWorker"),
 | |
|         mImpl(aImpl),
 | |
|         mShutdownIdle(aShutdownIdle) {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     nsCOMPtr<nsIThread> thisThread;
 | |
|     nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
 | |
| 
 | |
|     Work work = mImpl->StartWork(mShutdownIdle);
 | |
|     do {
 | |
|       switch (work.mType) {
 | |
|         case Work::Type::TASK:
 | |
|           work.mTask->Run();
 | |
|           work.mTask = nullptr;
 | |
|           break;
 | |
| 
 | |
|         case Work::Type::SHUTDOWN:
 | |
|           mImpl->ShutdownThread(thisThread, mShutdownIdle);
 | |
|           PROFILER_UNREGISTER_THREAD();
 | |
|           return NS_OK;
 | |
| 
 | |
|         default:
 | |
|           MOZ_ASSERT_UNREACHABLE("Unknown work type");
 | |
|       }
 | |
| 
 | |
|       work = mImpl->PopWork(mShutdownIdle);
 | |
|     } while (true);
 | |
| 
 | |
|     MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   RefPtr<DecodePoolImpl> mImpl;
 | |
|   bool mShutdownIdle;
 | |
| };
 | |
| 
 | |
| /// dav1d (used for AVIF decoding) crashes when decoding some images if the
 | |
| /// stack is too small. See related issue for AV1 decoding: bug 1474684.
 | |
| static constexpr uint32_t DECODE_POOL_STACK_SIZE =
 | |
|     std::max(nsIThreadManager::kThreadPoolStackSize, 512u * 1024u);
 | |
| 
 | |
| bool DecodePoolImpl::CreateThread() {
 | |
|   mMonitor.AssertCurrentThreadOwns();
 | |
|   MOZ_ASSERT(mAvailableThreads > 0);
 | |
| 
 | |
|   bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads;
 | |
|   nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle);
 | |
|   nsCOMPtr<nsIThread> thread;
 | |
|   nsresult rv =
 | |
|       NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
 | |
|                         getter_AddRefs(thread), worker, DECODE_POOL_STACK_SIZE);
 | |
| 
 | |
|   if (NS_FAILED(rv) || !thread) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
 | |
|     return false;
 | |
|   }
 | |
|   thread->SetNameForWakeupTelemetry("ImgDecoder (all)"_ns);
 | |
| 
 | |
|   mThreads.AppendElement(std::move(thread));
 | |
|   --mAvailableThreads;
 | |
|   ++mIdleThreads;
 | |
|   MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void DecodePool::Initialize() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
 | |
|   DecodePool::Singleton();
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| DecodePool* DecodePool::Singleton() {
 | |
|   if (!sSingleton) {
 | |
|     MOZ_ASSERT(NS_IsMainThread());
 | |
|     sSingleton = new DecodePool();
 | |
|     ClearOnShutdown(&sSingleton);
 | |
|   }
 | |
| 
 | |
|   return sSingleton;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| uint32_t DecodePool::NumberOfCores() { return sNumCores; }
 | |
| 
 | |
| #if defined(XP_WIN)
 | |
| class IOThreadIniter final : public Runnable {
 | |
|  public:
 | |
|   explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
 | |
| 
 | |
|   NS_IMETHOD Run() override {
 | |
|     MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|     CoInitialize(nullptr);
 | |
| 
 | |
|     return NS_OK;
 | |
|   }
 | |
| };
 | |
| #endif
 | |
| 
 | |
| DecodePool::DecodePool() : mMutex("image::IOThread") {
 | |
|   // Determine the number of threads we want.
 | |
|   int32_t prefLimit =
 | |
|       StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
 | |
|   uint32_t limit;
 | |
|   if (prefLimit <= 0) {
 | |
|     int32_t numCores = NumberOfCores();
 | |
|     if (numCores <= 1) {
 | |
|       limit = 1;
 | |
|     } else if (numCores == 2) {
 | |
|       // On an otherwise mostly idle system, having two image decoding threads
 | |
|       // doubles decoding performance, so it's worth doing on dual-core devices,
 | |
|       // even if under load we can't actually get that level of parallelism.
 | |
|       limit = 2;
 | |
|     } else {
 | |
|       limit = numCores - 1;
 | |
|     }
 | |
|   } else {
 | |
|     limit = static_cast<uint32_t>(prefLimit);
 | |
|   }
 | |
|   if (limit > 32) {
 | |
|     limit = 32;
 | |
|   }
 | |
|   // The parent process where there are content processes doesn't need as many
 | |
|   // threads for decoding images.
 | |
|   if (limit > 4 && XRE_IsE10sParentProcess()) {
 | |
|     limit = 4;
 | |
|   }
 | |
| 
 | |
|   // The maximum number of idle threads allowed.
 | |
|   uint32_t idleLimit;
 | |
| 
 | |
|   // The timeout period before shutting down idle threads.
 | |
|   int32_t prefIdleTimeout =
 | |
|       StaticPrefs::image_multithreaded_decoding_idle_timeout_AtStartup();
 | |
|   TimeDuration idleTimeout;
 | |
|   if (prefIdleTimeout <= 0) {
 | |
|     idleTimeout = TimeDuration::Forever();
 | |
|     idleLimit = limit;
 | |
|   } else {
 | |
|     idleTimeout = TimeDuration::FromMilliseconds(prefIdleTimeout);
 | |
|     idleLimit = (limit + 1) / 2;
 | |
|   }
 | |
| 
 | |
|   // Initialize the thread pool.
 | |
|   mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout);
 | |
| 
 | |
|   // Initialize the I/O thread.
 | |
| #if defined(XP_WIN)
 | |
|   // On Windows we use the io thread to get icons from the system. Any thread
 | |
|   // that makes system calls needs to call CoInitialize. And these system calls
 | |
|   // (SHGetFileInfo) should only be called from one thread at a time, in case
 | |
|   // we ever create more than on io thread.
 | |
|   nsCOMPtr<nsIRunnable> initer = new IOThreadIniter();
 | |
|   nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread), initer);
 | |
| #else
 | |
|   nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
 | |
| #endif
 | |
|   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
 | |
|                      "Should successfully create image I/O thread");
 | |
| 
 | |
|   nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
 | |
|   if (obsSvc) {
 | |
|     obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| DecodePool::~DecodePool() {
 | |
|   MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
 | |
|   MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
 | |
| 
 | |
|   nsCOMPtr<nsIThread> ioThread;
 | |
| 
 | |
|   {
 | |
|     MutexAutoLock lock(mMutex);
 | |
|     ioThread.swap(mIOThread);
 | |
|   }
 | |
| 
 | |
|   mImpl->Shutdown();
 | |
| 
 | |
|   if (ioThread) {
 | |
|     ioThread->Shutdown();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool DecodePool::IsShuttingDown() const { return mImpl->IsShuttingDown(); }
 | |
| 
 | |
| void DecodePool::AsyncRun(IDecodingTask* aTask) {
 | |
|   MOZ_ASSERT(aTask);
 | |
|   mImpl->PushWork(aTask);
 | |
| }
 | |
| 
 | |
| bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
 | |
|                                     const nsCString& aURI) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aTask);
 | |
| 
 | |
|   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
 | |
|                                         GRAPHICS, aURI);
 | |
| 
 | |
|   if (aTask->ShouldPreferSyncRun()) {
 | |
|     aTask->Run();
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   AsyncRun(aTask);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
 | |
|                                    const nsCString& aURI) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
|   MOZ_ASSERT(aTask);
 | |
| 
 | |
|   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
 | |
|                                         GRAPHICS, aURI);
 | |
| 
 | |
|   aTask->Run();
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() {
 | |
|   MutexAutoLock threadPoolLock(mMutex);
 | |
|   nsCOMPtr<nsIEventTarget> target = mIOThread;
 | |
|   return target.forget();
 | |
| }
 | |
| 
 | |
| }  // namespace image
 | |
| }  // namespace mozilla
 | 
