Bug 1104622 (Part 1) - Remove DiscardTracker. r=tn

This commit is contained in:
Seth Fowler 2014-11-27 19:55:57 -08:00
parent 8123b9e88a
commit b3a334e0e2
20 changed files with 93 additions and 700 deletions

View File

@ -329,15 +329,6 @@ 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 */
// At this point 'max_decoded_image_kb' only applies to animated images. They're
// unfortunately fairly large, so this pref still needs to be somewhat generous,
// but it makes sense to reduce it since most types of images are now in the
// surface cache. Once animated images are stored in the surface cache too, this
// pref will go away; see bug 977459. The same goes for
// 'hard_limit_decoded_image_kb'; the surface cache limits are all hard.
pref("image.mem.max_decoded_image_kb", 30000);
pref("image.mem.hard_limit_decoded_image_kb", 66560);
// Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
// Almost everything that was factored into 'max_decoded_image_kb' is now stored
// in the surface cache. 1/8 of main memory is 32MB on a 256MB device, which is

View File

@ -236,8 +236,6 @@ private:
DECL_GFX_PREF(Live, "image.mem.decode_bytes_at_a_time", ImageMemDecodeBytesAtATime, uint32_t, 200000);
DECL_GFX_PREF(Live, "image.mem.decodeondraw", ImageMemDecodeOnDraw, bool, false);
DECL_GFX_PREF(Live, "image.mem.discardable", ImageMemDiscardable, bool, false);
DECL_GFX_PREF(Live, "image.mem.hard_limit_decoded_image_kb", ImageMemHardLimitDecodedImageKB, uint32_t, 0);
DECL_GFX_PREF(Live, "image.mem.max_decoded_image_kb", ImageMemMaxDecodedImageKB, uint32_t, 50*1024);
DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield", ImageMemMaxMSBeforeYield, uint32_t, 400);
DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb", ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);

View File

@ -19,7 +19,6 @@
#include "imgRequest.h"
#include "imgRequestProxy.h"
#include "imgTools.h"
#include "DiscardTracker.h"
#include "nsICOEncoder.h"
#include "nsPNGEncoder.h"
@ -92,7 +91,6 @@ mozilla::image::InitModule()
gfxPrefs::GetSingleton();
mozilla::image::ShutdownTracker::Initialize();
mozilla::image::DiscardTracker::Initialize();
mozilla::image::ImageFactory::Initialize();
mozilla::image::RasterImage::Initialize();
mozilla::image::SurfaceCache::Initialize();
@ -109,7 +107,6 @@ mozilla::image::ShutdownModule()
}
imgLoader::Shutdown();
mozilla::image::SurfaceCache::Shutdown();
mozilla::image::DiscardTracker::Shutdown();
sInitialized = false;
}

View File

