Coverage Report

Created: 2018-09-25 14:53

/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