Bug 878577 - Part 1: Setup hard limit for DiscardTracker. r=seth
authorShih-Chiang Chien <schien@mozilla.com>
Mon, 16 Dec 2013 19:31:00 +0800
changeset 197884 2fcd144a6282b06000bc19b6a5d994f6d21f6342
parent 197883 a5679c8bc3fe99eab1f79a6f42915167767bc7f3
child 197885 dc2154549c6696e9f0f6e9814851c1caa9a99a05
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseth
bugs878577
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 878577 - Part 1: Setup hard limit for DiscardTracker. r=seth
b2g/app/b2g.js
image/src/DiscardTracker.cpp
image/src/DiscardTracker.h
image/src/imgFrame.cpp
modules/libpref/src/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -300,16 +300,18 @@ pref("media.cache_size", 4096);    // 4M
 // MediaDecoderReader's mVideoQueue.
 pref("media.video-queue.default-size", 3);
 
 // optimize images' memory usage
 pref("image.mem.decodeondraw", true);
 pref("image.mem.allow_locking_in_content_processes", false); /* don't allow image locking */
 pref("image.mem.min_discard_timeout_ms", 86400000); /* 24h, we rely on the out of memory hook */
 pref("image.mem.max_decoded_image_kb", 30000); /* 30MB seems reasonable */
+// 65MB seems reasonable and layout/reftests/bugs/370629-1.html requires more than 62MB
+pref("image.mem.hard_limit_decoded_image_kb", 66560);
 pref("image.onload.decode.limit", 24); /* don't decode more than 24 images eagerly */
 
 // XXX this isn't a good check for "are touch events supported", but
 // we don't really have a better one at the moment.
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
--- a/image/src/DiscardTracker.cpp
+++ b/image/src/DiscardTracker.cpp
@@ -14,19 +14,20 @@ 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<bool> DiscardTracker::sDiscardRunnablePending(false);
-/* static */ int64_t DiscardTracker::sCurrentDecodedImageBytes = 0;
+/* static */ uint64_t DiscardTracker::sCurrentDecodedImageBytes = 0;
 /* static */ uint32_t DiscardTracker::sMinDiscardTimeoutMs = 10000;
 /* static */ uint32_t DiscardTracker::sMaxDecodedImageKB = 42 * 1024;
+/* static */ uint32_t DiscardTracker::sHardLimitDecodedImageKB = 0;
 /* static */ PRLock * DiscardTracker::sAllocationLock = nullptr;
 /* static */ mozilla::Mutex* DiscardTracker::sNodeListMutex = nullptr;
 /* static */ Atomic<bool> DiscardTracker::sShutdown(false);
 
 /*
  * When we notice we're using too much memory for decoded images, we enqueue a
  * DiscardRunnable, which runs this code.
  */
@@ -133,47 +134,68 @@ DiscardTracker::DiscardAll()
   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)
