michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "SharedThreadPool.h" michael@0: #include "mozilla/Monitor.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "VideoUtils.h" michael@0: #include "nsXPCOMCIDInternal.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: michael@0: #ifdef XP_WIN michael@0: // Required to init MSCOM by MSCOMInitThreadPoolListener. michael@0: #include michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: // Created and destroyed on the main thread. michael@0: static StaticAutoPtr sMonitor; michael@0: michael@0: // Hashtable, maps thread pool name to SharedThreadPool instance. michael@0: // Modified only on the main thread. michael@0: static StaticAutoPtr> sPools; michael@0: michael@0: static already_AddRefed michael@0: CreateThreadPool(const nsCString& aName); michael@0: michael@0: static void michael@0: DestroySharedThreadPoolHashTable(); michael@0: michael@0: void michael@0: SharedThreadPool::EnsureInitialized() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (sMonitor || sPools) { michael@0: // Already initalized. michael@0: return; michael@0: } michael@0: sMonitor = new ReentrantMonitor("SharedThreadPool"); michael@0: sPools = new nsDataHashtable(); michael@0: } michael@0: michael@0: class ShutdownPoolsEvent : public nsRunnable { michael@0: public: michael@0: NS_IMETHODIMP Run() { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: DestroySharedThreadPoolHashTable(); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: static void michael@0: DestroySharedThreadPoolHashTable() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(sMonitor && sPools); michael@0: if (!sPools->Count()) { michael@0: // No more SharedThreadPool singletons. Delete the hash table. michael@0: // Note we don't need to lock sMonitor, since we only modify the michael@0: // hash table on the main thread, and if the hash table is empty michael@0: // there are no external references into its contents. michael@0: sPools = nullptr; michael@0: sMonitor = nullptr; michael@0: } michael@0: } michael@0: michael@0: /* static */ michael@0: void michael@0: SharedThreadPool::SpinUntilShutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: // Wait until the ShutdownPoolsEvent has been run and shutdown the pool. michael@0: while (sPools) { michael@0: if (!NS_ProcessNextEvent(NS_GetCurrentThread(), true)) { michael@0: break; michael@0: } michael@0: } michael@0: MOZ_ASSERT(!sPools); michael@0: MOZ_ASSERT(!sMonitor); michael@0: } michael@0: michael@0: TemporaryRef michael@0: SharedThreadPool::Get(const nsCString& aName, uint32_t aThreadLimit) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: EnsureInitialized(); michael@0: MOZ_ASSERT(sMonitor); michael@0: ReentrantMonitorAutoEnter mon(*sMonitor); michael@0: SharedThreadPool* pool = nullptr; michael@0: nsresult rv; michael@0: if (!sPools->Get(aName, &pool)) { michael@0: nsCOMPtr threadPool(CreateThreadPool(aName)); michael@0: NS_ENSURE_TRUE(threadPool, nullptr); michael@0: pool = new SharedThreadPool(aName, threadPool); michael@0: michael@0: // Set the thread and idle limits. Note that we don't rely on the michael@0: // EnsureThreadLimitIsAtLeast() call below, as the default thread limit michael@0: // is 4, and if aThreadLimit is less than 4 we'll end up with a pool michael@0: // with 4 threads rather than what we expected; so we'll have unexpected michael@0: // behaviour. michael@0: rv = pool->SetThreadLimit(aThreadLimit); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: rv = pool->SetIdleThreadLimit(aThreadLimit); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: sPools->Put(aName, pool); michael@0: } else if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { michael@0: NS_WARNING("Failed to set limits on thread pool"); michael@0: } michael@0: michael@0: MOZ_ASSERT(pool); michael@0: RefPtr instance(pool); michael@0: return instance.forget(); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) michael@0: { michael@0: MOZ_ASSERT(sMonitor); michael@0: ReentrantMonitorAutoEnter mon(*sMonitor); michael@0: MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); michael@0: nsrefcnt count = ++mRefCnt; michael@0: NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this)); michael@0: return count; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) michael@0: { michael@0: MOZ_ASSERT(sMonitor); michael@0: bool dispatchShutdownEvent; michael@0: { michael@0: ReentrantMonitorAutoEnter mon(*sMonitor); michael@0: nsrefcnt count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "SharedThreadPool"); michael@0: if (count) { michael@0: return count; michael@0: } michael@0: michael@0: // Zero refcount. Must shutdown and then delete the thread pool. michael@0: michael@0: // First, dispatch an event to the main thread to call Shutdown() on michael@0: // the nsIThreadPool. The Runnable here will add a refcount to the pool, michael@0: // and when the Runnable releases the nsIThreadPool it will be deleted. michael@0: RefPtr r = NS_NewRunnableMethod(mPool, &nsIThreadPool::Shutdown); michael@0: NS_DispatchToMainThread(r); michael@0: michael@0: // Remove SharedThreadPool from table of pools. michael@0: sPools->Remove(mName); michael@0: MOZ_ASSERT(!sPools->Get(mName)); michael@0: michael@0: // Stabilize refcount, so that if something in the dtor QIs, michael@0: // it won't explode. michael@0: mRefCnt = 1; michael@0: michael@0: delete this; michael@0: michael@0: dispatchShutdownEvent = sPools->Count() == 0; michael@0: } michael@0: if (dispatchShutdownEvent) { michael@0: // No more SharedThreadPools alive. Destroy the hash table. michael@0: // Ensure that we only run on the main thread. michael@0: // Do this in an event so that if something holds the monitor we won't michael@0: // be deleting the monitor while it's held. michael@0: NS_DispatchToMainThread(new ShutdownPoolsEvent(), NS_DISPATCH_NORMAL); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) michael@0: michael@0: SharedThreadPool::SharedThreadPool(const nsCString& aName, michael@0: nsIThreadPool* aPool) michael@0: : mName(aName) michael@0: , mPool(aPool) michael@0: , mRefCnt(0) michael@0: { michael@0: MOZ_COUNT_CTOR(SharedThreadPool); michael@0: mEventTarget = do_QueryInterface(aPool); michael@0: } michael@0: michael@0: SharedThreadPool::~SharedThreadPool() michael@0: { michael@0: MOZ_COUNT_DTOR(SharedThreadPool); michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: michael@0: // Thread pool listener which ensures that MSCOM is initialized and michael@0: // deinitialized on the thread pool thread. We may call into WMF or michael@0: // DirectShow on this thread, so we need MSCOM working. michael@0: class MSCOMInitThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSITHREADPOOLLISTENER michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(MSCOMInitThreadPoolListener, nsIThreadPoolListener) michael@0: michael@0: NS_IMETHODIMP michael@0: MSCOMInitThreadPoolListener::OnThreadCreated() michael@0: { michael@0: HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); michael@0: if (FAILED(hr)) { michael@0: NS_WARNING("Failed to initialize MSCOM on WMFByteStream thread."); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: MSCOMInitThreadPoolListener::OnThreadShuttingDown() michael@0: { michael@0: CoUninitialize(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #endif // XP_WIN michael@0: michael@0: nsresult michael@0: SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) michael@0: { michael@0: // We limit the number of threads that we use for media. Note that we michael@0: // set the thread limit to the same as the idle limit so that we're not michael@0: // constantly creating and destroying threads (see Bug 881954). When the michael@0: // thread pool threads shutdown they dispatch an event to the main thread michael@0: // to call nsIThread::Shutdown(), and if we're very busy that can take a michael@0: // while to run, and we end up with dozens of extra threads. Note that michael@0: // threads that are idle for 60 seconds are shutdown naturally. michael@0: uint32_t existingLimit = 0; michael@0: nsresult rv; michael@0: michael@0: rv = mPool->GetThreadLimit(&existingLimit); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (aLimit > existingLimit) { michael@0: rv = mPool->SetThreadLimit(aLimit); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = mPool->GetIdleThreadLimit(&existingLimit); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (aLimit > existingLimit) { michael@0: rv = mPool->SetIdleThreadLimit(aLimit); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static already_AddRefed michael@0: CreateThreadPool(const nsCString& aName) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: rv = pool->SetName(aName); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: rv = pool->SetThreadStackSize(MEDIA_THREAD_STACK_SIZE); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: #ifdef XP_WIN michael@0: // Ensure MSCOM is initialized on the thread pools threads. michael@0: nsCOMPtr listener = new MSCOMInitThreadPoolListener(); michael@0: rv = pool->SetListener(listener); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: #endif michael@0: michael@0: return pool.forget(); michael@0: } michael@0: michael@0: } // namespace mozilla