image/src/DiscardTracker.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "nsComponentManagerUtils.h"
michael@0 7 #include "nsITimer.h"
michael@0 8 #include "RasterImage.h"
michael@0 9 #include "DiscardTracker.h"
michael@0 10 #include "mozilla/Preferences.h"
michael@0 11
michael@0 12 namespace mozilla {
michael@0 13 namespace image {
michael@0 14
michael@0 15 static const char* sDiscardTimeoutPref = "image.mem.min_discard_timeout_ms";
michael@0 16
michael@0 17 /* static */ LinkedList<DiscardTracker::Node> DiscardTracker::sDiscardableImages;
michael@0 18 /* static */ nsCOMPtr<nsITimer> DiscardTracker::sTimer;
michael@0 19 /* static */ bool DiscardTracker::sInitialized = false;
michael@0 20 /* static */ bool DiscardTracker::sTimerOn = false;
michael@0 21 /* static */ Atomic<bool> DiscardTracker::sDiscardRunnablePending(false);
michael@0 22 /* static */ uint64_t DiscardTracker::sCurrentDecodedImageBytes = 0;
michael@0 23 /* static */ uint32_t DiscardTracker::sMinDiscardTimeoutMs = 10000;
michael@0 24 /* static */ uint32_t DiscardTracker::sMaxDecodedImageKB = 42 * 1024;
michael@0 25 /* static */ uint32_t DiscardTracker::sHardLimitDecodedImageKB = 0;
michael@0 26 /* static */ PRLock * DiscardTracker::sAllocationLock = nullptr;
michael@0 27 /* static */ mozilla::Mutex* DiscardTracker::sNodeListMutex = nullptr;
michael@0 28 /* static */ Atomic<bool> DiscardTracker::sShutdown(false);
michael@0 29
michael@0 30 /*
michael@0 31 * When we notice we're using too much memory for decoded images, we enqueue a
michael@0 32 * DiscardRunnable, which runs this code.
michael@0 33 */
michael@0 34 NS_IMETHODIMP
michael@0 35 DiscardTracker::DiscardRunnable::Run()
michael@0 36 {
michael@0 37 sDiscardRunnablePending = false;
michael@0 38
michael@0 39 DiscardTracker::DiscardNow();
michael@0 40 return NS_OK;
michael@0 41 }
michael@0 42
michael@0 43 void
michael@0 44 DiscardTimeoutChangedCallback(const char* aPref, void *aClosure)
michael@0 45 {
michael@0 46 DiscardTracker::ReloadTimeout();
michael@0 47 }
michael@0 48
michael@0 49 nsresult
michael@0 50 DiscardTracker::Reset(Node *node)
michael@0 51 {
michael@0 52 // We shouldn't call Reset() with a null |img| pointer, on images which can't
michael@0 53 // be discarded, or on animated images (which should be marked as
michael@0 54 // non-discardable, anyway).
michael@0 55 MutexAutoLock lock(*sNodeListMutex);
michael@0 56 MOZ_ASSERT(sInitialized);
michael@0 57 MOZ_ASSERT(node->img);
michael@0 58 MOZ_ASSERT(node->img->CanDiscard());
michael@0 59 MOZ_ASSERT(!node->img->mAnim);
michael@0 60
michael@0 61 // Insert the node at the front of the list and note when it was inserted.
michael@0 62 bool wasInList = node->isInList();
michael@0 63 if (wasInList) {
michael@0 64 node->remove();
michael@0 65 }
michael@0 66 node->timestamp = TimeStamp::Now();
michael@0 67 sDiscardableImages.insertFront(node);
michael@0 68
michael@0 69 // If the node wasn't already in the list of discardable images, then we may
michael@0 70 // need to discard some images to stay under the sMaxDecodedImageKB limit.
michael@0 71 // Call MaybeDiscardSoon to do this check.
michael@0 72 if (!wasInList) {
michael@0 73 MaybeDiscardSoon();
michael@0 74 }
michael@0 75
michael@0 76 // Make sure the timer is running.
michael@0 77 nsresult rv = EnableTimer();
michael@0 78 NS_ENSURE_SUCCESS(rv,rv);
michael@0 79
michael@0 80 return NS_OK;
michael@0 81 }
michael@0 82
michael@0 83 void
michael@0 84 DiscardTracker::Remove(Node *node)
michael@0 85 {
michael@0 86 if (sShutdown) {
michael@0 87 // Already shutdown. List should be empty, so just return.
michael@0 88 return;
michael@0 89 }
michael@0 90 MutexAutoLock lock(*sNodeListMutex);
michael@0 91
michael@0 92 if (node->isInList())
michael@0 93 node->remove();
michael@0 94
michael@0 95 if (sDiscardableImages.isEmpty())
michael@0 96 DisableTimer();
michael@0 97 }
michael@0 98
michael@0 99 /**
michael@0 100 * Shut down the tracker, deallocating the timer.
michael@0 101 */
michael@0 102 void
michael@0 103 DiscardTracker::Shutdown()
michael@0 104 {
michael@0 105 sShutdown = true;
michael@0 106
michael@0 107 if (sTimer) {
michael@0 108 sTimer->Cancel();
michael@0 109 sTimer = nullptr;
michael@0 110 }
michael@0 111
michael@0 112 // Clear the sDiscardableImages linked list so that its destructor
michael@0 113 // (LinkedList.h) finds an empty array, which is required after bug 803688.
michael@0 114 DiscardAll();
michael@0 115
michael@0 116 delete sNodeListMutex;
michael@0 117 sNodeListMutex = nullptr;
michael@0 118 }
michael@0 119
michael@0 120 /*
michael@0 121 * Discard all the images we're tracking.
michael@0 122 */
michael@0 123 void
michael@0 124 DiscardTracker::DiscardAll()
michael@0 125 {
michael@0 126 MutexAutoLock lock(*sNodeListMutex);
michael@0 127
michael@0 128 if (!sInitialized)
michael@0 129 return;
michael@0 130
michael@0 131 // Be careful: Calling Discard() on an image might cause it to be removed
michael@0 132 // from the list!
michael@0 133 Node *n;
michael@0 134 while ((n = sDiscardableImages.popFirst())) {
michael@0 135 n->img->Discard();
michael@0 136 }
michael@0 137
michael@0 138 // The list is empty, so there's no need to leave the timer on.
michael@0 139 DisableTimer();
michael@0 140 }
michael@0 141
michael@0 142 /* static */ bool
michael@0 143 DiscardTracker::TryAllocation(uint64_t aBytes)
michael@0 144 {
michael@0 145 MOZ_ASSERT(sInitialized);
michael@0 146
michael@0 147 PR_Lock(sAllocationLock);
michael@0 148 bool enoughSpace =
michael@0 149 !sHardLimitDecodedImageKB ||
michael@0 150 (sHardLimitDecodedImageKB * 1024) - sCurrentDecodedImageBytes >= aBytes;
michael@0 151
michael@0 152 if (enoughSpace) {
michael@0 153 sCurrentDecodedImageBytes += aBytes;
michael@0 154 }
michael@0 155 PR_Unlock(sAllocationLock);
michael@0 156
michael@0 157 // If we're using too much memory for decoded images, MaybeDiscardSoon will
michael@0 158 // enqueue a callback to discard some images.
michael@0 159 MaybeDiscardSoon();
michael@0 160
michael@0 161 return enoughSpace;
michael@0 162 }
michael@0 163
michael@0 164 /* static */ void
michael@0 165 DiscardTracker::InformDeallocation(uint64_t aBytes)
michael@0 166 {
michael@0 167 // This function is called back e.g. from RasterImage::Discard(); be careful!
michael@0 168
michael@0 169 MOZ_ASSERT(sInitialized);
michael@0 170
michael@0 171 PR_Lock(sAllocationLock);
michael@0 172 MOZ_ASSERT(aBytes <= sCurrentDecodedImageBytes);
michael@0 173 sCurrentDecodedImageBytes -= aBytes;
michael@0 174 PR_Unlock(sAllocationLock);
michael@0 175 }
michael@0 176
michael@0 177 /**
michael@0 178 * Initialize the tracker.
michael@0 179 */
michael@0 180 nsresult
michael@0 181 DiscardTracker::Initialize()
michael@0 182 {
michael@0 183 // Watch the timeout pref for changes.
michael@0 184 Preferences::RegisterCallback(DiscardTimeoutChangedCallback,
michael@0 185 sDiscardTimeoutPref);
michael@0 186
michael@0 187 Preferences::AddUintVarCache(&sMaxDecodedImageKB,
michael@0 188 "image.mem.max_decoded_image_kb",
michael@0 189 50 * 1024);
michael@0 190
michael@0 191 Preferences::AddUintVarCache(&sHardLimitDecodedImageKB,
michael@0 192 "image.mem.hard_limit_decoded_image_kb",
michael@0 193 0);
michael@0 194 // Create the timer.
michael@0 195 sTimer = do_CreateInstance("@mozilla.org/timer;1");
michael@0 196
michael@0 197 // Create a lock for safegarding the 64-bit sCurrentDecodedImageBytes
michael@0 198 sAllocationLock = PR_NewLock();
michael@0 199
michael@0 200 // Create a lock for the node list.
michael@0 201 MOZ_ASSERT(!sNodeListMutex);
michael@0 202 sNodeListMutex = new Mutex("image::DiscardTracker");
michael@0 203
michael@0 204 // Mark us as initialized
michael@0 205 sInitialized = true;
michael@0 206
michael@0 207 // Read the timeout pref and start the timer.
michael@0 208 ReloadTimeout();
michael@0 209
michael@0 210 return NS_OK;
michael@0 211 }
michael@0 212
michael@0 213 /**
michael@0 214 * Read the discard timeout from about:config.
michael@0 215 */
michael@0 216 void
michael@0 217 DiscardTracker::ReloadTimeout()
michael@0 218 {
michael@0 219 // Read the timeout pref.
michael@0 220 int32_t discardTimeout;
michael@0 221 nsresult rv = Preferences::GetInt(sDiscardTimeoutPref, &discardTimeout);
michael@0 222
michael@0 223 // If we got something bogus, return.
michael@0 224 if (!NS_SUCCEEDED(rv) || discardTimeout <= 0)
michael@0 225 return;
michael@0 226
michael@0 227 // If the value didn't change, return.
michael@0 228 if ((uint32_t) discardTimeout == sMinDiscardTimeoutMs)
michael@0 229 return;
michael@0 230
michael@0 231 // Update the value.
michael@0 232 sMinDiscardTimeoutMs = (uint32_t) discardTimeout;
michael@0 233
michael@0 234 // Restart the timer so the new timeout takes effect.
michael@0 235 DisableTimer();
michael@0 236 EnableTimer();
michael@0 237 }
michael@0 238
michael@0 239 /**
michael@0 240 * Enables the timer. No-op if the timer is already running.
michael@0 241 */
michael@0 242 nsresult
michael@0 243 DiscardTracker::EnableTimer()
michael@0 244 {
michael@0 245 // Nothing to do if the timer's already on or we haven't yet been
michael@0 246 // initialized. !sTimer probably means we've shut down, so just ignore that,
michael@0 247 // too.
michael@0 248 if (sTimerOn || !sInitialized || !sTimer)
michael@0 249 return NS_OK;
michael@0 250
michael@0 251 sTimerOn = true;
michael@0 252
michael@0 253 // Activate the timer. Have it call us back in (sMinDiscardTimeoutMs / 2)
michael@0 254 // ms, so that an image is discarded between sMinDiscardTimeoutMs and
michael@0 255 // (3/2 * sMinDiscardTimeoutMs) ms after it's unlocked.
michael@0 256 return sTimer->InitWithFuncCallback(TimerCallback,
michael@0 257 nullptr,
michael@0 258 sMinDiscardTimeoutMs / 2,
michael@0 259 nsITimer::TYPE_REPEATING_SLACK);
michael@0 260 }
michael@0 261
michael@0 262 /*
michael@0 263 * Disables the timer. No-op if the timer isn't running.
michael@0 264 */
michael@0 265 void
michael@0 266 DiscardTracker::DisableTimer()
michael@0 267 {
michael@0 268 // Nothing to do if the timer's already off.
michael@0 269 if (!sTimerOn || !sTimer)
michael@0 270 return;
michael@0 271 sTimerOn = false;
michael@0 272
michael@0 273 // Deactivate
michael@0 274 sTimer->Cancel();
michael@0 275 }
michael@0 276
michael@0 277 /**
michael@0 278 * Routine activated when the timer fires. This discards everything that's
michael@0 279 * older than sMinDiscardTimeoutMs, and tries to discard enough images so that
michael@0 280 * we go under sMaxDecodedImageKB.
michael@0 281 */
michael@0 282 void
michael@0 283 DiscardTracker::TimerCallback(nsITimer *aTimer, void *aClosure)
michael@0 284 {
michael@0 285 DiscardNow();
michael@0 286 }
michael@0 287
michael@0 288 void
michael@0 289 DiscardTracker::DiscardNow()
michael@0 290 {
michael@0 291 // Assuming the list is ordered with oldest discard tracker nodes at the back
michael@0 292 // and newest ones at the front, iterate from back to front discarding nodes
michael@0 293 // until we encounter one which is new enough to keep and until we go under
michael@0 294 // our sMaxDecodedImageKB limit.
michael@0 295
michael@0 296 TimeStamp now = TimeStamp::Now();
michael@0 297 Node* node;
michael@0 298 while ((node = sDiscardableImages.getLast())) {
michael@0 299 if ((now - node->timestamp).ToMilliseconds() > sMinDiscardTimeoutMs ||
michael@0 300 sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024) {
michael@0 301
michael@0 302 // Discarding the image should cause sCurrentDecodedImageBytes to
michael@0 303 // decrease via a call to InformDeallocation().
michael@0 304 node->img->Discard();
michael@0 305
michael@0 306 // Careful: Discarding may have caused the node to have been removed
michael@0 307 // from the list.
michael@0 308 Remove(node);
michael@0 309 }
michael@0 310 else {
michael@0 311 break;
michael@0 312 }
michael@0 313 }
michael@0 314
michael@0 315 // If the list is empty, disable the timer.
michael@0 316 if (sDiscardableImages.isEmpty())
michael@0 317 DisableTimer();
michael@0 318 }
michael@0 319
michael@0 320 void
michael@0 321 DiscardTracker::MaybeDiscardSoon()
michael@0 322 {
michael@0 323 // Are we carrying around too much decoded image data? If so, enqueue an
michael@0 324 // event to try to get us down under our limit.
michael@0 325 if (sCurrentDecodedImageBytes > sMaxDecodedImageKB * 1024 &&
michael@0 326 !sDiscardableImages.isEmpty()) {
michael@0 327 // Check if the value of sDiscardRunnablePending used to be false
michael@0 328 if (!sDiscardRunnablePending.exchange(true)) {
michael@0 329 nsRefPtr<DiscardRunnable> runnable = new DiscardRunnable();
michael@0 330 NS_DispatchToMainThread(runnable);
michael@0 331 }
michael@0 332 }
michael@0 333 }
michael@0 334
michael@0 335 } // namespace image
michael@0 336 } // namespace mozilla

mercurial