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"
8 #include "RasterImage.h"
9 #include "DiscardTracker.h"
10 #include "mozilla/Preferences.h"
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 */ Mutex
* DiscardTracker::sNodeListMutex
= nullptr;
28 /* static */ Atomic
<bool> DiscardTracker::sShutdown(false);
31 * When we notice we're using too much memory for decoded images, we enqueue a
32 * DiscardRunnable, which runs this code.
35 DiscardTracker::DiscardRunnable::Run()
37 sDiscardRunnablePending
= false;
39 DiscardTracker::DiscardNow();
44 DiscardTimeoutChangedCallback(const char* aPref
, void *aClosure
)
46 DiscardTracker::ReloadTimeout();
50 DiscardTracker::Reset(Node
*node
)
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();
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.
76 // Make sure the timer is running.
77 nsresult rv
= EnableTimer();
78 NS_ENSURE_SUCCESS(rv
,rv
);
84 DiscardTracker::Remove(Node
*node
)
87 // Already shutdown. List should be empty, so just return.
90 MutexAutoLock
lock(*sNodeListMutex
);
95 if (sDiscardableImages
.isEmpty())
100 * Shut down the tracker, deallocating the timer.
103 DiscardTracker::Shutdown()
112 // Clear the sDiscardableImages linked list so that its destructor
113 // (LinkedList.h) finds an empty array, which is required after bug 803688.
116 delete sNodeListMutex
;
117 sNodeListMutex
= nullptr;
121 * Discard all the images we're tracking.
124 DiscardTracker::DiscardAll()
126 MutexAutoLock
lock(*sNodeListMutex
);
131 // Be careful: Calling Discard() on an image might cause it to be removed
134 while ((n
= sDiscardableImages
.popFirst())) {
138 // The list is empty, so there's no need to leave the timer on.
143 DiscardTracker::TryAllocation(uint64_t aBytes
)
145 MOZ_ASSERT(sInitialized
);
147 PR_Lock(sAllocationLock
);
149 !sHardLimitDecodedImageKB
||
150 (sHardLimitDecodedImageKB
* 1024) - sCurrentDecodedImageBytes
>= aBytes
;
153 sCurrentDecodedImageBytes
+= aBytes
;
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.
165 DiscardTracker::InformDeallocation(uint64_t aBytes
)
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
);
178 * Initialize the tracker.
181 DiscardTracker::Initialize()
183 // Watch the timeout pref for changes.
184 Preferences::RegisterCallback(DiscardTimeoutChangedCallback
,
185 sDiscardTimeoutPref
);
187 Preferences::AddUintVarCache(&sMaxDecodedImageKB
,
188 "image.mem.max_decoded_image_kb",
191 Preferences::AddUintVarCache(&sHardLimitDecodedImageKB
,
192 "image.mem.hard_limit_decoded_image_kb",
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
207 // Read the timeout pref and start the timer.
214 * Read the discard timeout from about:config.
217 DiscardTracker::ReloadTimeout()
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)
227 // If the value didn't change, return.
228 if ((uint32_t) discardTimeout
== sMinDiscardTimeoutMs
)
232 sMinDiscardTimeoutMs
= (uint32_t) discardTimeout
;
234 // Restart the timer so the new timeout takes effect.
240 * Enables the timer. No-op if the timer is already running.
243 DiscardTracker::EnableTimer()
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,
248 if (sTimerOn
|| !sInitialized
|| !sTimer
)
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
,
258 sMinDiscardTimeoutMs
/ 2,
259 nsITimer::TYPE_REPEATING_SLACK
);
263 * Disables the timer. No-op if the timer isn't running.
266 DiscardTracker::DisableTimer()
268 // Nothing to do if the timer's already off.
269 if (!sTimerOn
|| !sTimer
)
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.
283 DiscardTracker::TimerCallback(nsITimer
*aTimer
, void *aClosure
)
289 DiscardTracker::DiscardNow()
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();
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
315 // If the list is empty, disable the timer.
316 if (sDiscardableImages
.isEmpty())
321 DiscardTracker::MaybeDiscardSoon()
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
);
336 } // namespace mozilla