mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 10:00:54 +00:00
Bug 962354 - Create a threadsafe threadpool singleton for media threads. r=kinetik
This commit is contained in:
parent
5c90f73c73
commit
1548267f86
227
content/media/SharedThreadPool.cpp
Normal file
227
content/media/SharedThreadPool.cpp
Normal 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
|
77
content/media/SharedThreadPool.h
Normal file
77
content/media/SharedThreadPool.h
Normal 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_
|
@ -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',
|
||||
|
@ -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()) {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user