Bug 1610357 [wpt PR 21278] - Update wpt metadata, a=testonly
[gecko.git] / image / DecodePool.cpp
blob3f501533410d0c5415bf47d75d86accc062a2ccb
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 "nsThreadManager.h"
18 #include "nsThreadUtils.h"
19 #include "nsXPCOMCIDInternal.h"
20 #include "prsystem.h"
22 #include "Decoder.h"
23 #include "IDecodingTask.h"
24 #include "RasterImage.h"
26 #if defined(XP_WIN)
27 # include <objbase.h>
28 #endif
30 using std::max;
31 using std::min;
33 namespace mozilla {
34 namespace image {
36 ///////////////////////////////////////////////////////////////////////////////
37 // DecodePool implementation.
38 ///////////////////////////////////////////////////////////////////////////////
40 /* static */
41 StaticRefPtr<DecodePool> DecodePool::sSingleton;
42 /* static */
43 uint32_t DecodePool::sNumCores = 0;
45 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
47 struct Work {
48 enum class Type { TASK, SHUTDOWN } mType;
50 RefPtr<IDecodingTask> mTask;
53 class DecodePoolImpl {
54 public:
55 MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
56 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
58 DecodePoolImpl(uint8_t aMaxThreads, uint8_t aMaxIdleThreads,
59 TimeDuration aIdleTimeout)
60 : mMonitor("DecodePoolImpl"),
61 mThreads(aMaxThreads),
62 mIdleTimeout(aIdleTimeout),
63 mMaxIdleThreads(aMaxIdleThreads),
64 mAvailableThreads(aMaxThreads),
65 mIdleThreads(0),
66 mShuttingDown(false) {
67 MonitorAutoLock lock(mMonitor);
68 bool success = CreateThread();
69 MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!");
72 /// Shut down the provided decode pool thread.
73 void ShutdownThread(nsIThread* aThisThread, bool aShutdownIdle) {
74 bool removed = false;
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 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, but only if we removed it from our thread
91 // pool explicitly. Otherwise we could try to shut down the same thread
92 // twice.
93 if (removed) {
94 SystemGroup::Dispatch(
95 TaskCategory::Other,
96 NewRunnableMethod("DecodePoolImpl::ShutdownThread", aThisThread,
97 &nsIThread::AsyncShutdown));
102 * Requests shutdown. New work items will be dropped on the floor, and all
103 * decode pool threads will be shut down once existing work items have been
104 * processed.
106 void Shutdown() {
107 nsTArray<nsCOMPtr<nsIThread>> threads;
110 MonitorAutoLock lock(mMonitor);
111 mShuttingDown = true;
112 mAvailableThreads = 0;
113 threads.SwapElements(mThreads);
114 mMonitor.NotifyAll();
117 for (uint32_t i = 0; i < threads.Length(); ++i) {
118 threads[i]->Shutdown();
122 bool IsShuttingDown() const {
123 MonitorAutoLock lock(mMonitor);
124 return mShuttingDown;
127 /// Pushes a new decode work item.
128 void PushWork(IDecodingTask* aTask) {
129 MOZ_ASSERT(aTask);
130 RefPtr<IDecodingTask> task(aTask);
132 MonitorAutoLock lock(mMonitor);
134 if (mShuttingDown) {
135 // Drop any new work on the floor if we're shutting down.
136 return;
139 if (task->Priority() == TaskPriority::eHigh) {
140 mHighPriorityQueue.AppendElement(std::move(task));
141 } else {
142 mLowPriorityQueue.AppendElement(std::move(task));
145 // If there are pending tasks, create more workers if and only if we have
146 // not exceeded the capacity, and any previously created workers are ready.
147 if (mAvailableThreads) {
148 size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length();
149 if (pending > mIdleThreads) {
150 CreateThread();
154 mMonitor.Notify();
157 Work StartWork(bool aShutdownIdle) {
158 MonitorAutoLock lock(mMonitor);
160 // The thread was already marked as idle when it was created. Once it gets
161 // its first work item, it is assumed it is busy performing that work until
162 // it blocks on the monitor once again.
163 MOZ_ASSERT(mIdleThreads > 0);
164 --mIdleThreads;
165 return PopWorkLocked(aShutdownIdle);
168 Work PopWork(bool aShutdownIdle) {
169 MonitorAutoLock lock(mMonitor);
170 return PopWorkLocked(aShutdownIdle);
173 private:
174 /// Pops a new work item, blocking if necessary.
175 Work PopWorkLocked(bool aShutdownIdle) {
176 mMonitor.AssertCurrentThreadOwns();
178 TimeDuration timeout = mIdleTimeout;
179 do {
180 if (!mHighPriorityQueue.IsEmpty()) {
181 return PopWorkFromQueue(mHighPriorityQueue);
184 if (!mLowPriorityQueue.IsEmpty()) {
185 return PopWorkFromQueue(mLowPriorityQueue);
188 if (mShuttingDown) {
189 return CreateShutdownWork();
192 // Nothing to do; block until some work is available.
193 AUTO_PROFILER_LABEL("DecodePoolImpl::PopWorkLocked::Wait", IDLE);
194 if (!aShutdownIdle) {
195 // This thread was created before we hit the idle thread maximum. It
196 // will never shutdown until the process itself is torn down.
197 ++mIdleThreads;
198 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
199 mMonitor.Wait();
200 } else {
201 // This thread should shutdown if it is idle. If we have waited longer
202 // than the timeout period without having done any work, then we should
203 // shutdown the thread.
204 if (timeout.IsZero()) {
205 return CreateShutdownWork();
208 ++mIdleThreads;
209 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
211 TimeStamp now = TimeStamp::Now();
212 mMonitor.Wait(timeout);
213 TimeDuration delta = TimeStamp::Now() - now;
214 if (delta > timeout) {
215 timeout = 0;
216 } else if (timeout != TimeDuration::Forever()) {
217 timeout -= delta;
221 MOZ_ASSERT(mIdleThreads > 0);
222 --mIdleThreads;
223 } while (true);
226 ~DecodePoolImpl() {}
228 bool CreateThread();
230 Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue) {
231 Work work;
232 work.mType = Work::Type::TASK;
233 work.mTask = aQueue.PopLastElement();
235 return work;
238 Work CreateShutdownWork() const {
239 Work work;
240 work.mType = Work::Type::SHUTDOWN;
241 return work;
244 nsThreadPoolNaming mThreadNaming;
246 // mMonitor guards everything below.
247 mutable Monitor mMonitor;
248 nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
249 nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
250 nsTArray<nsCOMPtr<nsIThread>> mThreads;
251 TimeDuration mIdleTimeout;
252 uint8_t mMaxIdleThreads; // Maximum number of workers when idle.
253 uint8_t mAvailableThreads; // How many new threads can be created.
254 uint8_t mIdleThreads; // How many created threads are waiting.
255 bool mShuttingDown;
258 class DecodePoolWorker final : public Runnable {
259 public:
260 explicit DecodePoolWorker(DecodePoolImpl* aImpl, bool aShutdownIdle)
261 : Runnable("image::DecodePoolWorker"),
262 mImpl(aImpl),
263 mShutdownIdle(aShutdownIdle) {}
265 NS_IMETHOD Run() override {
266 MOZ_ASSERT(!NS_IsMainThread());
268 nsCOMPtr<nsIThread> thisThread;
269 nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
271 Work work = mImpl->StartWork(mShutdownIdle);
272 do {
273 switch (work.mType) {
274 case Work::Type::TASK:
275 work.mTask->Run();
276 work.mTask = nullptr;
277 break;
279 case Work::Type::SHUTDOWN:
280 mImpl->ShutdownThread(thisThread, mShutdownIdle);
281 PROFILER_UNREGISTER_THREAD();
282 return NS_OK;
284 default:
285 MOZ_ASSERT_UNREACHABLE("Unknown work type");
288 work = mImpl->PopWork(mShutdownIdle);
289 } while (true);
291 MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
292 return NS_OK;
295 private:
296 RefPtr<DecodePoolImpl> mImpl;
297 bool mShutdownIdle;
300 bool DecodePoolImpl::CreateThread() {
301 mMonitor.AssertCurrentThreadOwns();
302 MOZ_ASSERT(mAvailableThreads > 0);
304 bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads;
305 nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle);
306 nsCOMPtr<nsIThread> thread;
307 nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"),
308 getter_AddRefs(thread), worker,
309 nsIThreadManager::kThreadPoolStackSize);
310 if (NS_FAILED(rv) || !thread) {
311 MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads");
312 return false;
314 thread->SetNameForWakeupTelemetry(NS_LITERAL_CSTRING("ImgDecoder (all)"));
316 mThreads.AppendElement(std::move(thread));
317 --mAvailableThreads;
318 ++mIdleThreads;
319 MOZ_ASSERT(mIdleThreads <= mThreads.Capacity());
320 return true;
323 /* static */
324 void DecodePool::Initialize() {
325 MOZ_ASSERT(NS_IsMainThread());
326 sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
327 DecodePool::Singleton();
330 /* static */
331 DecodePool* DecodePool::Singleton() {
332 if (!sSingleton) {
333 MOZ_ASSERT(NS_IsMainThread());
334 sSingleton = new DecodePool();
335 ClearOnShutdown(&sSingleton);
338 return sSingleton;
341 /* static */
342 uint32_t DecodePool::NumberOfCores() { return sNumCores; }
344 #if defined(XP_WIN)
345 class IOThreadIniter final : public Runnable {
346 public:
347 explicit IOThreadIniter() : Runnable("image::IOThreadIniter") {}
349 NS_IMETHOD Run() override {
350 MOZ_ASSERT(!NS_IsMainThread());
352 CoInitialize(nullptr);
354 return NS_OK;
357 #endif
359 DecodePool::DecodePool() : mMutex("image::IOThread") {
360 // Determine the number of threads we want.
361 int32_t prefLimit =
362 StaticPrefs::image_multithreaded_decoding_limit_AtStartup();
363 uint32_t limit;
364 if (prefLimit <= 0) {
365 int32_t numCores = NumberOfCores();
366 if (numCores <= 1) {
367 limit = 1;
368 } else if (numCores == 2) {
369 // On an otherwise mostly idle system, having two image decoding threads
370 // doubles decoding performance, so it's worth doing on dual-core devices,
371 // even if under load we can't actually get that level of parallelism.
372 limit = 2;
373 } else {
374 limit = numCores - 1;
376 } else {
377 limit = static_cast<uint32_t>(prefLimit);
379 if (limit > 32) {
380 limit = 32;
382 // The parent process where there are content processes doesn't need as many
383 // threads for decoding images.
384 if (limit > 4 && XRE_IsE10sParentProcess()) {
385 limit = 4;
388 // The maximum number of idle threads allowed.
389 uint32_t idleLimit;
391 // The timeout period before shutting down idle threads.
392 int32_t prefIdleTimeout =
393 StaticPrefs::image_multithreaded_decoding_idle_timeout_AtStartup();
394 TimeDuration idleTimeout;
395 if (prefIdleTimeout <= 0) {
396 idleTimeout = TimeDuration::Forever();
397 idleLimit = limit;
398 } else {
399 idleTimeout = TimeDuration::FromMilliseconds(prefIdleTimeout);
400 idleLimit = (limit + 1) / 2;
403 // Initialize the thread pool.
404 mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout);
406 // Initialize the I/O thread.
407 #if defined(XP_WIN)
408 // On Windows we use the io thread to get icons from the system. Any thread
409 // that makes system calls needs to call CoInitialize. And these system calls
410 // (SHGetFileInfo) should only be called from one thread at a time, in case
411 // we ever create more than on io thread.
412 nsCOMPtr<nsIRunnable> initer = new IOThreadIniter();
413 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread), initer);
414 #else
415 nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
416 #endif
417 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
418 "Should successfully create image I/O thread");
420 nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
421 if (obsSvc) {
422 obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
426 DecodePool::~DecodePool() {
427 MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
430 NS_IMETHODIMP
431 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) {
432 MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
434 nsCOMPtr<nsIThread> ioThread;
437 MutexAutoLock lock(mMutex);
438 ioThread.swap(mIOThread);
441 mImpl->Shutdown();
443 if (ioThread) {
444 ioThread->Shutdown();
447 return NS_OK;
450 bool DecodePool::IsShuttingDown() const { return mImpl->IsShuttingDown(); }
452 void DecodePool::AsyncRun(IDecodingTask* aTask) {
453 MOZ_ASSERT(aTask);
454 mImpl->PushWork(aTask);
457 bool DecodePool::SyncRunIfPreferred(IDecodingTask* aTask,
458 const nsCString& aURI) {
459 MOZ_ASSERT(NS_IsMainThread());
460 MOZ_ASSERT(aTask);
462 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPreferred",
463 GRAPHICS, aURI);
465 if (aTask->ShouldPreferSyncRun()) {
466 aTask->Run();
467 return true;
470 AsyncRun(aTask);
471 return false;
474 void DecodePool::SyncRunIfPossible(IDecodingTask* aTask,
475 const nsCString& aURI) {
476 MOZ_ASSERT(NS_IsMainThread());
477 MOZ_ASSERT(aTask);
479 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("DecodePool::SyncRunIfPossible",
480 GRAPHICS, aURI);
482 aTask->Run();
485 already_AddRefed<nsIEventTarget> DecodePool::GetIOEventTarget() {
486 MutexAutoLock threadPoolLock(mMutex);
487 nsCOMPtr<nsIEventTarget> target = mIOThread;
488 return target.forget();
491 } // namespace image
492 } // namespace mozilla