Bug 962354 - Create a threadsafe threadpool singleton for media threads. r=kinetik

This commit is contained in:
Chris Pearce 2014-01-23 17:21:05 +13:00
parent 5c90f73c73
commit 1548267f86
5 changed files with 311 additions and 111 deletions

View File

@ -0,0 +1,227 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "SharedThreadPool.h"
#include "mozilla/Monitor.h"
#include "mozilla/StaticPtr.h"
#include "nsDataHashtable.h"
#include "VideoUtils.h"
#include "nsXPCOMCIDInternal.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/Preferences.h"
#ifdef XP_WIN
// Required to init MSCOM by MSCOMInitThreadPoolListener.
#include <Objbase.h>
#endif
namespace mozilla {
// Created and destroyed on the main thread.
static StaticAutoPtr<ReentrantMonitor> sMonitor;
// Hashtable, maps thread pool name to SharedThreadPool instance.
// Modified only on the main thread.
static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>> sPools;
static already_AddRefed<nsIThreadPool>
CreateThreadPool(const nsCString& aName);
static void
DestroySharedThreadPoolHashTable();
void
SharedThreadPool::EnsureInitialized()
{
MOZ_ASSERT(NS_IsMainThread());
if (sMonitor || sPools) {
// Already initalized.
return;
}
sMonitor = new ReentrantMonitor("SharedThreadPool");
sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>();
}
class ShutdownPoolsEvent : public nsRunnable {
public:
NS_IMETHODIMP Run() {
MOZ_ASSERT(NS_IsMainThread());
DestroySharedThreadPoolHashTable();
return NS_OK;
}
};
static void
DestroySharedThreadPoolHashTable()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(sMonitor && sPools);
if (!sPools->Count()) {
// No more SharedThreadPool singletons. Delete the hash table.
// Note we don't need to lock sMonitor, since we only modify the
// hash table on the main thread, and if the hash table is empty
// there are no external references into its contents.
sPools = nullptr;
sMonitor = nullptr;
}
}
TemporaryRef<SharedThreadPool>
SharedThreadPool::Get(const nsCString& aName)
{
MOZ_ASSERT(NS_IsMainThread());
EnsureInitialized();
MOZ_ASSERT(sMonitor);
ReentrantMonitorAutoEnter mon(*sMonitor);
SharedThreadPool* pool = nullptr;
if (!sPools->Get(aName, &pool)) {
nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName));
NS_ENSURE_TRUE(threadPool, nullptr);
pool = new SharedThreadPool(aName, threadPool);
sPools->Put(aName, pool);
}
MOZ_ASSERT(pool);
RefPtr<SharedThreadPool> instance(pool);
return instance.forget();
}
NS_IMETHODIMP_(nsrefcnt) SharedThreadPool::AddRef(void)
{
MOZ_ASSERT(sMonitor);
ReentrantMonitorAutoEnter mon(*sMonitor);
MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
nsrefcnt count = ++mRefCnt;
NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this));
return count;
}
NS_IMETHODIMP_(nsrefcnt) SharedThreadPool::Release(void)
{
MOZ_ASSERT(sMonitor);
bool dispatchShutdownEvent;
{
ReentrantMonitorAutoEnter mon(*sMonitor);
nsrefcnt count = --mRefCnt;
NS_LOG_RELEASE(this, count, "SharedThreadPool");
if (count) {
return count;
}
// Zero refcount. Must shutdown and then delete the thread pool.
// First, dispatch an event to the main thread to call Shutdown() on
// the nsIThreadPool. The Runnable here will add a refcount to the pool,
// and when the Runnable releases the nsIThreadPool it will be deleted.
RefPtr<nsIRunnable> r = NS_NewRunnableMethod(mPool, &nsIThreadPool::Shutdown);
NS_DispatchToMainThread(r);
// Remove SharedThreadPool from table of pools.
sPools->Remove(mName);
MOZ_ASSERT(!sPools->Get(mName));
// Stabilize refcount, so that if something in the dtor QIs,
// it won't explode.
mRefCnt = 1;
delete this;
dispatchShutdownEvent = sPools->Count() == 0;
}
if (dispatchShutdownEvent) {
// No more SharedThreadPools alive. Destroy the hash table.
// Ensure that we only run on the main thread.
// Do this in an event so that if something holds the monitor we won't
// be deleting the monitor while it's held.
NS_DispatchToMainThread(new ShutdownPoolsEvent(), NS_DISPATCH_NORMAL);
}
return 0;
}
NS_IMPL_QUERY_INTERFACE1(SharedThreadPool, nsIThreadPool)
SharedThreadPool::SharedThreadPool(const nsCString& aName,
nsIThreadPool* aPool)
: mName(aName)
, mPool(aPool)
, mRefCnt(0)
{
mEventTarget = do_QueryInterface(aPool);
}
SharedThreadPool::~SharedThreadPool()
{
}
#ifdef XP_WIN
// Thread pool listener which ensures that MSCOM is initialized and
// deinitialized on the thread pool thread. We may call into WMF or
// DirectShow on this thread, so we need MSCOM working.
class MSCOMInitThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADPOOLLISTENER
};
NS_IMPL_ISUPPORTS1(MSCOMInitThreadPoolListener, nsIThreadPoolListener)
NS_IMETHODIMP
MSCOMInitThreadPoolListener::OnThreadCreated()
{
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr)) {
NS_WARNING("Failed to initialize MSCOM on WMFByteStream thread.");
}
return NS_OK;
}
NS_IMETHODIMP
MSCOMInitThreadPoolListener::OnThreadShuttingDown()
{
CoUninitialize();
return NS_OK;
}
#endif // XP_WIN
static already_AddRefed<nsIThreadPool>
CreateThreadPool(const nsCString& aName)
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, nullptr);
rv = pool->SetName(aName);
NS_ENSURE_SUCCESS(rv, nullptr);
// We limit the number of threads that we use for media. Note that the
// default thread limit is the same as the idle limit so that we're not
// constantly creating and destroying threads (see Bug 881954). When the
// thread pool threads shutdown they dispatch an event to the main thread
// to call nsIThread::Shutdown(), and if we're very busy that can take a
// while to run, and we end up with dozens of extra threads. Note that
// threads that are idle for 60 seconds are shutdown naturally.
rv = pool->SetThreadLimit(
Preferences::GetUint("media.thread-pool.thread-limit", 4));
NS_ENSURE_SUCCESS(rv, nullptr);
rv = pool->SetIdleThreadLimit(
Preferences::GetUint("media.thread-pool.idle-thread-limit", 4));
NS_ENSURE_SUCCESS(rv, nullptr);
#ifdef XP_WIN
// Ensure MSCOM is initialized on the thread pools threads.
nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener();
rv = pool->SetListener(listener);
NS_ENSURE_SUCCESS(rv, nullptr);
#endif
return pool.forget();
}
} // namespace mozilla

