DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (31ec81b5d7bb)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsComponentManagerUtils.h"
#include "nsITimer.h"
#include "RasterImage.h"
#include "DiscardTracker.h"
#include "mozilla/Preferences.h"

namespace mozilla {
namespace image {

static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms";

/* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages;
/* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer;
/* static */ bool DiscardTracker::sInitialized = false;
/* static */ bool DiscardTracker::sTimerOn = false;
/* static */ Atomic<int32_t> DiscardTracker::sDiscardRunnablePending(0);
/* static */ int64_t DiscardTracker::sCurrentDecodedImageBytes = 0;
/* static */ uint32_t DiscardTracker::sMinDiscardTimeoutMs = 10000;
/* static */ uint32_t DiscardTracker::sMaxDecodedImageKB = 42 * 1024;
/* static */ PRLock * DiscardTracker::sAllocationLock = nullptr;

/*
 * When we notice we're using too much memory for decoded images, we enqueue a
 * DiscardRunnable, which runs this code.
 */
NS_IMETHODIMP
DiscardTracker::DiscardRunnable::Run()
{
  sDiscardRunnablePending = 0;

  DiscardTracker::DiscardNow();
  return NS_OK;
}

int
DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
{
  DiscardTracker::ReloadTimeout();
  return 0;
}

nsresult
DiscardTracker::Reset(Node *node)
{
  // We shouldn't call Reset() with a null |img| pointer, on images which can't
  // be discarded, or on animated images (which should be marked as
  // non-discardable, anyway).
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(sInitialized);
  MOZ_ASSERT(node->img);
  MOZ_ASSERT(node->img->CanDiscard());
  MOZ_ASSERT(!node->img->mAnim);

  // Insert the node at the front of the list and note when it was inserted.
  bool wasInList = node->isInList();
  if (wasInList) {
    node->remove();
  }
  node->timestamp = TimeStamp::Now();
  sDiscardableImages.insertFront(node);

  // If the node wasn't already in the list of discardable images, then we may
  // need to discard some images to stay under the sMaxDecodedImageKB limit.
  // Call MaybeDiscardSoon to do this check.
  if (!wasInList) {
    MaybeDiscardSoon();
  }

  // Make sure the timer is running.
  nsresult rv = EnableTimer();
  NS_ENSURE_SUCCESS(rv,rv);

  return NS_OK;
}

void
DiscardTracker::Remove(Node *node)
{
  MOZ_ASSERT(NS_IsMainThread());

  if (node->isInList())
    node->remove();

  if (sDiscardableImages.isEmpty())
    DisableTimer();
}

/**
 * Shut down the tracker, deallocating the timer.
 */
void
DiscardTracker::Shutdown()
{
  MOZ_ASSERT(NS_IsMainThread());

  if (sTimer) {
    sTimer->Cancel();
    sTimer = nullptr;
  }

  // Clear the sDiscardableImages linked list so that its destructor
  // (LinkedList.h) finds an empty array, which is required after bug 803688.
  DiscardAll();
}

/*
 * Discard all the images we're tracking.
 */
void
DiscardTracker::DiscardAll()
{
  MOZ_ASSERT(NS_IsMainThread());

  if (!sInitialized)
    return;

  // Be careful: Calling Discard() on an image might cause it to be removed
  // from the list!
  Node *n;
  while ((n = sDiscardableImages.popFirst())) {
    n->img->Discard();
  }

  // The list is empty, so there's no need to leave the timer on.
  DisableTimer();
}

void
DiscardTracker::InformAllocation(int64_t bytes)
{
  // This function is called back e.g. from RasterImage::Discard(); be careful!

  MOZ_ASSERT(sInitialized);

  PR_Lock(sAllocationLock);
  sCurrentDecodedImageBytes += bytes;
  MOZ_ASSERT(sCurrentDecodedImageBytes >= 0);
  PR_Unlock(sAllocationLock);

  // If we're using too much memory for decoded images, MaybeDiscardSoon will
  // enqueue a callback to discard some images.
  MaybeDiscardSoon();
}

/**
 * Initialize the tracker.
 */
nsresult
DiscardTracker::Initialize()
{
  MOZ_ASSERT(NS_IsMainThread());

  // Watch the timeout pref for changes.
  Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
                                sDiscardTimeoutPref);

