/src/mozilla-central/image/DecodePool.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
5 | | |
6 | | #include "DecodePool.h" |
7 | | |
8 | | #include <algorithm> |
9 | | |
10 | | #include "mozilla/ClearOnShutdown.h" |
11 | | #include "mozilla/DebugOnly.h" |
12 | | #include "mozilla/Monitor.h" |
13 | | #include "mozilla/TimeStamp.h" |
14 | | #include "nsCOMPtr.h" |
15 | | #include "nsIObserverService.h" |
16 | | #include "nsIThreadPool.h" |
17 | | #include "nsThreadManager.h" |
18 | | #include "nsThreadUtils.h" |
19 | | #include "nsXPCOMCIDInternal.h" |
20 | | #include "prsystem.h" |
21 | | #include "nsIXULRuntime.h" |
22 | | |
23 | | #include "gfxPrefs.h" |
24 | | |
25 | | #include "Decoder.h" |
26 | | #include "IDecodingTask.h" |
27 | | #include "RasterImage.h" |
28 | | |
29 | | using std::max; |
30 | | using std::min; |
31 | | |
32 | | namespace mozilla { |
33 | | namespace image { |
34 | | |
35 | | /////////////////////////////////////////////////////////////////////////////// |
36 | | // DecodePool implementation. |
37 | | /////////////////////////////////////////////////////////////////////////////// |
38 | | |
39 | | /* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton; |
40 | | /* static */ uint32_t DecodePool::sNumCores = 0; |
41 | | |
42 | | NS_IMPL_ISUPPORTS(DecodePool, nsIObserver) |
43 | | |
44 | | struct Work |
45 | | { |
46 | | enum class Type { |
47 | | TASK, |
48 | | SHUTDOWN |
49 | | } mType; |
50 | | |
51 | | RefPtr<IDecodingTask> mTask; |
52 | | }; |
53 | | |
54 | | class DecodePoolImpl |
55 | | { |
56 | | public: |
57 | | MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl) |
58 | | NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl) |
59 | | |
60 | | DecodePoolImpl(uint8_t aMaxThreads, |
61 | | uint8_t aMaxIdleThreads, |
62 | | TimeDuration aIdleTimeout) |
63 | | : mMonitor("DecodePoolImpl") |
64 | | , mThreads(aMaxThreads) |
65 | | , mIdleTimeout(aIdleTimeout) |
66 | | , mMaxIdleThreads(aMaxIdleThreads) |
67 | | , mAvailableThreads(aMaxThreads) |
68 | | , mIdleThreads(0) |
69 | | , mShuttingDown(false) |
70 | 0 | { |
71 | 0 | MonitorAutoLock lock(mMonitor); |
72 | 0 | bool success = CreateThread(); |
73 | 0 | MOZ_RELEASE_ASSERT(success, "Must create first image decoder thread!"); |
74 | 0 | } |
75 | | |
76 | | /// Shut down the provided decode pool thread. |
77 | | void ShutdownThread(nsIThread* aThisThread, bool aShutdownIdle) |
78 | 0 | { |
79 | 0 | { |
80 | 0 | // If this is an idle thread shutdown, then we need to remove it from the |
81 | 0 | // worker array. Process shutdown will move the entire array. |
82 | 0 | MonitorAutoLock lock(mMonitor); |
83 | 0 | if (!mShuttingDown) { |
84 | 0 | ++mAvailableThreads; |
85 | 0 | DebugOnly<bool> removed = mThreads.RemoveElement(aThisThread); |
86 | 0 | MOZ_ASSERT(aShutdownIdle); |
87 | 0 | MOZ_ASSERT(mAvailableThreads < mThreads.Capacity()); |
88 | 0 | MOZ_ASSERT(removed); |
89 | 0 | } |
90 | 0 | } |
91 | 0 |
|
92 | 0 | // Threads have to be shut down from another thread, so we'll ask the |
93 | 0 | // main thread to do it for us. |
94 | 0 | SystemGroup::Dispatch(TaskCategory::Other, |
95 | 0 | NewRunnableMethod("DecodePoolImpl::ShutdownThread", |
96 | 0 | aThisThread, &nsIThread::Shutdown)); |
97 | 0 | } |
98 | | |
99 | | /** |
100 | | * Requests shutdown. New work items will be dropped on the floor, and all |
101 | | * decode pool threads will be shut down once existing work items have been |
102 | | * processed. |
103 | | */ |
104 | | void Shutdown() |
105 | 0 | { |
106 | 0 | nsTArray<nsCOMPtr<nsIThread>> threads; |
107 | 0 |
|
108 | 0 | { |
109 | 0 | MonitorAutoLock lock(mMonitor); |
110 | 0 | mShuttingDown = true; |
111 | 0 | mAvailableThreads = 0; |
112 | 0 | threads.SwapElements(mThreads); |
113 | 0 | mMonitor.NotifyAll(); |
114 | 0 | } |
115 | 0 |
|
116 | 0 | for (uint32_t i = 0 ; i < threads.Length() ; ++i) { |
117 | 0 | threads[i]->Shutdown(); |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | bool IsShuttingDown() const |
122 | 0 | { |
123 | 0 | MonitorAutoLock lock(mMonitor); |
124 | 0 | return mShuttingDown; |
125 | 0 | } |
126 | | |
127 | | /// Pushes a new decode work item. |
128 | | void PushWork(IDecodingTask* aTask) |
129 | 0 | { |
130 | 0 | MOZ_ASSERT(aTask); |
131 | 0 | RefPtr<IDecodingTask> task(aTask); |
132 | 0 |
|
133 | 0 | MonitorAutoLock lock(mMonitor); |
134 | 0 |
|
135 | 0 | if (mShuttingDown) { |
136 | 0 | // Drop any new work on the floor if we're shutting down. |
137 | 0 | return; |
138 | 0 | } |
139 | 0 | |
140 | 0 | if (task->Priority() == TaskPriority::eHigh) { |
141 | 0 | mHighPriorityQueue.AppendElement(std::move(task)); |
142 | 0 | } else { |
143 | 0 | mLowPriorityQueue.AppendElement(std::move(task)); |
144 | 0 | } |
145 | 0 |
|
146 | 0 | // If there are pending tasks, create more workers if and only if we have |
147 | 0 | // not exceeded the capacity, and any previously created workers are ready. |
148 | 0 | if (mAvailableThreads) { |
149 | 0 | size_t pending = mHighPriorityQueue.Length() + mLowPriorityQueue.Length(); |
150 | 0 | if (pending > mIdleThreads) { |
151 | 0 | CreateThread(); |
152 | 0 | } |
153 | 0 | } |
154 | 0 |
|
155 | 0 | mMonitor.Notify(); |
156 | 0 | } |
157 | | |
158 | | Work StartWork(bool aShutdownIdle) |
159 | 0 | { |
160 | 0 | MonitorAutoLock lock(mMonitor); |
161 | 0 |
|
162 | 0 | // The thread was already marked as idle when it was created. Once it gets |
163 | 0 | // its first work item, it is assumed it is busy performing that work until |
164 | 0 | // it blocks on the monitor once again. |
165 | 0 | MOZ_ASSERT(mIdleThreads > 0); |
166 | 0 | --mIdleThreads; |
167 | 0 | return PopWorkLocked(aShutdownIdle); |
168 | 0 | } |
169 | | |
170 | | Work PopWork(bool aShutdownIdle) |
171 | 0 | { |
172 | 0 | MonitorAutoLock lock(mMonitor); |
173 | 0 | return PopWorkLocked(aShutdownIdle); |
174 | 0 | } |
175 | | |
176 | | private: |
177 | | /// Pops a new work item, blocking if necessary. |
178 | | Work PopWorkLocked(bool aShutdownIdle) |
179 | 0 | { |
180 | 0 | mMonitor.AssertCurrentThreadOwns(); |
181 | 0 |
|
182 | 0 | TimeDuration timeout = mIdleTimeout; |
183 | 0 | do { |
184 | 0 | if (!mHighPriorityQueue.IsEmpty()) { |
185 | 0 | return PopWorkFromQueue(mHighPriorityQueue); |
186 | 0 | } |
187 | 0 | |
188 | 0 | if (!mLowPriorityQueue.IsEmpty()) { |
189 | 0 | return PopWorkFromQueue(mLowPriorityQueue); |
190 | 0 | } |
191 | 0 | |
192 | 0 | if (mShuttingDown) { |
193 | 0 | return CreateShutdownWork(); |
194 | 0 | } |
195 | 0 | |
196 | 0 | // Nothing to do; block until some work is available. |
197 | 0 | if (!aShutdownIdle) { |
198 | 0 | // This thread was created before we hit the idle thread maximum. It |
199 | 0 | // will never shutdown until the process itself is torn down. |
200 | 0 | ++mIdleThreads; |
201 | 0 | MOZ_ASSERT(mIdleThreads <= mThreads.Capacity()); |
202 | 0 | mMonitor.Wait(); |
203 | 0 | } else { |
204 | 0 | // This thread should shutdown if it is idle. If we have waited longer |
205 | 0 | // than the timeout period without having done any work, then we should |
206 | 0 | // shutdown the thread. |
207 | 0 | if (timeout.IsZero()) { |
208 | 0 | return CreateShutdownWork(); |
209 | 0 | } |
210 | 0 | |
211 | 0 | ++mIdleThreads; |
212 | 0 | MOZ_ASSERT(mIdleThreads <= mThreads.Capacity()); |
213 | 0 |
|
214 | 0 | TimeStamp now = TimeStamp::Now(); |
215 | 0 | mMonitor.Wait(timeout); |
216 | 0 | TimeDuration delta = TimeStamp::Now() - now; |
217 | 0 | if (delta > timeout) { |
218 | 0 | timeout = 0; |
219 | 0 | } else if (timeout != TimeDuration::Forever()) { |
220 | 0 | timeout -= delta; |
221 | 0 | } |
222 | 0 | } |
223 | 0 |
|
224 | 0 | MOZ_ASSERT(mIdleThreads > 0); |
225 | 0 | --mIdleThreads; |
226 | 0 | } while (true); |
227 | 0 | } |
228 | | |
229 | 0 | ~DecodePoolImpl() { } |
230 | | |
231 | | bool CreateThread(); |
232 | | |
233 | | Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue) |
234 | 0 | { |
235 | 0 | Work work; |
236 | 0 | work.mType = Work::Type::TASK; |
237 | 0 | work.mTask = aQueue.PopLastElement(); |
238 | 0 |
|
239 | 0 | return work; |
240 | 0 | } |
241 | | |
242 | | Work CreateShutdownWork() const |
243 | 0 | { |
244 | 0 | Work work; |
245 | 0 | work.mType = Work::Type::SHUTDOWN; |
246 | 0 | return work; |
247 | 0 | } |
248 | | |
249 | | nsThreadPoolNaming mThreadNaming; |
250 | | |
251 | | // mMonitor guards everything below. |
252 | | mutable Monitor mMonitor; |
253 | | nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue; |
254 | | nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue; |
255 | | nsTArray<nsCOMPtr<nsIThread>> mThreads; |
256 | | TimeDuration mIdleTimeout; |
257 | | uint8_t mMaxIdleThreads; // Maximum number of workers when idle. |
258 | | uint8_t mAvailableThreads; // How many new threads can be created. |
259 | | uint8_t mIdleThreads; // How many created threads are waiting. |
260 | | bool mShuttingDown; |
261 | | }; |
262 | | |
263 | | class DecodePoolWorker final : public Runnable |
264 | | { |
265 | | public: |
266 | | explicit DecodePoolWorker(DecodePoolImpl* aImpl, |
267 | | bool aShutdownIdle) |
268 | | : Runnable("image::DecodePoolWorker") |
269 | | , mImpl(aImpl) |
270 | | , mShutdownIdle(aShutdownIdle) |
271 | 0 | { } |
272 | | |
273 | | NS_IMETHOD Run() override |
274 | 0 | { |
275 | 0 | MOZ_ASSERT(!NS_IsMainThread()); |
276 | 0 |
|
277 | 0 | nsCOMPtr<nsIThread> thisThread; |
278 | 0 | nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread)); |
279 | 0 |
|
280 | 0 | Work work = mImpl->StartWork(mShutdownIdle); |
281 | 0 | do { |
282 | 0 | switch (work.mType) { |
283 | 0 | case Work::Type::TASK: |
284 | 0 | work.mTask->Run(); |
285 | 0 | work.mTask = nullptr; |
286 | 0 | break; |
287 | 0 |
|
288 | 0 | case Work::Type::SHUTDOWN: |
289 | 0 | mImpl->ShutdownThread(thisThread, mShutdownIdle); |
290 | 0 | PROFILER_UNREGISTER_THREAD(); |
291 | 0 | return NS_OK; |
292 | 0 |
|
293 | 0 | default: |
294 | 0 | MOZ_ASSERT_UNREACHABLE("Unknown work type"); |
295 | 0 | } |
296 | 0 |
|
297 | 0 | work = mImpl->PopWork(mShutdownIdle); |
298 | 0 | } while (true); |
299 | 0 |
|
300 | 0 | MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN"); |
301 | 0 | return NS_OK; |
302 | 0 | } |
303 | | |
304 | | private: |
305 | | RefPtr<DecodePoolImpl> mImpl; |
306 | | bool mShutdownIdle; |
307 | | }; |
308 | | |
309 | | bool DecodePoolImpl::CreateThread() |
310 | 0 | { |
311 | 0 | mMonitor.AssertCurrentThreadOwns(); |
312 | 0 | MOZ_ASSERT(mAvailableThreads > 0); |
313 | 0 |
|
314 | 0 | bool shutdownIdle = mThreads.Length() >= mMaxIdleThreads; |
315 | 0 | nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(this, shutdownIdle); |
316 | 0 | nsCOMPtr<nsIThread> thread; |
317 | 0 | nsresult rv = NS_NewNamedThread(mThreadNaming.GetNextThreadName("ImgDecoder"), |
318 | 0 | getter_AddRefs(thread), worker, |
319 | 0 | nsIThreadManager::kThreadPoolStackSize); |
320 | 0 | if (NS_FAILED(rv) || !thread) { |
321 | 0 | MOZ_ASSERT_UNREACHABLE("Should successfully create image decoding threads"); |
322 | 0 | return false; |
323 | 0 | } |
324 | 0 |
|
325 | 0 | mThreads.AppendElement(std::move(thread)); |
326 | 0 | --mAvailableThreads; |
327 | 0 | ++mIdleThreads; |
328 | 0 | MOZ_ASSERT(mIdleThreads <= mThreads.Capacity()); |
329 | 0 | return true; |
330 | 0 | } |
331 | | |
332 | | /* static */ void |
333 | | DecodePool::Initialize() |
334 | 0 | { |
335 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
336 | 0 | sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1); |
337 | 0 | DecodePool::Singleton(); |
338 | 0 | } |
339 | | |
340 | | /* static */ DecodePool* |
341 | | DecodePool::Singleton() |
342 | 0 | { |
343 | 0 | if (!sSingleton) { |
344 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
345 | 0 | sSingleton = new DecodePool(); |
346 | 0 | ClearOnShutdown(&sSingleton); |
347 | 0 | } |
348 | 0 |
|
349 | 0 | return sSingleton; |
350 | 0 | } |
351 | | |
352 | | /* static */ uint32_t |
353 | | DecodePool::NumberOfCores() |
354 | 0 | { |
355 | 0 | return sNumCores; |
356 | 0 | } |
357 | | |
358 | | DecodePool::DecodePool() |
359 | | : mMutex("image::DecodePool") |
360 | 0 | { |
361 | 0 | // Determine the number of threads we want. |
362 | 0 | int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit(); |
363 | 0 | uint32_t limit; |
364 | 0 | if (prefLimit <= 0) { |
365 | 0 | int32_t numCores = NumberOfCores(); |
366 | 0 | if (numCores <= 1) { |
367 | 0 | limit = 1; |
368 | 0 | } else if (numCores == 2) { |
369 | 0 | // On an otherwise mostly idle system, having two image decoding threads |
370 | 0 | // doubles decoding performance, so it's worth doing on dual-core devices, |
371 | 0 | // even if under load we can't actually get that level of parallelism. |
372 | 0 | limit = 2; |
373 | 0 | } else { |
374 | 0 | limit = numCores - 1; |
375 | 0 | } |
376 | 0 | } else { |
377 | 0 | limit = static_cast<uint32_t>(prefLimit); |
378 | 0 | } |
379 | 0 | if (limit > 32) { |
380 | 0 | limit = 32; |
381 | 0 | } |
382 | 0 | // The parent process where there are content processes doesn't need as many |
383 | 0 | // threads for decoding images. |
384 | 0 | if (limit > 4 && XRE_IsE10sParentProcess()) { |
385 | 0 | limit = 4; |
386 | 0 | } |
387 | 0 |
|
388 | 0 | // The maximum number of idle threads allowed. |
389 | 0 | uint32_t idleLimit; |
390 | 0 |
|
391 | 0 | // The timeout period before shutting down idle threads. |
392 | 0 | int32_t prefIdleTimeout = gfxPrefs::ImageMTDecodingIdleTimeout(); |
393 | 0 | TimeDuration idleTimeout; |
394 | 0 | if (prefIdleTimeout <= 0) { |
395 | 0 | idleTimeout = TimeDuration::Forever(); |
396 | 0 | idleLimit = limit; |
397 | 0 | } else { |
398 | 0 | idleTimeout = TimeDuration::FromMilliseconds(prefIdleTimeout); |
399 | 0 | idleLimit = (limit + 1) / 2; |
400 | 0 | } |
401 | 0 |
|
402 | 0 | // Initialize the thread pool. |
403 | 0 | mImpl = new DecodePoolImpl(limit, idleLimit, idleTimeout); |
404 | 0 |
|
405 | 0 | // Initialize the I/O thread. |
406 | 0 | nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread)); |
407 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread, |
408 | 0 | "Should successfully create image I/O thread"); |
409 | 0 |
|
410 | 0 | nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); |
411 | 0 | if (obsSvc) { |
412 | 0 | obsSvc->AddObserver(this, "xpcom-shutdown-threads", false); |
413 | 0 | } |
414 | 0 | } |
415 | | |
416 | | DecodePool::~DecodePool() |
417 | 0 | { |
418 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!"); |
419 | 0 | } |
420 | | |
421 | | NS_IMETHODIMP |
422 | | DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*) |
423 | 0 | { |
424 | 0 | MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic"); |
425 | 0 |
|
426 | 0 | nsCOMPtr<nsIThread> ioThread; |
427 | 0 |
|
428 | 0 | { |
429 | 0 | MutexAutoLock lock(mMutex); |
430 | 0 | ioThread.swap(mIOThread); |
431 | 0 | } |
432 | 0 |
|
433 | 0 | mImpl->Shutdown(); |
434 | 0 |
|
435 | 0 | if (ioThread) { |
436 | 0 | ioThread->Shutdown(); |
437 | 0 | } |
438 | 0 |
|
439 | 0 | return NS_OK; |
440 | 0 | } |
441 | | |
442 | | bool |
443 | | DecodePool::IsShuttingDown() const |
444 | 0 | { |
445 | 0 | return mImpl->IsShuttingDown(); |
446 | 0 | } |
447 | | |
448 | | void |
449 | | DecodePool::AsyncRun(IDecodingTask* aTask) |
450 | 0 | { |
451 | 0 | MOZ_ASSERT(aTask); |
452 | 0 | mImpl->PushWork(aTask); |
453 | 0 | } |
454 | | |
455 | | bool |
456 | | DecodePool::SyncRunIfPreferred(IDecodingTask* aTask, const nsCString& aURI) |
457 | 0 | { |
458 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
459 | 0 | MOZ_ASSERT(aTask); |
460 | 0 |
|
461 | 0 | AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING( |
462 | 0 | "DecodePool::SyncRunIfPreferred", GRAPHICS, aURI); |
463 | 0 |
|
464 | 0 | if (aTask->ShouldPreferSyncRun()) { |
465 | 0 | aTask->Run(); |
466 | 0 | return true; |
467 | 0 | } |
468 | 0 | |
469 | 0 | AsyncRun(aTask); |
470 | 0 | return false; |
471 | 0 | } |
472 | | |
473 | | void |
474 | | DecodePool::SyncRunIfPossible(IDecodingTask* aTask, const nsCString& aURI) |
475 | 0 | { |
476 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
477 | 0 | MOZ_ASSERT(aTask); |
478 | 0 |
|
479 | 0 | AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING( |
480 | 0 | "DecodePool::SyncRunIfPossible", GRAPHICS, aURI); |
481 | 0 |
|
482 | 0 | aTask->Run(); |
483 | 0 | } |
484 | | |
485 | | already_AddRefed<nsIEventTarget> |
486 | | DecodePool::GetIOEventTarget() |
487 | 0 | { |
488 | 0 | MutexAutoLock threadPoolLock(mMutex); |
489 | 0 | nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread); |
490 | 0 | return target.forget(); |
491 | 0 | } |
492 | | |
493 | | } // namespace image |
494 | | } // namespace mozilla |