gecko-dev/image/DecodePool.cpp
2017-06-26 14:19:58 -07:00

358 lines
8.4 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/Monitor.h"
#include "nsCOMPtr.h"
#include "nsIObserverService.h"
#include "nsIThreadPool.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "nsXPCOMCIDInternal.h"
#include "prsystem.h"
#include "nsIXULRuntime.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)
{ }
/// 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("DecodePoolImpl::ShutdownThread",
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);
}
nsresult CreateThread(nsIThread** aThread, nsIRunnable* aInitialEvent)
{
return NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
aThread, aInitialEvent);
}
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)
: Runnable("image::DecodePoolWorker")
, mImpl(aImpl)
{ }
NS_IMETHOD Run() override
{
MOZ_ASSERT(!NS_IsMainThread());
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);
profiler_unregister_thread();
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;
}
// The parent process where there are content processes doesn't need as many
// threads for decoding images.
if (limit > 4 && XRE_IsE10sParentProcess()) {
limit = 4;
}
// Initialize the thread pool.
for (uint32_t i = 0 ; i < limit ; ++i) {
nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl);
nsCOMPtr<nsIThread> thread;
nsresult rv = mImpl->CreateThread(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);
}
bool
DecodePool::SyncRunIfPreferred(IDecodingTask* aTask, const nsCString& aURI)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTask);
AUTO_PROFILER_LABEL_DYNAMIC("DecodePool::SyncRunIfPreferred", GRAPHICS,
aURI.get());
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("DecodePool::SyncRunIfPossible", GRAPHICS,
aURI.get());
aTask->Run();
}
already_AddRefed<nsIEventTarget>
DecodePool::GetIOEventTarget()
{
MutexAutoLock threadPoolLock(mMutex);
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
return target.forget();
}
} // namespace image
} // namespace mozilla