summaryrefslogtreecommitdiffstats
path: root/image/DecodePool.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/DecodePool.cpp')
-rw-r--r--image/DecodePool.cpp340
1 files changed, 340 insertions, 0 deletions
diff --git a/image/DecodePool.cpp b/image/DecodePool.cpp
new file mode 100644
index 000000000..a8c4cbecc
--- /dev/null
+++ b/image/DecodePool.cpp
@@ -0,0 +1,340 @@
+/* -*- 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/Monitor.h"
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsIThreadPool.h"
+#include "nsThreadManager.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCIDInternal.h"
+#include "prsystem.h"
+
+#include "gfxPrefs.h"
+
+#include "Decoder.h"
+#include "IDecodingTask.h"
+#include "RasterImage.h"
+
+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()
+ : mMonitor("DecodePoolImpl")
+ , mShuttingDown(false)
+ { }
+
+ /// Initialize the current thread for use by the decode pool.
+ void InitCurrentThread()
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mThreadNaming.SetThreadPoolName(NS_LITERAL_CSTRING("ImgDecoder"));
+ }
+
+ /// Shut down the provided decode pool thread.
+ static void ShutdownThread(nsIThread* aThisThread)
+ {
+ // Threads have to be shut down from another thread, so we'll ask the
+ // main thread to do it for us.
+ NS_DispatchToMainThread(NewRunnableMethod(aThisThread, &nsIThread::Shutdown));
+ }
+
+ /**
+ * 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 RequestShutdown()
+ {
+ MonitorAutoLock lock(mMonitor);
+ mShuttingDown = true;
+ mMonitor.NotifyAll();
+ }
+
+ /// 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(Move(task));
+ } else {
+ mLowPriorityQueue.AppendElement(Move(task));
+ }
+
+ mMonitor.Notify();
+ }
+
+ /// Pops a new work item, blocking if necessary.
+ Work PopWork()
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ do {
+ if (!mHighPriorityQueue.IsEmpty()) {
+ return PopWorkFromQueue(mHighPriorityQueue);
+ }
+
+ if (!mLowPriorityQueue.IsEmpty()) {
+ return PopWorkFromQueue(mLowPriorityQueue);
+ }
+
+ if (mShuttingDown) {
+ Work work;
+ work.mType = Work::Type::SHUTDOWN;
+ return work;
+ }
+
+ // Nothing to do; block until some work is available.
+ mMonitor.Wait();
+ } while (true);
+ }
+
+private:
+ ~DecodePoolImpl() { }
+
+ Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue)
+ {
+ Work work;
+ work.mType = Work::Type::TASK;
+ work.mTask = aQueue.LastElement().forget();
+ aQueue.RemoveElementAt(aQueue.Length() - 1);
+
+ return work;
+ }
+
+ nsThreadPoolNaming mThreadNaming;
+
+ // mMonitor guards the queues and mShuttingDown.
+ Monitor mMonitor;
+ nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
+ nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
+ bool mShuttingDown;
+};
+
+class DecodePoolWorker : public Runnable
+{
+public:
+ explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mImpl->InitCurrentThread();
+
+ nsCOMPtr<nsIThread> thisThread;
+ nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
+
+ do {
+ Work work = mImpl->PopWork();
+ switch (work.mType) {
+ case Work::Type::TASK:
+ work.mTask->Run();
+ break;
+
+ case Work::Type::SHUTDOWN:
+ DecodePoolImpl::ShutdownThread(thisThread);
+ return NS_OK;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown work type");
+ }
+ } while (true);
+
+ MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
+ return NS_OK;
+ }
+
+private:
+ RefPtr<DecodePoolImpl> mImpl;
+};
+
+/* 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;
+}
+
+DecodePool::DecodePool()
+ : mImpl(new DecodePoolImpl)
+ , mMutex("image::DecodePool")
+{
+ // Determine the number of threads we want.
+ int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
+ 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;
+ }
+
+ // Initialize the thread pool.
+ for (uint32_t i = 0 ; i < limit ; ++i) {
+ nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl);
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = NS_NewThread(getter_AddRefs(thread), worker);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread,
+ "Should successfully create image decoding threads");
+ mThreads.AppendElement(Move(thread));
+ }
+
+ // Initialize the I/O thread.
+ nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
+ 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");
+
+ nsTArray<nsCOMPtr<nsIThread>> threads;
+ nsCOMPtr<nsIThread> ioThread;
+
+ {
+ MutexAutoLock lock(mMutex);
+ threads.SwapElements(mThreads);
+ ioThread.swap(mIOThread);
+ }
+
+ mImpl->RequestShutdown();
+
+ for (uint32_t i = 0 ; i < threads.Length() ; ++i) {
+ threads[i]->Shutdown();
+ }
+
+ if (ioThread) {
+ ioThread->Shutdown();
+ }
+
+ return NS_OK;
+}
+
+void
+DecodePool::AsyncRun(IDecodingTask* aTask)
+{
+ MOZ_ASSERT(aTask);
+ mImpl->PushWork(aTask);
+}
+
+void
+DecodePool::SyncRunIfPreferred(IDecodingTask* aTask)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTask);
+
+ if (aTask->ShouldPreferSyncRun()) {
+ aTask->Run();
+ return;
+ }
+
+ AsyncRun(aTask);
+}
+
+void
+DecodePool::SyncRunIfPossible(IDecodingTask* aTask)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aTask);
+ aTask->Run();
+}
+
+already_AddRefed<nsIEventTarget>
+DecodePool::GetIOEventTarget()
+{
+ MutexAutoLock threadPoolLock(mMutex);
+ nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
+ return target.forget();
+}
+
+} // namespace image
+} // namespace mozilla