1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/SharedThreadPool.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,277 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "SharedThreadPool.h" 1.11 +#include "mozilla/Monitor.h" 1.12 +#include "mozilla/StaticPtr.h" 1.13 +#include "nsDataHashtable.h" 1.14 +#include "VideoUtils.h" 1.15 +#include "nsXPCOMCIDInternal.h" 1.16 +#include "nsComponentManagerUtils.h" 1.17 + 1.18 +#ifdef XP_WIN 1.19 +// Required to init MSCOM by MSCOMInitThreadPoolListener. 1.20 +#include <objbase.h> 1.21 +#endif 1.22 + 1.23 +namespace mozilla { 1.24 + 1.25 +// Created and destroyed on the main thread. 1.26 +static StaticAutoPtr<ReentrantMonitor> sMonitor; 1.27 + 1.28 +// Hashtable, maps thread pool name to SharedThreadPool instance. 1.29 +// Modified only on the main thread. 1.30 +static StaticAutoPtr<nsDataHashtable<nsCStringHashKey, SharedThreadPool*>> sPools; 1.31 + 1.32 +static already_AddRefed<nsIThreadPool> 1.33 +CreateThreadPool(const nsCString& aName); 1.34 + 1.35 +static void 1.36 +DestroySharedThreadPoolHashTable(); 1.37 + 1.38 +void 1.39 +SharedThreadPool::EnsureInitialized() 1.40 +{ 1.41 + MOZ_ASSERT(NS_IsMainThread()); 1.42 + if (sMonitor || sPools) { 1.43 + // Already initalized. 1.44 + return; 1.45 + } 1.46 + sMonitor = new ReentrantMonitor("SharedThreadPool"); 1.47 + sPools = new nsDataHashtable<nsCStringHashKey, SharedThreadPool*>(); 1.48 +} 1.49 + 1.50 +class ShutdownPoolsEvent : public nsRunnable { 1.51 +public: 1.52 + NS_IMETHODIMP Run() { 1.53 + MOZ_ASSERT(NS_IsMainThread()); 1.54 + DestroySharedThreadPoolHashTable(); 1.55 + return NS_OK; 1.56 + } 1.57 +}; 1.58 + 1.59 +static void 1.60 +DestroySharedThreadPoolHashTable() 1.61 +{ 1.62 + MOZ_ASSERT(NS_IsMainThread()); 1.63 + MOZ_ASSERT(sMonitor && sPools); 1.64 + if (!sPools->Count()) { 1.65 + // No more SharedThreadPool singletons. Delete the hash table. 1.66 + // Note we don't need to lock sMonitor, since we only modify the 1.67 + // hash table on the main thread, and if the hash table is empty 1.68 + // there are no external references into its contents. 1.69 + sPools = nullptr; 1.70 + sMonitor = nullptr; 1.71 + } 1.72 +} 1.73 + 1.74 +/* static */ 1.75 +void 1.76 +SharedThreadPool::SpinUntilShutdown() 1.77 +{ 1.78 + MOZ_ASSERT(NS_IsMainThread()); 1.79 + // Wait until the ShutdownPoolsEvent has been run and shutdown the pool. 1.80 + while (sPools) { 1.81 + if (!NS_ProcessNextEvent(NS_GetCurrentThread(), true)) { 1.82 + break; 1.83 + } 1.84 + } 1.85 + MOZ_ASSERT(!sPools); 1.86 + MOZ_ASSERT(!sMonitor); 1.87 +} 1.88 + 1.89 +TemporaryRef<SharedThreadPool> 1.90 +SharedThreadPool::Get(const nsCString& aName, uint32_t aThreadLimit) 1.91 +{ 1.92 + MOZ_ASSERT(NS_IsMainThread()); 1.93 + EnsureInitialized(); 1.94 + MOZ_ASSERT(sMonitor); 1.95 + ReentrantMonitorAutoEnter mon(*sMonitor); 1.96 + SharedThreadPool* pool = nullptr; 1.97 + nsresult rv; 1.98 + if (!sPools->Get(aName, &pool)) { 1.99 + nsCOMPtr<nsIThreadPool> threadPool(CreateThreadPool(aName)); 1.100 + NS_ENSURE_TRUE(threadPool, nullptr); 1.101 + pool = new SharedThreadPool(aName, threadPool); 1.102 + 1.103 + // Set the thread and idle limits. Note that we don't rely on the 1.104 + // EnsureThreadLimitIsAtLeast() call below, as the default thread limit 1.105 + // is 4, and if aThreadLimit is less than 4 we'll end up with a pool 1.106 + // with 4 threads rather than what we expected; so we'll have unexpected 1.107 + // behaviour. 1.108 + rv = pool->SetThreadLimit(aThreadLimit); 1.109 + NS_ENSURE_SUCCESS(rv, nullptr); 1.110 + 1.111 + rv = pool->SetIdleThreadLimit(aThreadLimit); 1.112 + NS_ENSURE_SUCCESS(rv, nullptr); 1.113 + 1.114 + sPools->Put(aName, pool); 1.115 + } else if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { 1.116 + NS_WARNING("Failed to set limits on thread pool"); 1.117 + } 1.118 + 1.119 + MOZ_ASSERT(pool); 1.120 + RefPtr<SharedThreadPool> instance(pool); 1.121 + return instance.forget(); 1.122 +} 1.123 + 1.124 +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) 1.125 +{ 1.126 + MOZ_ASSERT(sMonitor); 1.127 + ReentrantMonitorAutoEnter mon(*sMonitor); 1.128 + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); 1.129 + nsrefcnt count = ++mRefCnt; 1.130 + NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this)); 1.131 + return count; 1.132 +} 1.133 + 1.134 +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) 1.135 +{ 1.136 + MOZ_ASSERT(sMonitor); 1.137 + bool dispatchShutdownEvent; 1.138 + { 1.139 + ReentrantMonitorAutoEnter mon(*sMonitor); 1.140 + nsrefcnt count = --mRefCnt; 1.141 + NS_LOG_RELEASE(this, count, "SharedThreadPool"); 1.142 + if (count) { 1.143 + return count; 1.144 + } 1.145 + 1.146 + // Zero refcount. Must shutdown and then delete the thread pool. 1.147 + 1.148 + // First, dispatch an event to the main thread to call Shutdown() on 1.149 + // the nsIThreadPool. The Runnable here will add a refcount to the pool, 1.150 + // and when the Runnable releases the nsIThreadPool it will be deleted. 1.151 + RefPtr<nsIRunnable> r = NS_NewRunnableMethod(mPool, &nsIThreadPool::Shutdown); 1.152 + NS_DispatchToMainThread(r); 1.153 + 1.154 + // Remove SharedThreadPool from table of pools. 1.155 + sPools->Remove(mName); 1.156 + MOZ_ASSERT(!sPools->Get(mName)); 1.157 + 1.158 + // Stabilize refcount, so that if something in the dtor QIs, 1.159 + // it won't explode. 1.160 + mRefCnt = 1; 1.161 + 1.162 + delete this; 1.163 + 1.164 + dispatchShutdownEvent = sPools->Count() == 0; 1.165 + } 1.166 + if (dispatchShutdownEvent) { 1.167 + // No more SharedThreadPools alive. Destroy the hash table. 1.168 + // Ensure that we only run on the main thread. 1.169 + // Do this in an event so that if something holds the monitor we won't 1.170 + // be deleting the monitor while it's held. 1.171 + NS_DispatchToMainThread(new ShutdownPoolsEvent(), NS_DISPATCH_NORMAL); 1.172 + } 1.173 + return 0; 1.174 +} 1.175 + 1.176 +NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) 1.177 + 1.178 +SharedThreadPool::SharedThreadPool(const nsCString& aName, 1.179 + nsIThreadPool* aPool) 1.180 + : mName(aName) 1.181 + , mPool(aPool) 1.182 + , mRefCnt(0) 1.183 +{ 1.184 + MOZ_COUNT_CTOR(SharedThreadPool); 1.185 + mEventTarget = do_QueryInterface(aPool); 1.186 +} 1.187 + 1.188 +SharedThreadPool::~SharedThreadPool() 1.189 +{ 1.190 + MOZ_COUNT_DTOR(SharedThreadPool); 1.191 +} 1.192 + 1.193 +#ifdef XP_WIN 1.194 + 1.195 +// Thread pool listener which ensures that MSCOM is initialized and 1.196 +// deinitialized on the thread pool thread. We may call into WMF or 1.197 +// DirectShow on this thread, so we need MSCOM working. 1.198 +class MSCOMInitThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener { 1.199 +public: 1.200 + NS_DECL_THREADSAFE_ISUPPORTS 1.201 + NS_DECL_NSITHREADPOOLLISTENER 1.202 +}; 1.203 + 1.204 +NS_IMPL_ISUPPORTS(MSCOMInitThreadPoolListener, nsIThreadPoolListener) 1.205 + 1.206 +NS_IMETHODIMP 1.207 +MSCOMInitThreadPoolListener::OnThreadCreated() 1.208 +{ 1.209 + HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); 1.210 + if (FAILED(hr)) { 1.211 + NS_WARNING("Failed to initialize MSCOM on WMFByteStream thread."); 1.212 + } 1.213 + return NS_OK; 1.214 +} 1.215 + 1.216 +NS_IMETHODIMP 1.217 +MSCOMInitThreadPoolListener::OnThreadShuttingDown() 1.218 +{ 1.219 + CoUninitialize(); 1.220 + return NS_OK; 1.221 +} 1.222 + 1.223 +#endif // XP_WIN 1.224 + 1.225 +nsresult 1.226 +SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) 1.227 +{ 1.228 + // We limit the number of threads that we use for media. Note that we 1.229 + // set the thread limit to the same as the idle limit so that we're not 1.230 + // constantly creating and destroying threads (see Bug 881954). When the 1.231 + // thread pool threads shutdown they dispatch an event to the main thread 1.232 + // to call nsIThread::Shutdown(), and if we're very busy that can take a 1.233 + // while to run, and we end up with dozens of extra threads. Note that 1.234 + // threads that are idle for 60 seconds are shutdown naturally. 1.235 + uint32_t existingLimit = 0; 1.236 + nsresult rv; 1.237 + 1.238 + rv = mPool->GetThreadLimit(&existingLimit); 1.239 + NS_ENSURE_SUCCESS(rv, rv); 1.240 + if (aLimit > existingLimit) { 1.241 + rv = mPool->SetThreadLimit(aLimit); 1.242 + NS_ENSURE_SUCCESS(rv, rv); 1.243 + } 1.244 + 1.245 + rv = mPool->GetIdleThreadLimit(&existingLimit); 1.246 + NS_ENSURE_SUCCESS(rv, rv); 1.247 + if (aLimit > existingLimit) { 1.248 + rv = mPool->SetIdleThreadLimit(aLimit); 1.249 + NS_ENSURE_SUCCESS(rv, rv); 1.250 + } 1.251 + 1.252 + return NS_OK; 1.253 +} 1.254 + 1.255 +static already_AddRefed<nsIThreadPool> 1.256 +CreateThreadPool(const nsCString& aName) 1.257 +{ 1.258 + MOZ_ASSERT(NS_IsMainThread()); 1.259 + 1.260 + nsresult rv; 1.261 + nsCOMPtr<nsIThreadPool> pool = do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv); 1.262 + NS_ENSURE_SUCCESS(rv, nullptr); 1.263 + 1.264 + rv = pool->SetName(aName); 1.265 + NS_ENSURE_SUCCESS(rv, nullptr); 1.266 + 1.267 + rv = pool->SetThreadStackSize(MEDIA_THREAD_STACK_SIZE); 1.268 + NS_ENSURE_SUCCESS(rv, nullptr); 1.269 + 1.270 +#ifdef XP_WIN 1.271 + // Ensure MSCOM is initialized on the thread pools threads. 1.272 + nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener(); 1.273 + rv = pool->SetListener(listener); 1.274 + NS_ENSURE_SUCCESS(rv, nullptr); 1.275 +#endif 1.276 + 1.277 + return pool.forget(); 1.278 +} 1.279 + 1.280 +} // namespace mozilla