@ -1,328 +0,0 @@
/* -*- 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"
#include "gfxPrefs.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<bool> DiscardTracker::sDiscardRunnablePending(false);
/* static */ uint64_t DiscardTracker::sCurrentDecodedImageBytes = 0;
/* static */ uint32_t DiscardTracker::sMinDiscardTimeoutMs = 10000;
/* static */ PRLock * DiscardTracker::sAllocationLock = nullptr;
/* static */ 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.
*/
NS_IMETHODIMP
DiscardTracker::DiscardRunnable::Run()
{
sDiscardRunnablePending = false;
DiscardTracker::DiscardNow();
return NS_OK;
}
void
DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
{
DiscardTracker::ReloadTimeout();
}
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).
MutexAutoLock lock(*sNodeListMutex);
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 gfxPrefs::ImageMemMaxDecodedImageKB() 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)
{
if (sShutdown) {
// Already shutdown. List should be empty, so just return.
return;
}
MutexAutoLock lock(*sNodeListMutex);
if (node->isInList())
node->remove();
if (sDiscardableImages.isEmpty())
DisableTimer();
}
/**
* Shut down the tracker, deallocating the timer.
*/
void
DiscardTracker::Shutdown()
{
sShutdown = true;
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();
delete sNodeListMutex;
sNodeListMutex = nullptr;
}
/*
* Discard all the images we're tracking.
*/
void
DiscardTracker::DiscardAll()
{
MutexAutoLock lock(*sNodeListMutex);
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();
}
/* static */ bool
DiscardTracker::TryAllocation(uint64_t aBytes)
{
MOZ_ASSERT(sInitialized);
PR_Lock(sAllocationLock);
bool enoughSpace =
!gfxPrefs::ImageMemHardLimitDecodedImageKB() ||
(gfxPrefs::ImageMemHardLimitDecodedImageKB() * 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);
MOZ_ASSERT(aBytes <= sCurrentDecodedImageBytes);
sCurrentDecodedImageBytes -= aBytes;
PR_Unlock(sAllocationLock);
}
/**
* Initialize the tracker.
*/
nsresult
DiscardTracker::Initialize()
{
// Watch the timeout pref for changes.
Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
sDiscardTimeoutPref);
// 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);
sNodeListMutex = new Mutex("image::DiscardTracker");
// 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 gfxPrefs::ImageMemMaxDecodedImageKB().
*/
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 gfxPrefs::ImageMemMaxDecodedImageKB() limit.
TimeStamp now = TimeStamp::Now();
Node* node;
while ((node = sDiscardableImages.getLast())) {
if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs ||
sCurrentDecodedImageBytes > gfxPrefs::ImageMemMaxDecodedImageKB() * 1024) {
// Discarding the image should cause sCurrentDecodedImageBytes to
// 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;
}
}
// 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 > gfxPrefs::ImageMemMaxDecodedImageKB() * 1024 &&
!sDiscardableImages.isEmpty()) {
// Check if the value of sDiscardRunnablePending used to be false
if (!sDiscardRunnablePending.exchange(true)) {
nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable();
NS_DispatchToMainThread(runnable);
}
}
}
} // namespace image
} // namespace mozilla

View File

@ -1,140 +0,0 @@
/* -*- 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/. */
#ifndef mozilla_imagelib_DiscardTracker_h_
#define mozilla_imagelib_DiscardTracker_h_
#include "mozilla/Atomics.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Mutex.h"
#include "mozilla/TimeStamp.h"
#include "prlock.h"
#include "nsThreadUtils.h"
#include "nsAutoPtr.h"
class nsITimer;
namespace mozilla {
namespace image {
class RasterImage;
/**
* This static class maintains a linked list of RasterImage objects which are
* eligible for discarding.
*
* When Reset() is called, the node is removed from its position in the list
* (if it was there before) and appended to the beginnings of the list.
*
* Periodically (on a timer and when we notice that we're using more memory
* than we'd like for decoded images), we go through the list and discard
* decoded data from images at the end of the list.
*/
class DiscardTracker
{
public:
/**
* The DiscardTracker keeps a linked list of Node objects. Each object
* points to a RasterImage and contains a timestamp indicating when the
* node was inserted into the tracker.
*
* This structure is embedded within each RasterImage object, and we do
* |mDiscardTrackerNode.img = this| on RasterImage construction. Thus, a
* RasterImage must always call DiscardTracker::Remove() in its destructor
* to avoid having the tracker point to bogus memory.
*/
struct Node : public LinkedListElement<Node>
{
RasterImage *img;
TimeStamp timestamp;
};
/**
* Add an image to the front of the tracker's list, or move it to the front
* if it's already in the list. This function is main thread only.
*/
static nsresult Reset(struct Node* node);
/**
* Remove a node from the tracker; do nothing if the node is currently
* untracked. This function is main thread only.
*/
static void Remove(struct Node* node);
/**
* Initializes the discard tracker. This function is main thread only.
*/
static nsresult Initialize();
/**
* Shut the discard tracker down. This should be called on XPCOM shutdown
* so we destroy the discard timer's nsITimer. This function is main thread
* only.
*/
static void Shutdown();
/**
* 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 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 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);
/**
* When run, this runnable sets sDiscardRunnablePending to false and calls
* DiscardNow().
*/
class DiscardRunnable : public nsRunnable
{
NS_IMETHOD Run();
};
static void ReloadTimeout();
static nsresult EnableTimer();
static void DisableTimer();
static void MaybeDiscardSoon();
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 Atomic<bool> sDiscardRunnablePending;
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 Mutex* sNodeListMutex;
static Atomic<bool> sShutdown;
};
} // namespace image
} // namespace mozilla
#endif /* mozilla_imagelib_DiscardTracker_h_ */