  Preferences::AddUintVarCache(&sMaxDecodedImageKB,
                              "image.mem.max_decoded_image_kb",
                              50 * 1024);

  // Create the timer.
  sTimer = do_CreateInstance("@mozilla.org/timer;1");

  // Create a lock for safegarding the 64-bit sCurrentDecodedImageBytes
  sAllocationLock = PR_NewLock();

  // Mark us as initialized
  sInitialized = true;

  // Read the timeout pref and start the timer.
  ReloadTimeout();

  return NS_OK;
}

/**
 * Read the discard timeout from about:config.
 */
void
DiscardTracker::ReloadTimeout()
{
  // Read the timeout pref.
  int32_t discardTimeout;
  nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout);

  // If we got something bogus, return.
  if (!NS_SUCCEEDED(rv) || discardTimeout <= 0)
    return;

  // If the value didn't change, return.
  if ((uint32_t) discardTimeout == sMinDiscardTimeoutMs)
    return;

  // Update the value.
  sMinDiscardTimeoutMs = (uint32_t) discardTimeout;

  // Restart the timer so the new timeout takes effect.
  DisableTimer();
  EnableTimer();
}

/**
 * Enables the timer. No-op if the timer is already running.
 */
nsresult
DiscardTracker::EnableTimer()
{
  // Nothing to do if the timer's already on or we haven't yet been
  // initialized.  !sTimer probably means we've shut down, so just ignore that,
  // too.
  if (sTimerOn || !sInitialized || !sTimer)
    return NS_OK;

  sTimerOn = true;

  // Activate the timer.  Have it call us back in (sMinDiscardTimeoutMs / 2)
  // ms, so that an image is discarded between sMinDiscardTimeoutMs and
  // (3/2 * sMinDiscardTimeoutMs) ms after it's unlocked.
  return sTimer->InitWithFuncCallback(TimerCallback,
                                      nullptr,
                                      sMinDiscardTimeoutMs / 2,
                                      nsITimer::TYPE_REPEATING_SLACK);
}

/*
 * Disables the timer. No-op if the timer isn't running.
 */
void
DiscardTracker::DisableTimer()
{
  // Nothing to do if the timer's already off.
  if (!sTimerOn || !sTimer)
    return;
  sTimerOn = false;

  // Deactivate
  sTimer->Cancel();
}

/**
 * Routine activated when the timer fires. This discards everything that's
 * older than sMinDiscardTimeoutMs, and tries to discard enough images so that
 * we go under sMaxDecodedImageKB.
 */
void
DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
{
  DiscardNow();
}

void
DiscardTracker::DiscardNow()
{
  // Assuming the list is ordered with oldest discard tracker nodes at the back
  // and newest ones at the front, iterate from back to front discarding nodes
  // until we encounter one which is new enough to keep and until we go under
  // our sMaxDecodedImageKB limit.

  TimeStamp now = TimeStamp::Now();
  Node* node;
  while ((node = sDiscardableImages.getLast())) {
    if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs ||
        sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024) {

      // Discarding the image should cause sCurrentDecodedImageBytes to
      // decrease via a call to InformAllocation().
      node->img->Discard();

      // Careful: Discarding may have caused the node to have been removed
      // from the list.
      Remove(node);
    }
    else {
      break;
    }
  }

  // If the list is empty, disable the timer.
  if (sDiscardableImages.isEmpty())
    DisableTimer();
}

void
DiscardTracker::MaybeDiscardSoon()
{
  // Are we carrying around too much decoded image data?  If so, enqueue an
  // event to try to get us down under our limit.
  if (sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024 &&
      !sDiscardableImages.isEmpty()) {
    // Check if the value of sDiscardRunnablePending used to be false
    if (!sDiscardRunnablePending.exchange(1)) {
      nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable();
      NS_DispatchToMainThread(runnable);
    }
  }
}

} // namespace image
} // namespace mozilla