Bumping manifests a=b2g-bump
[gecko.git] / image / src / DiscardTracker.cpp
blob0dfac6a78fba71e4385af36de8f4f252ea56ce3d
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 */ 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.
34 NS_IMETHODIMP
35 DiscardTracker::DiscardRunnable::Run()
37 sDiscardRunnablePending = false;
39 DiscardTracker::DiscardNow();
40 return NS_OK;
43 void
44 DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
46 DiscardTracker::ReloadTimeout();
49 nsresult
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();
63 if (wasInList) {
64 node->remove();
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();
76 // Make sure the timer is running.
77 nsresult rv = EnableTimer();
78 NS_ENSURE_SUCCESS(rv,rv);
80 return NS_OK;
83 void
84 DiscardTracker::Remove(Node *node)
86 if (sShutdown) {
87 // Already shutdown. List should be empty, so just return.
88 return;
90 MutexAutoLock lock(*sNodeListMutex);
92 if (node->isInList())
93 node->remove();
95 if (sDiscardableImages.isEmpty())
96 DisableTimer();
99 /**
100 * Shut down the tracker, deallocating the timer.
102 void
103 DiscardTracker::Shutdown()
105 sShutdown = true;
107 if (sTimer) {
108 sTimer->Cancel();
109 sTimer = nullptr;
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;
121 * Discard all the images we're tracking.
123 void
124 DiscardTracker::DiscardAll()
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();
138 // The list is empty, so there's no need to leave the timer on.
139 DisableTimer();
142 /* static */ bool
143 DiscardTracker::TryAllocation(uint64_t aBytes)
145 MOZ_ASSERT(sInitialized);
147 PR_Lock(sAllocationLock);
148 bool enoughSpace =
149 !sHardLimitDecodedImageKB ||
150 (sHardLimitDecodedImageKB * 1024) - sCurrentDecodedImageBytes >= aBytes;
152 if (enoughSpace) {
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.
159 MaybeDiscardSoon();
161 return enoughSpace;
164 /* static */ void
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.
180 nsresult
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",
189 50 * 1024);
191 Preferences::AddUintVarCache(&sHardLimitDecodedImageKB,
192 "image.mem.hard_limit_decoded_image_kb",
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;
214 * Read the discard timeout from about:config.
216 void
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)
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();
240 * Enables the timer. No-op if the timer is already running.
242 nsresult
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,
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);
263 * Disables the timer. No-op if the timer isn't running.
265 void
266 DiscardTracker::DisableTimer()
268 // Nothing to do if the timer's already off.
269 if (!sTimerOn || !sTimer)
270 return;
271 sTimerOn = false;
273 // Deactivate
274 sTimer->Cancel();
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.
282 void
283 DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
285 DiscardNow();
288 void
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();
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);
310 else {
311 break;
315 // If the list is empty, disable the timer.
316 if (sDiscardableImages.isEmpty())
317 DisableTimer();
320 void
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);
335 } // namespace image
336 } // namespace mozilla