View File

@ -98,6 +98,10 @@ DynamicImage::OnNewSourceData()
return NS_OK;
}
void
DynamicImage::OnSurfaceDiscarded()
{ }
void
DynamicImage::SetInnerWindowID(uint64_t aInnerWindowId)
{ }

View File

@ -58,6 +58,8 @@ public:
bool aLastPart) MOZ_OVERRIDE;
virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
virtual void OnSurfaceDiscarded() MOZ_OVERRIDE;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE;
virtual uint64_t InnerWindowID() const MOZ_OVERRIDE;

View File

@ -127,6 +127,12 @@ public:
*/
virtual nsresult OnNewSourceData() = 0;
/**
* Called when the SurfaceCache discards a persistent surface belonging to
* this image.
*/
virtual void OnSurfaceDiscarded() = 0;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) = 0;
virtual uint64_t InnerWindowID() const = 0;
@ -156,6 +162,8 @@ public:
virtual uint32_t GetAnimationConsumers() MOZ_OVERRIDE { return mAnimationConsumers; }
#endif
virtual void OnSurfaceDiscarded() MOZ_OVERRIDE { }
virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE {
mInnerWindowId = aInnerWindowId;
}

View File

@ -102,6 +102,12 @@ ImageWrapper::OnNewSourceData()
return mInnerImage->OnNewSourceData();
}
void
ImageWrapper::OnSurfaceDiscarded()
{
return mInnerImage->OnSurfaceDiscarded();
}
void
ImageWrapper::SetInnerWindowID(uint64_t aInnerWindowId)
{

View File

@ -47,6 +47,8 @@ public:
bool aLastPart) MOZ_OVERRIDE;
virtual nsresult OnNewSourceData() MOZ_OVERRIDE;
virtual void OnSurfaceDiscarded() MOZ_OVERRIDE;
virtual void SetInnerWindowID(uint64_t aInnerWindowId) MOZ_OVERRIDE;
virtual uint64_t InnerWindowID() const MOZ_OVERRIDE;

View File

