image/src/DiscardTracker.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 file,
     4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "nsComponentManagerUtils.h"
     7 #include "nsITimer.h"
     8 #include "RasterImage.h"
     9 #include "DiscardTracker.h"
    10 #include "mozilla/Preferences.h"
    12 namespace mozilla {
    13 namespace image {
    15 static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms";
    17 /* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages;
    18 /* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer;
    19 /* static */ bool DiscardTracker::sInitialized = false;
    20 /* static */ bool DiscardTracker::sTimerOn = false;
    21 /* static */ Atomic<bool> DiscardTracker::sDiscardRunnablePending(false);
    22 /* static */ uint64_t DiscardTracker::sCurrentDecodedImageBytes = 0;
    23 /* static */ uint32_t DiscardTracker::sMinDiscardTimeoutMs = 10000;
    24 /* static */ uint32_t DiscardTracker::sMaxDecodedImageKB = 42 * 1024;
    25 /* static */ uint32_t DiscardTracker::sHardLimitDecodedImageKB = 0;
    26 /* static */ PRLock * DiscardTracker::sAllocationLock = nullptr;
    27 /* static */ mozilla::Mutex* DiscardTracker::sNodeListMutex = nullptr;
    28 /* static */ Atomic<bool> DiscardTracker::sShutdown(false);
    30 /*
    31  * When we notice we're using too much memory for decoded images, we enqueue a
    32  * DiscardRunnable, which runs this code.
    33  */
    34 NS_IMETHODIMP
    35 DiscardTracker::DiscardRunnable::Run()
    36 {
    37   sDiscardRunnablePending = false;
    39   DiscardTracker::DiscardNow();
    40   return NS_OK;
    41 }
    43 void
    44 DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
    45 {
    46   DiscardTracker::ReloadTimeout();
    47 }
    49 nsresult
    50 DiscardTracker::Reset(Node *node)
    51 {
    52   // We shouldn't call Reset() with a null |img| pointer, on images which can't
    53   // be discarded, or on animated images (which should be marked as
    54   // non-discardable, anyway).
    55   MutexAutoLock lock(*sNodeListMutex);
    56   MOZ_ASSERT(sInitialized);
    57   MOZ_ASSERT(node->img);
    58   MOZ_ASSERT(node->img->CanDiscard());
    59   MOZ_ASSERT(!node->img->mAnim);
    61   // Insert the node at the front of the list and note when it was inserted.
    62   bool wasInList = node->isInList();
    63   if (wasInList) {
    64     node->remove();
    65   }
    66   node->timestamp = TimeStamp::Now();
    67   sDiscardableImages.insertFront(node);
    69   // If the node wasn't already in the list of discardable images, then we may
    70   // need to discard some images to stay under the sMaxDecodedImageKB limit.
    71   // Call MaybeDiscardSoon to do this check.
    72   if (!wasInList) {
    73     MaybeDiscardSoon();
    74   }
    76   // Make sure the timer is running.
    77   nsresult rv = EnableTimer();
    78   NS_ENSURE_SUCCESS(rv,rv);
    80   return NS_OK;
    81 }
    83 void
    84 DiscardTracker::Remove(Node *node)
    85 {
    86   if (sShutdown) {
    87     // Already shutdown. List should be empty, so just return.
    88     return;
    89   }
    90   MutexAutoLock lock(*sNodeListMutex);
    92   if (node->isInList())
    93     node->remove();
    95   if (sDiscardableImages.isEmpty())
    96     DisableTimer();
    97 }
    99 /**
   100  * Shut down the tracker, deallocating the timer.
   101  */
   102 void
   103 DiscardTracker::Shutdown()
   104 {
   105   sShutdown = true;
   107   if (sTimer) {
   108     sTimer->Cancel();
   109     sTimer = nullptr;
   110   }
   112   // Clear the sDiscardableImages linked list so that its destructor
   113   // (LinkedList.h) finds an empty array, which is required after bug 803688.
   114   DiscardAll();
   116   delete sNodeListMutex;
   117   sNodeListMutex = nullptr;
   118 }
   120 /*
   121  * Discard all the images we're tracking.
   122  */
   123 void
   124 DiscardTracker::DiscardAll()
   125 {
   126   MutexAutoLock lock(*sNodeListMutex);
   128   if (!sInitialized)
   129     return;
   131   // Be careful: Calling Discard() on an image might cause it to be removed
   132   // from the list!
   133   Node *n;
   134   while ((n = sDiscardableImages.popFirst())) {
   135     n->img->Discard();
   136   }
   138   // The list is empty, so there's no need to leave the timer on.
   139   DisableTimer();
   140 }
   142 /* static */ bool
   143 DiscardTracker::TryAllocation(uint64_t aBytes)
   144 {
   145   MOZ_ASSERT(sInitialized);
   147   PR_Lock(sAllocationLock);
   148   bool enoughSpace =
   149     !sHardLimitDecodedImageKB ||
   150     (sHardLimitDecodedImageKB * 1024) - sCurrentDecodedImageBytes >= aBytes;
   152   if (enoughSpace) {
   153     sCurrentDecodedImageBytes += aBytes;
   154   }
   155   PR_Unlock(sAllocationLock);
   157   // If we're using too much memory for decoded images, MaybeDiscardSoon will
   158   // enqueue a callback to discard some images.
   159   MaybeDiscardSoon();
   161   return enoughSpace;
   162 }
   164 /* static */ void
   165 DiscardTracker::InformDeallocation(uint64_t aBytes)
   166 {
   167   // This function is called back e.g. from RasterImage::Discard(); be careful!
   169   MOZ_ASSERT(sInitialized);
   171   PR_Lock(sAllocationLock);
   172   MOZ_ASSERT(aBytes <= sCurrentDecodedImageBytes);
   173   sCurrentDecodedImageBytes -= aBytes;
   174   PR_Unlock(sAllocationLock);
   175 }
   177 /**
   178  * Initialize the tracker.
   179  */
   180 nsresult
   181 DiscardTracker::Initialize()
   182 {
   183   // Watch the timeout pref for changes.
   184   Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
   185                                 sDiscardTimeoutPref);
   187   Preferences::AddUintVarCache(&sMaxDecodedImageKB,
   188                               "image.mem.max_decoded_image_kb",
   189                               50 * 1024);
   191   Preferences::AddUintVarCache(&sHardLimitDecodedImageKB,
   192                                "image.mem.hard_limit_decoded_image_kb",
   193                                0);
   194   // Create the timer.
   195   sTimer = do_CreateInstance("@mozilla.org/timer;1");
   197   // Create a lock for safegarding the 64-bit sCurrentDecodedImageBytes
   198   sAllocationLock = PR_NewLock();
   200   // Create a lock for the node list.
   201   MOZ_ASSERT(!sNodeListMutex);
   202   sNodeListMutex = new Mutex("image::DiscardTracker");
   204   // Mark us as initialized
   205   sInitialized = true;
   207   // Read the timeout pref and start the timer.
   208   ReloadTimeout();
   210   return NS_OK;
   211 }
   213 /**
   214  * Read the discard timeout from about:config.
   215  */
   216 void
   217 DiscardTracker::ReloadTimeout()
   218 {
   219   // Read the timeout pref.
   220   int32_t discardTimeout;
   221   nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout);
   223   // If we got something bogus, return.
   224   if (!NS_SUCCEEDED(rv) || discardTimeout <= 0)
   225     return;
   227   // If the value didn't change, return.
   228   if ((uint32_t) discardTimeout == sMinDiscardTimeoutMs)
   229     return;
   231   // Update the value.
   232   sMinDiscardTimeoutMs = (uint32_t) discardTimeout;
   234   // Restart the timer so the new timeout takes effect.
   235   DisableTimer();
   236   EnableTimer();
   237 }
   239 /**
   240  * Enables the timer. No-op if the timer is already running.
   241  */
   242 nsresult
   243 DiscardTracker::EnableTimer()
   244 {
   245   // Nothing to do if the timer's already on or we haven't yet been
   246   // initialized.  !sTimer probably means we've shut down, so just ignore that,
   247   // too.
   248   if (sTimerOn || !sInitialized || !sTimer)
   249     return NS_OK;
   251   sTimerOn = true;
   253   // Activate the timer.  Have it call us back in (sMinDiscardTimeoutMs / 2)
   254   // ms, so that an image is discarded between sMinDiscardTimeoutMs and
   255   // (3/2 * sMinDiscardTimeoutMs) ms after it's unlocked.
   256   return sTimer->InitWithFuncCallback(TimerCallback,
   257                                       nullptr,
   258                                       sMinDiscardTimeoutMs / 2,
   259                                       nsITimer::TYPE_REPEATING_SLACK);
   260 }
   262 /*
   263  * Disables the timer. No-op if the timer isn't running.
   264  */
   265 void
   266 DiscardTracker::DisableTimer()
   267 {
   268   // Nothing to do if the timer's already off.
   269   if (!sTimerOn || !sTimer)
   270     return;
   271   sTimerOn = false;
   273   // Deactivate
   274   sTimer->Cancel();
   275 }
   277 /**
   278  * Routine activated when the timer fires. This discards everything that's
   279  * older than sMinDiscardTimeoutMs, and tries to discard enough images so that
   280  * we go under sMaxDecodedImageKB.
   281  */
   282 void
   283 DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
   284 {
   285   DiscardNow();
   286 }
   288 void
   289 DiscardTracker::DiscardNow()
   290 {
   291   // Assuming the list is ordered with oldest discard tracker nodes at the back
   292   // and newest ones at the front, iterate from back to front discarding nodes
   293   // until we encounter one which is new enough to keep and until we go under
   294   // our sMaxDecodedImageKB limit.
   296   TimeStamp now = TimeStamp::Now();
   297   Node* node;
   298   while ((node = sDiscardableImages.getLast())) {
   299     if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs ||
   300         sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024) {
   302       // Discarding the image should cause sCurrentDecodedImageBytes to
   303       // decrease via a call to InformDeallocation().
   304       node->img->Discard();
   306       // Careful: Discarding may have caused the node to have been removed
   307       // from the list.
   308       Remove(node);
   309     }
   310     else {
   311       break;
   312     }
   313   }
   315   // If the list is empty, disable the timer.
   316   if (sDiscardableImages.isEmpty())
   317     DisableTimer();
   318 }
   320 void
   321 DiscardTracker::MaybeDiscardSoon()
   322 {
   323   // Are we carrying around too much decoded image data?  If so, enqueue an
   324   // event to try to get us down under our limit.
   325   if (sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024 &&
   326       !sDiscardableImages.isEmpty()) {
   327     // Check if the value of sDiscardRunnablePending used to be false
   328     if (!sDiscardRunnablePending.exchange(true)) {
   329       nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable();
   330       NS_DispatchToMainThread(runnable);
   331     }
   332   }
   333 }
   335 } // namespace image
   336 } // namespace mozilla

mercurial