Bug 1577498 - Part 3: Ensure actor and conduit cleanup r=rpl
[gecko.git] / image / DecodePool.cpp
blobcdd6149dd2808bf30f639075fef4b52abcf86e58
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "DecodePool.h"
8 #include <algorithm>
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/Monitor.h"
13 #include "mozilla/StaticPrefs_image.h"
14 #include "mozilla/TimeStamp.h"
15 #include "nsCOMPtr.h"
16 #include "nsIObserverService.h"
17 #include "nsIThreadPool.h"
18 #include "nsThreadManager.h"
19 #include "nsThreadUtils.h"
20 #include "nsXPCOMCIDInternal.h"
21 #include "prsystem.h"
22 #include "nsIXULRuntime.h"
24 #include "Decoder.h"
25 #include "IDecodingTask.h"
26 #include "RasterImage.h"
28 #if defined(XP_WIN)
29 # include <objbase.h>
30 #endif
32 using std::max;
33 using std::min;
35 namespace mozilla {
36 namespace image {
38 ///////////////////////////////////////////////////////////////////////////////
39 // DecodePool implementation.
40 ///////////////////////////////////////////////////////////////////////////////
42 /* static */
43 StaticRefPtr<DecodePool> DecodePool::sSingleton;
44 /* static */
45 uint32_t DecodePool::sNumCores = 0;
47 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
49 struct Work {
50 enum class Type { TASK, SHUTDOWN } mType;
52 RefPtr<IDecodingTask> mTask;
55 class DecodePoolImpl {
56 public:
57 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
58 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
60 DecodePoolImpl(uint8_t aMaxThreads, uint8_t aMaxIdleThreads,
61 TimeDuration aIdleTimeout)
62 : mMonitor("DecodePoolImpl"),
63 mThreads(aMaxThreads),
64 mIdleTimeout(aIdleTimeout),
65 mMaxIdleThreads(aMaxIdleThreads),
66 mAvailableThreads(aMaxThreads),
67 mIdleThreads(0),
68 mShuttingDown(false) {
69 MonitorAutoLock lock(mMonitor);
70 bool success = CreateThread();
71 MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!");
74 /// Shut down the provided decode pool thread.
75 void ShutdownThread(nsIThread* aThisThread, bool aShutdownIdle) {
77 // If this is an idle thread shutdown, then we need to remove it from the
78 // worker array. Process shutdown will move the entire array.
79 MonitorAutoLock lock(mMonitor);
80 if (!mShuttingDown) {
81 ++mAvailableThreads;
82 DebugOnly<bool> removed = mThreads.RemoveElement(aThisThread);
83 MOZ_ASSERT(aShutdownIdle);
84 MOZ_ASSERT(mAvailableThreads < mThreads.Capacity());
85 MOZ_ASSERT(removed);
89 // Threads have to be shut down from another thread, so we'll ask the
90 // main thread to do it for us.
91 SystemGroup::Dispatch(
92 TaskCategory::Other,
93 NewRunnableMethod("DecodePoolImpl::ShutdownThread", aThisThread,
94 &nsIThread::AsyncShutdown));
97 /**
98 * Requests shutdown. New work items will be dropped on the floor, and all
99 * decode pool threads will be shut down once existing work items have been
100 * processed.
102 void Shutdown() {
103 nsTArray<nsCOMPtr<nsIThread>> threads;
106 MonitorAutoLock lock(mMonitor);
107 mShuttingDown = true;
108 mAvailableThreads = 0;
109 threads.SwapElements(mThreads);
110 mMonitor.NotifyAll();
113 for (uint32_t i = 0; i < threads.Length(); ++i) {
114 threads[i]->Shutdown();
118 bool IsShuttingDown() const {
119 MonitorAutoLock lock(mMonitor);
120 return mShuttingDown;
123 /// Pushes a new decode work item.
124 void PushWork(IDecodingTask* aTask) {
125 MOZ_ASSERT(aTask);
126 RefPtr<IDecodingTask> task(aTask);
128 MonitorAutoLock lock(mMonitor);
130 if (mShuttingDown) {
131 // Drop any new work on the floor if we're shutting down.
132 return;
135 if (task->Priority() == TaskPriority::eHigh) {
136 mHighPriorityQueue.AppendElement(std::move(task));
137 } else {
138 mLowPriorityQueue.AppendElement(std::move(task));
141 // If there are pending tasks, create more workers if and only if we have
142 // not exceeded the capacity, and any previously created workers are ready.
143 if (mAvailableThreads) {
144 size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length();
145 if (pending > mIdleThreads) {
146 CreateThread();
150 mMonitor.Notify();
153 Work StartWork(bool aShutdownIdle) {
154 MonitorAutoLock lock(mMonitor);
156 // The thread was already marked as idle when it was created. Once it gets
157 // its first work item, it is assumed it is busy performing that work until
158 // it blocks on the monitor once again.
159 MOZ_ASSERT(mIdleThreads > 0);
160 --mIdleThreads;
161 return PopWorkLocked(aShutdownIdle);
164 Work PopWork(bool aShutdownIdle) {
165 MonitorAutoLock lock(mMonitor);
166 return PopWorkLocked(aShutdownIdle);
169 private:
170 /// Pops a new work item, blocking if necessary.
171 Work PopWorkLocked(bool aShutdownIdle) {
172 mMonitor.AssertCurrentThreadOwns();
174 TimeDuration timeout = mIdleTimeout;
175 do {
176 if (!mHighPriorityQueue.IsEmpty()) {
177 return PopWorkFromQueue(mHighPriorityQueue);
180 if (!mLowPriorityQueue.IsEmpty()) {
181 return PopWorkFromQueue(mLowPriorityQueue);
184 if (mShuttingDown) {
185 return CreateShutdownWork();
188 // Nothing to do; block until some work is available.
189 AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE);
190 if (!aShutdownIdle) {
191 // This thread was created before we hit the idle thread maximum. It
192 // will never shutdown until the process itself is torn down.
193 ++mIdleThreads;
194 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
195 mMonitor.Wait();
196 } else {
197 // This thread should shutdown if it is idle. If we have waited longer
198 // than the timeout period without having done any work, then we should
199 // shutdown the thread.
200 if (timeout.IsZero()) {
201 return CreateShutdownWork();
204 ++mIdleThreads;
205 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
207 TimeStamp now = TimeStamp::Now();
208 mMonitor.Wait(timeout);
209 TimeDuration delta = TimeStamp::Now() - now;
210 if (delta > timeout) {
211 timeout = 0;
212 } else if (timeout != TimeDuration::Forever()) {
213 timeout -= delta;
217 MOZ_ASSERT(mIdleThreads > 0);
218 --mIdleThreads;
219 } while (true);
222 ~DecodePoolImpl() {}
224 bool CreateThread();
226 Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue) {
227 Work work;
228 work.mType = Work::Type::TASK;
229 work.mTask = aQueue.PopLastElement();
231 return work;
234 Work CreateShutdownWork() const {
235 Work work;
236 work.mType = Work::Type::SHUTDOWN;
237 return work;
240 nsThreadPoolNaming mThreadNaming;
242 // mMonitor guards everything below.
243 mutable Monitor mMonitor;
244 nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
245 nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
246 nsTArray<nsCOMPtr<nsIThread>> mThreads;
247 TimeDuration mIdleTimeout;
248 uint8_t mMaxIdleThreads; // Maximum number of workers when idle.
249 uint8_t mAvailableThreads; // How many new threads can be created.
250 uint8_t mIdleThreads; // How many created threads are waiting.
251 bool mShuttingDown;
254 class DecodePoolWorker final : public Runnable {
255 public:
256 explicit DecodePoolWorker(DecodePoolImpl* aImpl, bool aShutdownIdle)
257 : Runnable("image::DecodePoolWorker"),
258 mImpl(aImpl),
259 mShutdownIdle(aShutdownIdle) {}
261 NS_IMETHOD Run() override {
262 MOZ_ASSERT(!NS_IsMainThread());
264 nsCOMPtr<nsIThread> thisThread;
265 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
267 Work work = mImpl->StartWork(mShutdownIdle);
268 do {
269 switch (work.mType) {
270 case Work::Type::TASK:
271 work.mTask->Run();
272 work.mTask = nullptr;
273 break;
275 case Work::Type::SHUTDOWN:
276 mImpl->ShutdownThread(thisThread, mShutdownIdle);
277 PROFILER_UNREGISTER_THREAD();
278 return NS_OK;
280 default:
281 MOZ_ASSERT_UNREACHABLE("Unknown work type");
284 work = mImpl->PopWork(mShutdownIdle);
285 } while (true);
287 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
288 return NS_OK;
291 private:
292 RefPtr<DecodePoolImpl> mImpl;
293 bool mShutdownIdle;
296 bool DecodePoolImpl::CreateThread() {
297 mMonitor.AssertCurrentThreadOwns();
298 MOZ_ASSERT(mAvailableThreads > 0);
300 bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads;
301 nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle);
302 nsCOMPtr<nsIThread> thread;
303 nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
304 getter_AddRefs(thread), worker,
305 nsIThreadManager::kThreadPoolStackSize);
306 if (NS_FAILED(rv) || !thread) {
307 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
308 return false;
310 thread->SetNameForWakeupTelemetry(NS_LITERAL_CSTRING("ImgDecoder (all)"));
312 mThreads.AppendElement(std::move(thread));
313 --mAvailableThreads;
314 ++mIdleThreads;
315 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
316 return true;
319 /* static */
320 void DecodePool::Initialize() {
321 MOZ_ASSERT(NS_IsMainThread());
322 sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
323 DecodePool::Singleton();
326 /* static */
327 DecodePool* DecodePool::Singleton() {
328 if (!sSingleton) {
329 MOZ_ASSERT(NS_IsMainThread());
330 sSingleton = new DecodePool();
331 ClearOnShutdown(&sSingleton);
334 return sSingleton;
337 /* static */
338 uint32_t DecodePool::NumberOfCores() { return sNumCores; }
340 #if defined(XP_WIN)
341 class IOThreadIniter final : public Runnable {
342 public:
343 explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
345 NS_IMETHOD Run() override {
346 MOZ_ASSERT(!NS_IsMainThread());
348 CoInitialize(nullptr);
350 return NS_OK;
353 #endif
355 DecodePool::DecodePool() : mMutex("image::IOThread") {
356 // Determine the number of threads we want.
357 int32_t prefLimit =
358 StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
359 uint32_t limit;
360 if (prefLimit <= 0) {
361 int32_t numCores = NumberOfCores();
362 if (numCores <= 1) {
363 limit = 1;
364 } else if (numCores == 2) {
365 // On an otherwise mostly idle system, having two image decoding threads
366 // doubles decoding performance, so it's worth doing on dual-core devices,
367 // even if under load we can't actually get that level of parallelism.
368 limit = 2;
369 } else {
370 limit = numCores - 1;
372 } else {
373 limit = static_cast<uint32_t>(prefLimit);
375 if (limit > 32) {
376 limit = 32;
378 // The parent process where there are content processes doesn't need as many
379 // threads for decoding images.
380 if (limit > 4 && XRE_IsE10sParentProcess()) {
381 limit = 4;
384 // The maximum number of idle threads allowed.
385 uint32_t idleLimit;
387 // The timeout period before shutting down idle threads.
388 int32_t prefIdleTimeout =
389 StaticPrefs::image_multithreaded_decoding_idle_timeout_AtStartup();
390 TimeDuration idleTimeout;
391 if (prefIdleTimeout <= 0) {
392 idleTimeout = TimeDuration::Forever();
393 idleLimit = limit;
394 } else {
395 idleTimeout = TimeDuration::FromMilliseconds(prefIdleTimeout);
396 idleLimit = (limit + 1) / 2;
399 // Initialize the thread pool.
400 mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout);
402 // Initialize the I/O thread.
403 #if defined(XP_WIN)
404 // On Windows we use the io thread to get icons from the system. Any thread
405 // that makes system calls needs to call CoInitialize. And these system calls
406 // (SHGetFileInfo) should only be called from one thread at a time, in case
407 // we ever create more than on io thread.
408 nsCOMPtr<nsIRunnable> initer = new IOThreadIniter();
409 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread), initer);
410 #else
411 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
412 #endif
413 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
414 "Should successfully create image I/O thread");
416 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
417 if (obsSvc) {
418 obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
422 DecodePool::~DecodePool() {
423 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
426 NS_IMETHODIMP
427 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
428 MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
430 nsCOMPtr<nsIThread> ioThread;
433 MutexAutoLock lock(mMutex);
434 ioThread.swap(mIOThread);
437 mImpl->Shutdown();
439 if (ioThread) {
440 ioThread->Shutdown();
443 return NS_OK;
446 bool DecodePool::IsShuttingDown() const { return mImpl->IsShuttingDown(); }
448 void DecodePool::AsyncRun(IDecodingTask* aTask) {
449 MOZ_ASSERT(aTask);
450 mImpl->PushWork(aTask);
453 bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
454 const nsCString& aURI) {
455 MOZ_ASSERT(NS_IsMainThread());
456 MOZ_ASSERT(aTask);
458 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
459 GRAPHICS, aURI);
461 if (aTask->ShouldPreferSyncRun()) {
462 aTask->Run();
463 return true;
466 AsyncRun(aTask);
467 return false;
470 void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
471 const nsCString& aURI) {
472 MOZ_ASSERT(NS_IsMainThread());
473 MOZ_ASSERT(aTask);
475 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
476 GRAPHICS, aURI);
478 aTask->Run();
481 already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() {
482 MutexAutoLock threadPoolLock(mMutex);
483 nsCOMPtr<nsIEventTarget> target = mIOThread;
484 return target.forget();
487 } // namespace image
488 } // namespace mozilla