View File

@ -0,0 +1,77 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef SharedThreadPool_h_
#define SharedThreadPool_h_
#include <queue>
#include "mozilla/RefPtr.h"
#include "nsThreadUtils.h"
#include "nsIThreadPool.h"
#include "nsISupports.h"
#include "nsISupportsImpl.h"
#include "nsCOMPtr.h"
namespace mozilla {
// Wrapper that makes an nsIThreadPool a singleton, and provides a
// consistent threadsafe interface to get instances. Callers simply get a
// SharedThreadPool by the name of its nsIThreadPool. All get requests of
// the same name get the same SharedThreadPool. Users must store a reference
// to the pool, and when the last reference to a SharedThreadPool is dropped
// the pool is shutdown and deleted. Users aren't required to manually
// shutdown the pool, and can release references on any thread. On Windows
// all threads in the pool have MSCOM initialized with COINIT_MULTITHREADED.
class SharedThreadPool : public nsIThreadPool {
public:
// Gets (possibly creating) the shared thread pool singleton instance with
// thread pool named aName.
// *Must* be called on the main thread.
static TemporaryRef<SharedThreadPool> Get(const nsCString& aName);
// We implement custom threadsafe AddRef/Release pair, that destroys the
// the shared pool singleton when the refcount drops to 0. The addref/release
// are implemented using locking, so it's not recommended that you use them
// in a tight loop.
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr);
NS_IMETHOD_(nsrefcnt) AddRef(void);
NS_IMETHOD_(nsrefcnt) Release(void);
// Forward behaviour to wrapped thread pool implementation.
NS_FORWARD_SAFE_NSITHREADPOOL(mPool);
NS_FORWARD_SAFE_NSIEVENTTARGET(mEventTarget);
private:
// Creates necessary statics.
// Main thread only.
static void EnsureInitialized();
// Creates a singleton SharedThreadPool wrapper around aPool.
// aName is the name of the aPool, and is used to lookup the
// SharedThreadPool in the hash table of all created pools.
SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool);
virtual ~SharedThreadPool();
// Name of mPool.
const nsCString mName;
// Thread pool being wrapped.
nsCOMPtr<nsIThreadPool> mPool;
// Refcount. We implement custom ref counting so that the thread pool is
// shutdown in a threadsafe manner and singletonness is preserved.
nsrefcnt mRefCnt;
// mPool QI'd to nsIEventTarget. We cache this, so that we can use
// NS_FORWARD_SAFE_NSIEVENTTARGET above.
nsCOMPtr<nsIEventTarget> mEventTarget;
};
} // namespace mozilla
#endif // SharedThreadPool_h_

