Bug 962354 - Create a threadsafe threadpool singleton for media threads. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Thu, 23 Jan 2014 17:21:05 +1300
changeset 164784 b61f756f9f54d23499ad1302bfb99bd0b4c8eef6
parent 164783 de7009282021ba162819ccdac7de557cca0ad929
child 164785 a90eb14cad0b541b9ee7c762ab1d9a216b71ac9b
push id26060
push usercbook@mozilla.com
push dateThu, 23 Jan 2014 09:18:25 +0000
treeherdermozilla-central@d418cc97bacb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs962354
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 962354 - Create a threadsafe threadpool singleton for media threads. r=kinetik
content/media/SharedThreadPool.cpp
content/media/SharedThreadPool.h
content/media/moz.build
content/media/wmf/WMFByteStream.cpp
content/media/wmf/WMFByteStream.h
new file mode 100644
--- /dev/null
+++ b/content/media/SharedThreadPool.cpp
@@ -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
new file mode 100644
--- /dev/null
+++ b/content/media/SharedThreadPool.h
@@ -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_
\ No newline at end of file
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -80,16 +80,17 @@ EXPORTS += [
     'MediaMetadataManager.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaSegment.h',
     'MediaStreamGraph.h',
     'MP3FrameParser.h',
     'RtspMediaResource.h',
     'SharedBuffer.h',
+    'SharedThreadPool.h',
     'StreamBuffer.h',
     'TimeVarying.h',
     'TrackUnionStream.h',
     'VideoFrameContainer.h',
     'VideoSegment.h',
     'VideoUtils.h',
     'VorbisUtils.h',
 ]
@@ -126,16 +127,17 @@ UNIFIED_SOURCES += [
     'MediaDecoderStateMachine.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamTrack.cpp',
     'MP3FrameParser.cpp',
     'RtspMediaResource.cpp',
+    'SharedThreadPool.cpp',
     'StreamBuffer.cpp',
     'TextTrack.cpp',
     'TextTrackCue.cpp',
     'TextTrackCueList.cpp',
     'TextTrackList.cpp',
     'TextTrackRegion.cpp',
     'TextTrackRegionList.cpp',
     'VideoFrameContainer.cpp',
--- a/content/media/wmf/WMFByteStream.cpp
+++ b/content/media/wmf/WMFByteStream.cpp
@@ -14,97 +14,29 @@
 #include "WMFUtils.h"
 #include "MediaResource.h"
 #include "nsISeekableStream.h"
 #include "mozilla/RefPtr.h"
 #include "nsIThreadPool.h"
 #include "nsXPCOMCIDInternal.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/DebugOnly.h"
+#include "SharedThreadPool.h"
 #include <algorithm>
 #include <cassert>
 
 namespace mozilla {
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* gWMFByteStreamLog = nullptr;
 #define WMF_BS_LOG(...) PR_LOG(gWMFByteStreamLog, PR_LOG_DEBUG, (__VA_ARGS__))
 #else
 #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),
     mResource(aResource),
     mReentrantMonitor("WMFByteStream.Data"),
     mOffset(0),
     mIsShutdown(false)
 {
@@ -118,63 +50,26 @@ WMFByteStream::WMFByteStream(MediaResour
 #endif
   WMF_BS_LOG("[%p] WMFByteStream CTOR", this);
   MOZ_COUNT_CTOR(WMFByteStream);
 }
 
 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);
 }
 
 nsresult
 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()) {
     HRESULT hr = wmf::MFCreateAttributes(byRef(mAttributes), 1);
     NS_ENSURE_TRUE(SUCCEEDED(hr), NS_ERROR_FAILURE);
 
     hr = mAttributes->SetString(MF_BYTESTREAM_CONTENT_TYPE,
                                 contentTypeUTF16.get());
--- a/content/media/wmf/WMFByteStream.h
+++ b/content/media/wmf/WMFByteStream.h
@@ -10,23 +10,22 @@
 
 #include "nsISupportsImpl.h"
 #include "nsCOMPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Attributes.h"
 #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
 // on which blocking I/O is performed. The SourceReader requests reads
 // asynchronously using {Begin,End}Read(), and more rarely synchronously
 // using Read().
 //
 // Note: This implementation attempts to be bug-compatible with Windows Media
@@ -118,17 +117,17 @@ private:
   // call this function.
   nsresult Read(ReadRequest* aRequestState);
 
   // Returns true if the current position of the stream is at end of stream.
   bool IsEOS();
 
   // 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
   // if the stream is closed, which happens when the media element is
   // shutdown.
   RefPtr<WMFSourceReaderCallback> mSourceReaderCallback;
 
   // Resource we're wrapping.