@ -136,22 +136,6 @@ static int num_discardable_containers;
static int64_t total_source_bytes;
static int64_t discardable_source_bytes;
/* Are we globally disabling image discarding? */
static bool
DiscardingEnabled()
{
static bool inited;
static bool enabled;
if (!inited) {
inited = true;
enabled = (PR_GetEnv("MOZ_DISABLE_IMAGE_DISCARD") == nullptr);
}
return enabled;
}
class ScaleRunner : public nsRunnable
{
enum ScaleState
@ -328,8 +312,6 @@ RasterImage::RasterImage(ProgressTracker* aProgressTracker,
{
mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
// Set up the discard tracker node.
mDiscardTrackerNode.img = this;
Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
// Statistics
@ -371,10 +353,6 @@ RasterImage::~RasterImage()
// Total statistics
num_containers--;
total_source_bytes -= mSourceData.Length();
if (NS_IsMainThread()) {
DiscardTracker::Remove(&mDiscardTrackerNode);
}
}
/* static */ void
@ -603,8 +581,8 @@ RasterImage::LookupFrame(uint32_t aFrameNum,
if (!ref) {
// The OS threw this frame away. We need to discard and redecode.
MOZ_ASSERT(!mAnim, "Animated frames should be locked");
if (CanForciblyDiscardAndRedecode()) {
Discard(/* aForce = */ true, /* aNotify = */ false);
if (CanDiscard()) {
Discard(/* aNotify = */ false);
ApplyDecodeFlags(aFlags);
WantDecodedFrames(aFlags, aShouldSyncNotify);
@ -706,6 +684,14 @@ RasterImage::FrameRect(uint32_t aWhichFrame)
return nsIntRect();
}
void
RasterImage::OnSurfaceDiscarded()
{
if (mProgressTracker) {
mProgressTracker->OnDiscard();
}
}
uint32_t
RasterImage::GetNumFrames() const
{
@ -940,7 +926,7 @@ RasterImage::GetImageContainer(LayerManager* aManager, ImageContainer **_retval)
NS_ADDREF(*_retval);
// We only need to be careful about holding on to the image when it is
// discardable by the OS.
if (CanForciblyDiscardAndRedecode()) {
if (CanDiscard()) {
mImageContainerCache = mImageContainer;
mImageContainer = nullptr;
}
@ -1120,11 +1106,13 @@ RasterImage::ApplyDecodeFlags(uint32_t aNewFlags)
// if we can't discard, then we're screwed; we have no way
// to re-decode. Similarly if we aren't allowed to do a sync
// decode.
if (!(aNewFlags & FLAG_SYNC_DECODE))
if (!(aNewFlags & FLAG_SYNC_DECODE)) {
return false;
if (!CanForciblyDiscardAndRedecode())
}
if (!CanDiscard()) {
return false;
ForceDiscard();
}
Discard();
}
mFrameDecodeFlags = aNewFlags & DECODE_FLAGS_MASK;
@ -1237,13 +1225,6 @@ RasterImage::DecodingComplete(imgFrame* aFinalFrame)
mDecoded = true;
mHasBeenDecoded = true;
// We now have one of the qualifications for discarding. Re-evaluate.
if (CanDiscard()) {
NS_ABORT_IF_FALSE(!DiscardingActive(),
"We shouldn't have been discardable before this");
DiscardTracker::Reset(&mDiscardTrackerNode);
}
bool singleFrame = GetNumFrames() == 1;
// If there's only 1 frame, mark it as optimizable. Optimizing animated images
@ -1572,11 +1553,6 @@ RasterImage::DoImageDataComplete()
mSourceData.Length()));
}
// We now have one of the qualifications for discarding. Re-evaluate.
if (CanDiscard()) {
nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode);
CONTAINER_ENSURE_SUCCESS(rv);
}
return NS_OK;
}
@ -1737,12 +1713,11 @@ RasterImage::GetKeys(uint32_t *count, char ***keys)
}
void
RasterImage::Discard(bool aForce, bool aNotify)
RasterImage::Discard(bool aNotify)
{
MOZ_ASSERT(NS_IsMainThread());
// We should be ok for discard
NS_ABORT_IF_FALSE(aForce ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!");
MOZ_ASSERT(CanDiscard(), "Asked to discard but can't");
// We should never discard when we have an active decoder
NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
@ -1772,10 +1747,6 @@ RasterImage::Discard(bool aForce, bool aNotify)
mDecodeStatus = DecodeStatus::INACTIVE;
if (aForce) {
DiscardTracker::Remove(&mDiscardTrackerNode);
}
// Log
PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
("CompressedImageAccounting: discarded uncompressed image "
@ -1792,35 +1763,13 @@ RasterImage::Discard(bool aForce, bool aNotify)
discardable_source_bytes));
}
// Helper method to determine if we can discard an image
bool
RasterImage::CanDiscard() {
return (DiscardingEnabled() && // Globally enabled...
mDiscardable && // ...Enabled at creation time...
(mLockCount == 0) && // ...not temporarily disabled...
mHasSourceData && // ...have the source data...
mDecoded); // ...and have something to discard.
}
bool
RasterImage::CanForciblyDiscard() {
return mHasSourceData; // ...have the source data...
}
bool
RasterImage::CanForciblyDiscardAndRedecode() {
return mHasSourceData && // ...have the source data...
!mDecoder && // Can't discard with an open decoder
!mAnim; // Can never discard animated images
}
// Helper method to tell us whether the clock is currently running for
// discarding this image. Mainly for assertions.
bool
RasterImage::DiscardingActive() {
return mDiscardTrackerNode.isInList();
}
// Helper method to determine if we're storing the source data in a buffer
// or just writing it directly to the decoder
bool
@ -1840,9 +1789,6 @@ RasterImage::InitDecoder(bool aDoSizeDecode)
// We shouldn't be firing up a decoder if we already have the frames decoded
NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!");
// Since we're not decoded, we should not have a discard timer active
NS_ABORT_IF_FALSE(!DiscardingActive(), "Discard Timer active in InitDecoder()!");
// Make sure we actually get size before doing a full decode.
if (!aDoSizeDecode) {
NS_ABORT_IF_FALSE(mHasSize, "Must do a size decode before a full decode!");
@ -1995,16 +1941,6 @@ RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount, DecodeStrategy
nsresult
RasterImage::WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify)
{
nsresult rv;
// If we can discard, the clock should be running. Reset it.
if (CanDiscard()) {
NS_ABORT_IF_FALSE(DiscardingActive(),
"Decoded and discardable but discarding not activated!");
rv = DiscardTracker::Reset(&mDiscardTrackerNode);
CONTAINER_ENSURE_SUCCESS(rv);
}
// Request a decode, which does nothing if we're already decoded.
if (aShouldSyncNotify) {
// We can sync notify, which means we can also sync decode.
@ -2230,9 +2166,10 @@ RasterImage::SyncDecode()
// If we've finished decoding we need to discard so we can re-decode
// with the new flags. If we can't discard then there isn't
// anything we can do.
if (!CanForciblyDiscardAndRedecode())
if (!CanDiscard()) {
return NS_ERROR_NOT_AVAILABLE;
ForceDiscard();
}
Discard();
}
}
@ -2452,24 +2389,14 @@ RasterImage::Draw(gfxContext* aContext,
(mFrameDecodeFlags == FLAG_DECODE_NO_PREMULTIPLY_ALPHA) && IsOpaque();
if (!(haveDefaultFlags || haveSafeAlphaFlags)) {
if (!CanForciblyDiscardAndRedecode())
if (!CanDiscard()) {
return NS_ERROR_NOT_AVAILABLE;
ForceDiscard();
}
Discard();
mFrameDecodeFlags = DECODE_FLAGS_DEFAULT;
}
// If this image is a candidate for discarding, reset its position in the
// discard tracker so we're less likely to discard it right away.
//
// (We don't normally draw unlocked images, so this conditition will usually
// be false. But we will draw unlocked images if image locking is globally
// disabled via the image.mem.allow_locking_in_content_processes pref.)
if (DiscardingActive()) {
DiscardTracker::Reset(&mDiscardTrackerNode);
}
if (IsUnlocked() && mProgressTracker) {
mProgressTracker->OnUnlockedDraw();
}
@ -2520,9 +2447,6 @@ RasterImage::LockImage()
if (mError)
return NS_ERROR_FAILURE;
// Cancel the discard timer if it's there
DiscardTracker::Remove(&mDiscardTrackerNode);
// Increment the lock count
mLockCount++;
@ -2550,9 +2474,6 @@ RasterImage::UnlockImage()
if (mLockCount == 0)
return NS_ERROR_ABORT;
// We're locked, so discarding should not be active
NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated");
// Decrement our lock count
mLockCount--;
@ -2566,23 +2487,16 @@ RasterImage::UnlockImage()
// decoded data around), try to cancel the decode and throw away whatever
// we've decoded.
if (mHasBeenDecoded && mDecoder &&
mLockCount == 0 && CanForciblyDiscard()) {
mLockCount == 0 && CanDiscard()) {
PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
("RasterImage[0x%p] canceling decode because image "
"is now unlocked.", this));
ReentrantMonitorAutoEnter lock(mDecodingMonitor);
FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
ForceDiscard();
Discard();
return NS_OK;
}
// Otherwise, we might still be a candidate for discarding in the future. If
// we are, add ourselves to the discard tracker.
if (CanDiscard()) {
nsresult rv = DiscardTracker::Reset(&mDiscardTrackerNode);
CONTAINER_ENSURE_SUCCESS(rv);
}
return NS_OK;
}
@ -2591,8 +2505,11 @@ RasterImage::UnlockImage()
NS_IMETHODIMP
RasterImage::RequestDiscard()
{
if (CanDiscard() && CanForciblyDiscardAndRedecode()) {
ForceDiscard();
if (mDiscardable && // Enabled at creation time...
mLockCount == 0 && // ...not temporarily disabled...
mDecoded && // ...and have something to discard.
CanDiscard()) {
Discard();
}
return NS_OK;

View File

@ -26,7 +26,6 @@
#include "imgFrame.h"
#include "nsThreadUtils.h"
#include "DecodePool.h"
#include "DiscardTracker.h"
#include "Orientation.h"
#include "nsIObserver.h"
#include "mozilla/Maybe.h"
@ -161,6 +160,7 @@ public:
nsresult Init(const char* aMimeType,
uint32_t aFlags);
virtual nsIntRect FrameRect(uint32_t aWhichFrame) MOZ_OVERRIDE;
virtual void OnSurfaceDiscarded() MOZ_OVERRIDE;
// Raster-specific methods
static NS_METHOD WriteToRasterImage(nsIInputStream* aIn, void* aClosure,
@ -176,8 +176,7 @@ public:
MallocSizeOf aMallocSizeOf) const;
/* Triggers discarding. */
void Discard(bool aForce = false, bool aNotify = true);
void ForceDiscard() { Discard(/* aForce = */ true); }
void Discard(bool aNotify = true);
/* Callbacks for decoders */
/** Sets the size and inherent orientation of the container. This should only
@ -364,15 +363,12 @@ private: // data
// we maybe decoding on draw).
UniquePtr<FrameAnimator> mAnim;
// Discard members
// Image locking.
uint32_t mLockCount;
DiscardTracker::Node mDiscardTrackerNode;
// Source data members
nsCString mSourceDataMimeType;
friend class DiscardTracker;
// How many times we've decoded this image.
// This is currently only used for statistics
int32_t mDecodeCount;
@ -497,9 +493,6 @@ private: // data
// Helpers
bool CanDiscard();
bool CanForciblyDiscard();
bool CanForciblyDiscardAndRedecode();
bool DiscardingActive();
bool StoringSourceData() const;
protected:

View File

@ -21,6 +21,7 @@
#include "gfxPlatform.h"
#include "gfxPrefs.h"
#include "imgFrame.h"
#include "Image.h"
#include "nsAutoPtr.h"
#include "nsExpirationTracker.h"
#include "nsHashKeys.h"
@ -351,11 +352,16 @@ public:
void Remove(CachedSurface* aSurface)
{
MOZ_ASSERT(aSurface, "Should have a surface");
const ImageKey imageKey = aSurface->GetImageKey();
ImageKey imageKey = aSurface->GetImageKey();
nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
// If the surface was persistent, tell its image that we discarded it.
if (aSurface->GetLifetime() == Lifetime::Persistent) {
static_cast<Image*>(imageKey)->OnSurfaceDiscarded();
}
StopTracking(aSurface);
cache->Remove(aSurface);

View File

@ -6,7 +6,6 @@
#include "imgFrame.h"
#include "ImageRegion.h"
#include "DiscardTracker.h"
#include "ShutdownTracker.h"
#include "prenv.h"
@ -20,6 +19,7 @@ static bool gDisableOptimize = false;
#include "GeckoProfiler.h"
#include "mozilla/Likely.h"
#include "MainThreadUtils.h"
#include "mozilla/MemoryReporting.h"
#include "nsMargin.h"
#include "mozilla/CheckedInt.h"
@ -140,8 +140,7 @@ imgFrame::imgFrame() :
mCompositingFailed(false),
mHasNoAlpha(false),
mNonPremult(false),
mOptimizable(false),
mInformedDiscardTracker(false)
mOptimizable(false)
{
static bool hasCheckedOptimize = false;
if (!hasCheckedOptimize) {
@ -156,10 +155,6 @@ imgFrame::~imgFrame()
{
moz_free(mPalettedImageData);
mPalettedImageData = nullptr;
if (mInformedDiscardTracker) {
DiscardTracker::InformDeallocation(4 * mSize.height * mSize.width);
}
}
nsresult
@ -198,13 +193,6 @@ imgFrame::InitForDecoder(const nsIntSize& aImageSize,
} else {
MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?");
// Inform the discard tracker that we are going to allocate some memory.
mInformedDiscardTracker =
DiscardTracker::TryAllocation(4 * mSize.width * mSize.height);
if (!mInformedDiscardTracker) {
NS_WARNING("Exceeded the image decode size hard limit");
return NS_ERROR_OUT_OF_MEMORY;
}
mVBuf = AllocateBufferForImage(mSize, mFormat);
if (!mVBuf) {
return NS_ERROR_OUT_OF_MEMORY;
@ -246,14 +234,6 @@ imgFrame::InitWithDrawable(gfxDrawable* aDrawable,
mFormat = aFormat;
mPaletteDepth = 0;
// Inform the discard tracker that we are going to allocate some memory.
mInformedDiscardTracker =
DiscardTracker::TryAllocation(4 * mSize.width * mSize.height);
if (!mInformedDiscardTracker) {
NS_WARNING("Exceed the image decode size hard limit");
return NS_ERROR_OUT_OF_MEMORY;
}
RefPtr<DrawTarget> target;
bool canUseDataSurface =
@ -368,13 +348,6 @@ nsresult imgFrame::Optimize()
mImageSurface = nullptr;
mOptSurface = 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::InformDeallocation(4 * mSize.width * mSize.height);
mInformedDiscardTracker = false;
}
return NS_OK;
}
}

View File

@ -207,9 +207,6 @@ private: // data
bool mNonPremult;
bool mOptimizable;
/** Have we called DiscardTracker::InformAllocation()? */
bool mInformedDiscardTracker;
friend class DrawableFrameRef;
friend class RawAccessFrameRef;
};

View File

@ -36,7 +36,6 @@
#include "nsIMemoryReporter.h"
#include "Image.h"
#include "DiscardTracker.h"
#include "gfxPrefs.h"
// we want to explore making the document own the load group
@ -955,27 +954,6 @@ nsresult imgLoader::CreateNewProxyForRequest(imgRequest *aRequest, nsILoadGroup
return NS_OK;
}
class imgCacheObserver MOZ_FINAL : public nsIObserver
{
~imgCacheObserver() {}
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
};
NS_IMPL_ISUPPORTS(imgCacheObserver, nsIObserver)
NS_IMETHODIMP
imgCacheObserver::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aSomeData)
{
if (strcmp(aTopic, "memory-pressure") == 0 ||
strcmp(aTopic, "app-theme-changed") == 0) {
DiscardTracker::DiscardAll();
}
return NS_OK;
}
class imgCacheExpirationTracker MOZ_FINAL
: public nsExpirationTracker<imgCacheEntry, 3>
{
@ -1016,8 +994,6 @@ void imgCacheExpirationTracker::NotifyExpired(imgCacheEntry *entry)
entry->Loader()->VerifyCacheSizes();
}
imgCacheObserver *gCacheObserver;
double imgLoader::sCacheTimeWeight;
uint32_t imgLoader::sCacheMaxSize;
imgMemoryReporter* imgLoader::sMemReporter;
@ -1138,15 +1114,6 @@ imgCacheQueue & imgLoader::GetCacheQueue(ImageURL *aURI)
void imgLoader::GlobalInit()
{
gCacheObserver = new imgCacheObserver();
NS_ADDREF(gCacheObserver);
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->AddObserver(gCacheObserver, "memory-pressure", false);
os->AddObserver(gCacheObserver, "app-theme-changed", false);
}
sCacheTimeWeight = gfxPrefs::ImageCacheTimeWeight() / 1000.0;
int32_t cachesize = gfxPrefs::ImageCacheSize();
sCacheMaxSize = cachesize > 0 ? cachesize : 0;
@ -1284,7 +1251,6 @@ void imgLoader::Shutdown()
{
NS_IF_RELEASE(gSingleton);
NS_IF_RELEASE(gPBSingleton);
NS_RELEASE(gCacheObserver);
}
nsresult imgLoader::ClearChromeImageCache()