View File

@ -85,6 +85,7 @@ EXPORTS += [
'MP3FrameParser.h',
'RtspMediaResource.h',
'SharedBuffer.h',
'SharedThreadPool.h',
'StreamBuffer.h',
'TimeVarying.h',
'TrackUnionStream.h',
@ -131,6 +132,7 @@ UNIFIED_SOURCES += [
'MediaStreamTrack.cpp',
'MP3FrameParser.cpp',
'RtspMediaResource.cpp',
'SharedThreadPool.cpp',
'StreamBuffer.cpp',
'TextTrack.cpp',
'TextTrackCue.cpp',

View File

@ -19,6 +19,7 @@
#include "nsXPCOMCIDInternal.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/DebugOnly.h"
#include "SharedThreadPool.h"
#include <algorithm>
#include <cassert>
@ -31,75 +32,6 @@ PRLogModuleInfo* gWMFByteStreamLog = nullptr;
#define WMF_BS_LOG(...)
#endif
// Limit the number of threads that we use for IO.
static const uint32_t NumWMFIoThreads = 4;
// Thread pool listener which ensures that MSCOM is initialized and
// deinitialized on the thread pool thread. We can call back into WMF
// on this thread, so we need MSCOM working.
class ThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADPOOLLISTENER
};
NS_IMPL_ISUPPORTS1(ThreadPoolListener, nsIThreadPoolListener)
NS_IMETHODIMP
ThreadPoolListener::OnThreadCreated()
{
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hr)) {
NS_WARNING("Failed to initialize MSCOM on WMFByteStream thread.");
}
return NS_OK;
}
NS_IMETHODIMP
ThreadPoolListener::OnThreadShuttingDown()
{
CoUninitialize();
return NS_OK;
}
// Thread pool on which read requests are processed.
// This is created and destroyed on the main thread only.
static nsIThreadPool* sThreadPool = nullptr;
// Counter of the number of WMFByteStreams that are instantiated and that need
// the thread pool. This is read/write on the main thread only.
static int32_t sThreadPoolRefCnt = 0;
class ReleaseWMFByteStreamResourcesEvent MOZ_FINAL : public nsRunnable {
public:
ReleaseWMFByteStreamResourcesEvent(already_AddRefed<MediaResource> aResource)
: mResource(aResource) {}
virtual ~ReleaseWMFByteStreamResourcesEvent() {}
NS_IMETHOD Run() {
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
// Explicitly release the MediaResource reference. We *must* do this on
// the main thread, so we must explicitly release it here, we can't rely
// on the destructor to release it, since if this event runs before its
// dispatch call returns the destructor may run on the non-main thread.
mResource = nullptr;
NS_ASSERTION(sThreadPoolRefCnt > 0, "sThreadPoolRefCnt Should be non-negative");
sThreadPoolRefCnt--;
if (sThreadPoolRefCnt == 0) {
NS_ASSERTION(sThreadPool != nullptr, "Should have thread pool ref if sThreadPoolRefCnt==0.");
// Note: store ref to thread pool, then clear global ref, then
// Shutdown() using the stored ref. Events can run during the Shutdown()
// call, so if we release after calling Shutdown(), another event may
// have incremented the refcnt in the meantime, and have a dangling
// pointer to the now destroyed threadpool!
nsCOMPtr<nsIThreadPool> pool = sThreadPool;
NS_IF_RELEASE(sThreadPool);
pool->Shutdown();
}
return NS_OK;
}
nsRefPtr<MediaResource> mResource;
};
WMFByteStream::WMFByteStream(MediaResource* aResource,
WMFSourceReaderCallback* aSourceReaderCallback)
: mSourceReaderCallback(aSourceReaderCallback),
@ -123,12 +55,6 @@ WMFByteStream::WMFByteStream(MediaResource* aResource,
WMFByteStream::~WMFByteStream()
{
MOZ_COUNT_DTOR(WMFByteStream);
// The WMFByteStream can be deleted from a thread pool thread, so we
// dispatch an event to the main thread to deref the thread pool and
// deref the MediaResource.
nsCOMPtr<nsIRunnable> event =
new ReleaseWMFByteStreamResourcesEvent(mResource.forget());
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
WMF_BS_LOG("[%p] WMFByteStream DTOR", this);
}
@ -137,39 +63,8 @@ WMFByteStream::Init()
{
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
if (!sThreadPool) {
nsresult rv;
nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
sThreadPool = pool;
NS_ADDREF(sThreadPool);
rv = sThreadPool->SetName(NS_LITERAL_CSTRING("WMFByteStream Async Read Pool"));
NS_ENSURE_SUCCESS(rv, rv);
// We limit the number of threads that we use for IO. Note that the thread
// limit is the same as the idle limit so that we're not constantly creating
// and destroying threads. When the thread pool threads shutdown they
// dispatch an event to the main thread to call nsIThread::Shutdown(),
// and if we're very busy that can take a while to run, and we end up with
// dozens of extra threads. Note that threads that are idle for 60 seconds
// are shutdown naturally.
rv = sThreadPool->SetThreadLimit(NumWMFIoThreads);
NS_ENSURE_SUCCESS(rv, rv);
rv = sThreadPool->SetIdleThreadLimit(NumWMFIoThreads);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIThreadPoolListener> listener = new ThreadPoolListener();
rv = sThreadPool->SetListener(listener);
NS_ENSURE_SUCCESS(rv, rv);
}
sThreadPoolRefCnt++;
// Store a ref to the thread pool, so that we keep the pool alive as long as
// we're alive.
mThreadPool = sThreadPool;
mThreadPool = SharedThreadPool::Get(NS_LITERAL_CSTRING("WMFByteStream IO"));
NS_ENSURE_TRUE(mThreadPool, NS_ERROR_FAILURE);
NS_ConvertUTF8toUTF16 contentTypeUTF16(mResource->GetContentType());
if (!contentTypeUTF16.IsEmpty()) {

View File

@ -15,13 +15,12 @@
#include "nsAutoPtr.h"
#include "mozilla/RefPtr.h"
class nsIThreadPool;
namespace mozilla {
class MediaResource;
class ReadRequest;
class WMFSourceReaderCallback;
class SharedThreadPool;
// Wraps a MediaResource around an IMFByteStream interface, so that it can
// be used by the IMFSourceReader. Each WMFByteStream creates a WMF Work Queue
@ -123,7 +122,7 @@ private:
// Reference to the thread pool in which we perform the reads asynchronously.
// Note this is pool is shared amongst all active WMFByteStreams.
nsCOMPtr<nsIThreadPool> mThreadPool;
RefPtr<SharedThreadPool> mThreadPool;
// Reference to the source reader's callback. We use this reference to
// notify threads waiting on a ReadSample() callback to stop waiting