1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/image/src/DiscardTracker.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,336 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsComponentManagerUtils.h" 1.10 +#include "nsITimer.h" 1.11 +#include "RasterImage.h" 1.12 +#include "DiscardTracker.h" 1.13 +#include "mozilla/Preferences.h" 1.14 + 1.15 +namespace mozilla { 1.16 +namespace image { 1.17 + 1.18 +static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms"; 1.19 + 1.20 +/* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages; 1.21 +/* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer; 1.22 +/* static */ bool DiscardTracker::sInitialized = false; 1.23 +/* static */ bool DiscardTracker::sTimerOn = false; 1.24 +/* static */ Atomic<bool> DiscardTracker::sDiscardRunnablePending(false); 1.25 +/* static */ uint64_t DiscardTracker::sCurrentDecodedImageBytes = 0; 1.26 +/* static */ uint32_t DiscardTracker::sMinDiscardTimeoutMs = 10000; 1.27 +/* static */ uint32_t DiscardTracker::sMaxDecodedImageKB = 42 * 1024; 1.28 +/* static */ uint32_t DiscardTracker::sHardLimitDecodedImageKB = 0; 1.29 +/* static */ PRLock * DiscardTracker::sAllocationLock = nullptr; 1.30 +/* static */ mozilla::Mutex* DiscardTracker::sNodeListMutex = nullptr; 1.31 +/* static */ Atomic<bool> DiscardTracker::sShutdown(false); 1.32 + 1.33 +/* 1.34 + * When we notice we're using too much memory for decoded images, we enqueue a 1.35 + * DiscardRunnable, which runs this code. 1.36 + */ 1.37 +NS_IMETHODIMP 1.38 +DiscardTracker::DiscardRunnable::Run() 1.39 +{ 1.40 + sDiscardRunnablePending = false; 1.41 + 1.42 + DiscardTracker::DiscardNow(); 1.43 + return NS_OK; 1.44 +} 1.45 + 1.46 +void 1.47 +DiscardTimeoutChangedCallback(const char* aPref, void *aClosure) 1.48 +{ 1.49 + DiscardTracker::ReloadTimeout(); 1.50 +} 1.51 + 1.52 +nsresult 1.53 +DiscardTracker::Reset(Node *node) 1.54 +{ 1.55 + // We shouldn't call Reset() with a null |img| pointer, on images which can't 1.56 + // be discarded, or on animated images (which should be marked as 1.57 + // non-discardable, anyway). 1.58 + MutexAutoLock lock(*sNodeListMutex); 1.59 + MOZ_ASSERT(sInitialized); 1.60 + MOZ_ASSERT(node->img); 1.61 + MOZ_ASSERT(node->img->CanDiscard()); 1.62 + MOZ_ASSERT(!node->img->mAnim); 1.63 + 1.64 + // Insert the node at the front of the list and note when it was inserted. 1.65 + bool wasInList = node->isInList(); 1.66 + if (wasInList) { 1.67 + node->remove(); 1.68 + } 1.69 + node->timestamp = TimeStamp::Now(); 1.70 + sDiscardableImages.insertFront(node); 1.71 + 1.72 + // If the node wasn't already in the list of discardable images, then we may 1.73 + // need to discard some images to stay under the sMaxDecodedImageKB limit. 1.74 + // Call MaybeDiscardSoon to do this check. 1.75 + if (!wasInList) { 1.76 + MaybeDiscardSoon(); 1.77 + } 1.78 + 1.79 + // Make sure the timer is running. 1.80 + nsresult rv = EnableTimer(); 1.81 + NS_ENSURE_SUCCESS(rv,rv); 1.82 + 1.83 + return NS_OK; 1.84 +} 1.85 + 1.86 +void 1.87 +DiscardTracker::Remove(Node *node) 1.88 +{ 1.89 + if (sShutdown) { 1.90 + // Already shutdown. List should be empty, so just return. 1.91 + return; 1.92 + } 1.93 + MutexAutoLock lock(*sNodeListMutex); 1.94 + 1.95 + if (node->isInList()) 1.96 + node->remove(); 1.97 + 1.98 + if (sDiscardableImages.isEmpty()) 1.99 + DisableTimer(); 1.100 +} 1.101 + 1.102 +/** 1.103 + * Shut down the tracker, deallocating the timer. 1.104 + */ 1.105 +void 1.106 +DiscardTracker::Shutdown() 1.107 +{ 1.108 + sShutdown = true; 1.109 + 1.110 + if (sTimer) { 1.111 + sTimer->Cancel(); 1.112 + sTimer = nullptr; 1.113 + } 1.114 + 1.115 + // Clear the sDiscardableImages linked list so that its destructor 1.116 + // (LinkedList.h) finds an empty array, which is required after bug 803688. 1.117 + DiscardAll(); 1.118 + 1.119 + delete sNodeListMutex; 1.120 + sNodeListMutex = nullptr; 1.121 +} 1.122 + 1.123 +/* 1.124 + * Discard all the images we're tracking. 1.125 + */ 1.126 +void 1.127 +DiscardTracker::DiscardAll() 1.128 +{ 1.129 + MutexAutoLock lock(*sNodeListMutex); 1.130 + 1.131 + if (!sInitialized) 1.132 + return; 1.133 + 1.134 + // Be careful: Calling Discard() on an image might cause it to be removed 1.135 + // from the list! 1.136 + Node *n; 1.137 + while ((n = sDiscardableImages.popFirst())) { 1.138 + n->img->Discard(); 1.139 + } 1.140 + 1.141 + // The list is empty, so there's no need to leave the timer on. 1.142 + DisableTimer(); 1.143 +} 1.144 + 1.145 +/* static */ bool 1.146 +DiscardTracker::TryAllocation(uint64_t aBytes) 1.147 +{ 1.148 + MOZ_ASSERT(sInitialized); 1.149 + 1.150 + PR_Lock(sAllocationLock); 1.151 + bool enoughSpace = 1.152 + !sHardLimitDecodedImageKB || 1.153 + (sHardLimitDecodedImageKB * 1024) - sCurrentDecodedImageBytes >= aBytes; 1.154 + 1.155 + if (enoughSpace) { 1.156 + sCurrentDecodedImageBytes += aBytes; 1.157 + } 1.158 + PR_Unlock(sAllocationLock); 1.159 + 1.160 + // If we're using too much memory for decoded images, MaybeDiscardSoon will 1.161 + // enqueue a callback to discard some images. 1.162 + MaybeDiscardSoon(); 1.163 + 1.164 + return enoughSpace; 1.165 +} 1.166 + 1.167 +/* static */ void 1.168 +DiscardTracker::InformDeallocation(uint64_t aBytes) 1.169 +{ 1.170 + // This function is called back e.g. from RasterImage::Discard(); be careful! 1.171 + 1.172 + MOZ_ASSERT(sInitialized); 1.173 + 1.174 + PR_Lock(sAllocationLock); 1.175 + MOZ_ASSERT(aBytes <= sCurrentDecodedImageBytes); 1.176 + sCurrentDecodedImageBytes -= aBytes; 1.177 + PR_Unlock(sAllocationLock); 1.178 +} 1.179 + 1.180 +/** 1.181 + * Initialize the tracker. 1.182 + */ 1.183 +nsresult 1.184 +DiscardTracker::Initialize() 1.185 +{ 1.186 + // Watch the timeout pref for changes. 1.187 + Preferences::RegisterCallback(DiscardTimeoutChangedCallback, 1.188 + sDiscardTimeoutPref); 1.189 + 1.190 + Preferences::AddUintVarCache(&sMaxDecodedImageKB, 1.191 + "image.mem.max_decoded_image_kb", 1.192 + 50 * 1024); 1.193 + 1.194 + Preferences::AddUintVarCache(&sHardLimitDecodedImageKB, 1.195 + "image.mem.hard_limit_decoded_image_kb", 1.196 + 0); 1.197 + // Create the timer. 1.198 + sTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.199 + 1.200 + // Create a lock for safegarding the 64-bit sCurrentDecodedImageBytes 1.201 + sAllocationLock = PR_NewLock(); 1.202 + 1.203 + // Create a lock for the node list. 1.204 + MOZ_ASSERT(!sNodeListMutex); 1.205 + sNodeListMutex = new Mutex("image::DiscardTracker"); 1.206 + 1.207 + // Mark us as initialized 1.208 + sInitialized = true; 1.209 + 1.210 + // Read the timeout pref and start the timer. 1.211 + ReloadTimeout(); 1.212 + 1.213 + return NS_OK; 1.214 +} 1.215 + 1.216 +/** 1.217 + * Read the discard timeout from about:config. 1.218 + */ 1.219 +void 1.220 +DiscardTracker::ReloadTimeout() 1.221 +{ 1.222 + // Read the timeout pref. 1.223 + int32_t discardTimeout; 1.224 + nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout); 1.225 + 1.226 + // If we got something bogus, return. 1.227 + if (!NS_SUCCEEDED(rv) || discardTimeout <= 0) 1.228 + return; 1.229 + 1.230 + // If the value didn't change, return. 1.231 + if ((uint32_t) discardTimeout == sMinDiscardTimeoutMs) 1.232 + return; 1.233 + 1.234 + // Update the value. 1.235 + sMinDiscardTimeoutMs = (uint32_t) discardTimeout; 1.236 + 1.237 + // Restart the timer so the new timeout takes effect. 1.238 + DisableTimer(); 1.239 + EnableTimer(); 1.240 +} 1.241 + 1.242 +/** 1.243 + * Enables the timer. No-op if the timer is already running. 1.244 + */ 1.245 +nsresult 1.246 +DiscardTracker::EnableTimer() 1.247 +{ 1.248 + // Nothing to do if the timer's already on or we haven't yet been 1.249 + // initialized. !sTimer probably means we've shut down, so just ignore that, 1.250 + // too. 1.251 + if (sTimerOn || !sInitialized || !sTimer) 1.252 + return NS_OK; 1.253 + 1.254 + sTimerOn = true; 1.255 + 1.256 + // Activate the timer. Have it call us back in (sMinDiscardTimeoutMs / 2) 1.257 + // ms, so that an image is discarded between sMinDiscardTimeoutMs and 1.258 + // (3/2 * sMinDiscardTimeoutMs) ms after it's unlocked. 1.259 + return sTimer->InitWithFuncCallback(TimerCallback, 1.260 + nullptr, 1.261 + sMinDiscardTimeoutMs / 2, 1.262 + nsITimer::TYPE_REPEATING_SLACK); 1.263 +} 1.264 + 1.265 +/* 1.266 + * Disables the timer. No-op if the timer isn't running. 1.267 + */ 1.268 +void 1.269 +DiscardTracker::DisableTimer() 1.270 +{ 1.271 + // Nothing to do if the timer's already off. 1.272 + if (!sTimerOn || !sTimer) 1.273 + return; 1.274 + sTimerOn = false; 1.275 + 1.276 + // Deactivate 1.277 + sTimer->Cancel(); 1.278 +} 1.279 + 1.280 +/** 1.281 + * Routine activated when the timer fires. This discards everything that's 1.282 + * older than sMinDiscardTimeoutMs, and tries to discard enough images so that 1.283 + * we go under sMaxDecodedImageKB. 1.284 + */ 1.285 +void 1.286 +DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure) 1.287 +{ 1.288 + DiscardNow(); 1.289 +} 1.290 + 1.291 +void 1.292 +DiscardTracker::DiscardNow() 1.293 +{ 1.294 + // Assuming the list is ordered with oldest discard tracker nodes at the back 1.295 + // and newest ones at the front, iterate from back to front discarding nodes 1.296 + // until we encounter one which is new enough to keep and until we go under 1.297 + // our sMaxDecodedImageKB limit. 1.298 + 1.299 + TimeStamp now = TimeStamp::Now(); 1.300 + Node* node; 1.301 + while ((node = sDiscardableImages.getLast())) { 1.302 + if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs || 1.303 + sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024) { 1.304 + 1.305 + // Discarding the image should cause sCurrentDecodedImageBytes to 1.306 + // decrease via a call to InformDeallocation(). 1.307 + node->img->Discard(); 1.308 + 1.309 + // Careful: Discarding may have caused the node to have been removed 1.310 + // from the list. 1.311 + Remove(node); 1.312 + } 1.313 + else { 1.314 + break; 1.315 + } 1.316 + } 1.317 + 1.318 + // If the list is empty, disable the timer. 1.319 + if (sDiscardableImages.isEmpty()) 1.320 + DisableTimer(); 1.321 +} 1.322 + 1.323 +void 1.324 +DiscardTracker::MaybeDiscardSoon() 1.325 +{ 1.326 + // Are we carrying around too much decoded image data? If so, enqueue an 1.327 + // event to try to get us down under our limit. 1.328 + if (sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024 && 1.329 + !sDiscardableImages.isEmpty()) { 1.330 + // Check if the value of sDiscardRunnablePending used to be false 1.331 + if (!sDiscardRunnablePending.exchange(true)) { 1.332 + nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable(); 1.333 + NS_DispatchToMainThread(runnable); 1.334 + } 1.335 + } 1.336 +} 1.337 + 1.338 +} // namespace image 1.339 +} // namespace mozilla