View File

@ -18,7 +18,6 @@ UNIFIED_SOURCES += [
'ClippedImage.cpp',
'DecodePool.cpp',
'Decoder.cpp',
'DiscardTracker.cpp',
'DynamicImage.cpp',
'FrameAnimator.cpp',
'FrameBlender.cpp',

View File

@ -19,25 +19,35 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=399925
<script class="testbody" type="text/javascript">
/** Test for Bug 399925. **/
var triggerDiscardingManually = false;
var pngResults = new Array();
SimpleTest.waitForExplicitFinish();
window.onload = function() {
// 1. Enable Discarding
// 2. Sets the discard timer to 500ms so we don't have to wait so long. The
// actual time is nondeterministic, but is bounded by 2 * timer = 1 second.
// It'd be nice to reduce the discard timer here, but unfortunately we only
// read that pref on startup. We instead manually trigger discarding on
// platforms where the discard timer is too long (which we'll somewhat
// arbitrarily define as 'longer than 60 seconds').
var expirationMs =
SpecialPowers.getIntPref('image.mem.surfacecache.min_expiration_ms');
if (expirationMs > 60000) {
ok(true, 'Triggering discarding manually because SurfaceCache expiration ' +
'is ' + expirationMs + ' ms');
triggerDiscardingManually = true;
} else {
ok(true, 'Using normal discarding because SurfaceCache expiration ' +
'is ' + expirationMs + ' ms');
}
// Enable discarding for the test.
SpecialPowers.pushPrefEnv({
'set':[['image.mem.discardable',true],
['image.mem.min_discard_timeout_ms',1000]]
'set':[['image.mem.discardable',true]]
}, runTest);
}
function runTest() {
// Create the image _after_ setting the discard timer pref
var image = new Image();
image.setAttribute("id", "gif");
image.src = "bug399925.gif";
document.getElementById("content").appendChild(image);
// 1. Draw the canvas once on loadComplete
// 2. Redraw the canvas and compare the results right on discard
@ -46,6 +56,16 @@ function runTest() {
is(pngResults[0], pngResults[1], "got different rendered results");
SimpleTest.finish();
});
image.src = "bug399925.gif";
document.getElementById("content").appendChild(image);
if (triggerDiscardingManually) {
var request = SpecialPowers.wrap(image)
.QueryInterface(SpecialPowers.Ci.nsIImageLoadingContent)
.getRequest(SpecialPowers.Ci.nsIImageLoadingContent.CURRENT_REQUEST);
setTimeout(() => request.requestDiscard(), 1000);
}
}
function addCallbacks(anImage, loadCompleteCallback, discardCallback) {

View File

@ -574,7 +574,6 @@ pref("media.fragmented-mp4.android-media-codec.preferred", true);
// optimize images memory usage
pref("image.mem.decodeondraw", true);
pref("image.mem.min_discard_timeout_ms", 10000);
#ifdef NIGHTLY_BUILD
// Shumway component (SWF player) is disabled by default. Also see bug 904346.

View File

@ -3775,29 +3775,12 @@ pref("image.mem.decodeondraw", true);
// Allows image locking of decoded image data in content processes.
pref("image.mem.allow_locking_in_content_processes", true);
// Minimum timeout for image discarding (in milliseconds). The actual time in
// which an image must inactive for it to be discarded will vary between this
// value and twice this value.
//
// This used to be 120 seconds, but having it that high causes our working
// set to grow very large. Switching it back to 10 seconds will hopefully
// be better.
pref("image.mem.min_discard_timeout_ms", 10000);
// Chunk size for calls to the image decoders
pref("image.mem.decode_bytes_at_a_time", 16384);
// 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