+/* static */ bool
+DiscardTracker::TryAllocation(uint64_t aBytes)
+{
+  MOZ_ASSERT(sInitialized);
+
+  PR_Lock(sAllocationLock);
+  bool enoughSpace =
+    !sHardLimitDecodedImageKB ||
+    (sHardLimitDecodedImageKB * 1024) - sCurrentDecodedImageBytes >= aBytes;
+
+  if (enoughSpace) {
+    sCurrentDecodedImageBytes += aBytes;
+  }
+  PR_Unlock(sAllocationLock);
+
+  // If we're using too much memory for decoded images, MaybeDiscardSoon will
+  // enqueue a callback to discard some images.
+  MaybeDiscardSoon();
+
+  return enoughSpace;
+}
+
+/* static */ void
+DiscardTracker::InformDeallocation(uint64_t aBytes)
 {
   // This function is called back e.g. from RasterImage::Discard(); be careful!
 
   MOZ_ASSERT(sInitialized);
 
   PR_Lock(sAllocationLock);
-  sCurrentDecodedImageBytes += bytes;
-  MOZ_ASSERT(sCurrentDecodedImageBytes >= 0);
+  MOZ_ASSERT(aBytes <= sCurrentDecodedImageBytes);
+  sCurrentDecodedImageBytes -= aBytes;
   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()
 {
   // Watch the timeout pref for changes.
   Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
                                 sDiscardTimeoutPref);
 
   Preferences::AddUintVarCache(&sMaxDecodedImageKB,
                               "image.mem.max_decoded_image_kb",
                               50 * 1024);
 
+  Preferences::AddUintVarCache(&sHardLimitDecodedImageKB,
+                               "image.mem.hard_limit_decoded_image_kb",
+                               0);
   // Create the timer.
   sTimer = do_CreateInstance("@mozilla.org/timer;1");
 
   // Create a lock for safegarding the 64-bit sCurrentDecodedImageBytes
   sAllocationLock = PR_NewLock();
 
   // Create a lock for the node list.
   MOZ_ASSERT(!sNodeListMutex);
@@ -273,17 +295,17 @@ DiscardTracker::DiscardNow()
 
   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().
+      // decrease via a call to InformDeallocation().
       node->img->Discard();
 
       // Careful: Discarding may have caused the node to have been removed
       // from the list.
       Remove(node);
     }
     else {
       break;
--- a/image/src/DiscardTracker.h
+++ b/image/src/DiscardTracker.h
@@ -77,22 +77,30 @@ class DiscardTracker
 
     /**
      * Discard the decoded image data for all images tracked by the discard
      * tracker.  This function is main thread only.
      */
     static void DiscardAll();
 
     /**
-     * Inform the discard tracker that we've allocated or deallocated some
-     * memory for a decoded image.  We use this to determine when we've
-     * allocated too much memory and should discard some images.  This function
-     * can be called from any thread and is thread-safe.
+     * Inform the discard tracker that we are going to allocate some memory
+     * for a decoded image. We use this to determine when we've allocated
+     * too much memory and should discard some images.  This function can be
+     * called from any thread and is thread-safe. If this function succeeds, the
+     * caller is now responsible for ensuring that InformDeallocation is called.
      */
-    static void InformAllocation(int64_t bytes);
+    static bool TryAllocation(uint64_t aBytes);
+
+    /**
+     * Inform the discard tracker that we've deallocated some memory for a
+     * decoded image. This function can be called from any thread and is
+     * thread-safe.
+     */
+    static void InformDeallocation(uint64_t aBytes);
 
   private:
     /**
      * This is called when the discard timer fires; it calls into DiscardNow().
      */
     friend void DiscardTimeoutChangedCallback(const char* aPref, void *aClosure);
 
     /**
@@ -111,19 +119,20 @@ class DiscardTracker
     static void TimerCallback(nsITimer *aTimer, void *aClosure);
     static void DiscardNow();
 
     static LinkedList<Node> sDiscardableImages;
     static nsCOMPtr<nsITimer> sTimer;
     static bool sInitialized;
     static bool sTimerOn;
     static mozilla::Atomic<bool> sDiscardRunnablePending;
-    static int64_t sCurrentDecodedImageBytes;
+    static uint64_t sCurrentDecodedImageBytes;
     static uint32_t sMinDiscardTimeoutMs;
     static uint32_t sMaxDecodedImageKB;
+    static uint32_t sHardLimitDecodedImageKB;
     // Lock for safegarding the 64-bit sCurrentDecodedImageBytes
     static PRLock *sAllocationLock;
     static mozilla::Mutex* sNodeListMutex;
     static Atomic<bool> sShutdown;
 };
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -165,17 +165,17 @@ imgFrame::imgFrame() :
 }
 
 imgFrame::~imgFrame()
 {
   moz_free(mPalettedImageData);
   mPalettedImageData = nullptr;
 
   if (mInformedDiscardTracker) {
-    DiscardTracker::InformAllocation(-4 * mSize.height * mSize.width);
+    DiscardTracker::InformDeallocation(4 * mSize.height * mSize.width);
   }
 }
 
 nsresult imgFrame::Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight,
                         gfxImageFormat aFormat, uint8_t aPaletteDepth /* = 0 */)
 {
   // assert for properties that should be verified by decoders, warn for properties related to bad content
   if (!AllowedImageSize(aWidth, aHeight)) {
@@ -198,16 +198,21 @@ nsresult imgFrame::Init(int32_t aX, int3
     }
 
     // Use the fallible allocator here
     mPalettedImageData = (uint8_t*)moz_malloc(PaletteDataLength() + GetImageDataLength());
     if (!mPalettedImageData)
       NS_WARNING("moz_malloc for paletted image data should succeed");
     NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
   } else {
+    // Inform the discard tracker that we are going to allocate some memory.
+    if (!DiscardTracker::TryAllocation(4 * mSize.width * mSize.height)) {
+      NS_WARNING("Exceed the hard limit of decode image size");
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
     // For Windows, we must create the device surface first (if we're
     // going to) so that the image surface can wrap it.  Can't be done
     // the other way around.
 #ifdef USE_WIN_SURFACE
     if (!ShouldUseImageSurfaces()) {
       mWinSurface = new gfxWindowsSurface(gfxIntSize(mSize.width, mSize.height), mFormat);
       if (mWinSurface && mWinSurface->CairoStatus() == 0) {
         // no error
@@ -233,34 +238,32 @@ nsresult imgFrame::Init(int32_t aX, int3
     if (!mImageSurface || mImageSurface->CairoStatus()) {
       mImageSurface = nullptr;
       // guess
       if (!mImageSurface) {
         NS_WARNING("Allocation of gfxImageSurface should succeed");
       } else if (!mImageSurface->CairoStatus()) {
         NS_WARNING("gfxImageSurface should have good CairoStatus");
       }
+
+      // Image surface allocation is failed, need to return
+      // the booked buffer size.
+      DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height);
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
+    mInformedDiscardTracker = true;
+
 #ifdef XP_MACOSX
     if (!ShouldUseImageSurfaces()) {
       mQuartzSurface = new gfxQuartzImageSurface(mImageSurface);
     }
 #endif
   }
 
-  // Inform the discard tracker that we've allocated some memory, but only if
-  // we're not a paletted image (paletted images are not usually large and are
-  // used only for animated frames, which we don't discard).
-  if (!mPalettedImageData) {
-    DiscardTracker::InformAllocation(4 * mSize.width * mSize.height);
-    mInformedDiscardTracker = true;
-  }
-
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gDisableOptimize)
@@ -309,17 +312,17 @@ nsresult imgFrame::Optimize()
 #ifdef XP_MACOSX
         mQuartzSurface = nullptr;
 #endif
         mDrawSurface = nullptr;
 
         // We just dumped most of our allocated memory, so tell the discard
         // tracker that we're not using any at all.
         if (mInformedDiscardTracker) {
-          DiscardTracker::InformAllocation(-4 * mSize.width * mSize.height);
+          DiscardTracker::InformDeallocation(4 * mSize.width * mSize.height);
           mInformedDiscardTracker = false;
         }
 
         return NS_OK;
       }
     }
 
     // if it's not RGB24/ARGB32, don't optimize, but we never hit this at the moment
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3913,16 +3913,20 @@ pref("image.mem.decode_bytes_at_a_time",
 
 // The longest time we can spend in an iteration of an async decode
 pref("image.mem.max_ms_before_yield", 5);
 
 // The maximum amount of decoded image data we'll willingly keep around (we
 // might keep around more than this, but we'll try to get down to this value).
 pref("image.mem.max_decoded_image_kb", 51200);
 
+// Hard limit for the amount of decoded image data, 0 means we don't have the
+// hard limit for it.
+pref("image.mem.hard_limit_decoded_image_kb", 0);
+
 // Minimum timeout for expiring unused images from the surface cache, in
 // milliseconds. This controls how long we store cached temporary surfaces.
 pref("image.mem.surfacecache.min_expiration_ms", 60000); // 60ms
 
 // Maximum size for the surface cache, in kilobytes.
 pref("image.mem.surfacecache.max_size_kb", 102400); // 100MB
 
 // The surface cache's size, within the constraints of the maximum size set