1 /* 2 * Copyright (c) 2013, Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "core/fetch/MemoryCache.h" 33 34 #include "core/fetch/MockImageResourceClient.h" 35 #include "core/fetch/RawResource.h" 36 #include "core/fetch/ResourcePtr.h" 37 #include "platform/network/ResourceRequest.h" 38 #include "public/platform/Platform.h" 39 #include "wtf/OwnPtr.h" 40 41 #include <gtest/gtest.h> 42 43 namespace WebCore { 44 45 class MemoryCacheTest : public ::testing::Test { 46 public: 47 class FakeDecodedResource : public WebCore::Resource { 48 public: 49 FakeDecodedResource(const ResourceRequest& request, Type type) 50 : Resource(request, type) 51 { 52 } 53 54 virtual void appendData(const char* data, int len) 55 { 56 Resource::appendData(data, len); 57 setDecodedSize(this->size()); 58 } 59 60 virtual void destroyDecodedData() 61 { 62 setDecodedSize(0); 63 } 64 }; 65 66 class FakeResource : public WebCore::Resource { 67 public: 68 FakeResource(const ResourceRequest& request, Type type) 69 : Resource(request, type) 70 { 71 } 72 73 void fakeEncodedSize(size_t size) 74 { 75 setEncodedSize(size); 76 } 77 }; 78 79 protected: 80 virtual void SetUp() 81 { 82 // Save the global memory cache to restore it upon teardown. 83 m_globalMemoryCache = adoptPtr(memoryCache()); 84 // Create the test memory cache instance and hook it in. 85 m_testingMemoryCache = adoptPtr(new MemoryCache()); 86 setMemoryCacheForTesting(m_testingMemoryCache.leakPtr()); 87 } 88 89 virtual void TearDown() 90 { 91 // Regain the ownership of testing memory cache, so that it will be 92 // destroyed. 93 m_testingMemoryCache = adoptPtr(memoryCache()); 94 // Yield the ownership of the global memory cache back. 95 setMemoryCacheForTesting(m_globalMemoryCache.leakPtr()); 96 } 97 98 OwnPtr<MemoryCache> m_testingMemoryCache; 99 OwnPtr<MemoryCache> m_globalMemoryCache; 100 }; 101 102 // Verifies that setters and getters for cache capacities work correcty. 103 TEST_F(MemoryCacheTest, CapacityAccounting) 104 { 105 const size_t sizeMax = ~static_cast<size_t>(0); 106 const size_t totalCapacity = sizeMax / 4; 107 const size_t minDeadCapacity = sizeMax / 16; 108 const size_t maxDeadCapacity = sizeMax / 8; 109 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 110 ASSERT_EQ(totalCapacity, memoryCache()->capacity()); 111 ASSERT_EQ(minDeadCapacity, memoryCache()->minDeadCapacity()); 112 ASSERT_EQ(maxDeadCapacity, memoryCache()->maxDeadCapacity()); 113 } 114 115 TEST_F(MemoryCacheTest, VeryLargeResourceAccounting) 116 { 117 const size_t sizeMax = ~static_cast<size_t>(0); 118 const size_t totalCapacity = sizeMax / 4; 119 const size_t minDeadCapacity = sizeMax / 16; 120 const size_t maxDeadCapacity = sizeMax / 8; 121 const size_t resourceSize1 = sizeMax / 16; 122 const size_t resourceSize2 = sizeMax / 20; 123 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 124 ResourcePtr<FakeResource> cachedResource = 125 new FakeResource(ResourceRequest(""), Resource::Raw); 126 cachedResource->fakeEncodedSize(resourceSize1); 127 128 ASSERT_EQ(0u, memoryCache()->deadSize()); 129 ASSERT_EQ(0u, memoryCache()->liveSize()); 130 memoryCache()->add(cachedResource.get()); 131 ASSERT_EQ(cachedResource->size(), memoryCache()->deadSize()); 132 ASSERT_EQ(0u, memoryCache()->liveSize()); 133 134 MockImageResourceClient client; 135 cachedResource->addClient(&client); 136 ASSERT_EQ(0u, memoryCache()->deadSize()); 137 ASSERT_EQ(cachedResource->size(), memoryCache()->liveSize()); 138 139 cachedResource->fakeEncodedSize(resourceSize2); 140 ASSERT_EQ(0u, memoryCache()->deadSize()); 141 ASSERT_EQ(cachedResource->size(), memoryCache()->liveSize()); 142 } 143 144 // Verifies that dead resources that exceed dead resource capacity are evicted 145 // from cache when pruning. 146 TEST_F(MemoryCacheTest, DeadResourceEviction) 147 { 148 memoryCache()->setDelayBeforeLiveDecodedPrune(0); 149 memoryCache()->setMaxPruneDeferralDelay(0); 150 const unsigned totalCapacity = 1000000; 151 const unsigned minDeadCapacity = 0; 152 const unsigned maxDeadCapacity = 0; 153 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 154 155 ResourcePtr<Resource> cachedResource = 156 new Resource(ResourceRequest(""), Resource::Raw); 157 const char data[5] = "abcd"; 158 cachedResource->appendData(data, 3); 159 // The resource size has to be nonzero for this test to be meaningful, but 160 // we do not rely on it having any particular value. 161 ASSERT_GT(cachedResource->size(), 0u); 162 163 ASSERT_EQ(0u, memoryCache()->deadSize()); 164 ASSERT_EQ(0u, memoryCache()->liveSize()); 165 166 memoryCache()->add(cachedResource.get()); 167 ASSERT_EQ(cachedResource->size(), memoryCache()->deadSize()); 168 ASSERT_EQ(0u, memoryCache()->liveSize()); 169 170 memoryCache()->prune(); 171 ASSERT_EQ(0u, memoryCache()->deadSize()); 172 ASSERT_EQ(0u, memoryCache()->liveSize()); 173 } 174 175 // Verified that when ordering a prune in a runLoop task, the prune 176 // is deferred to the end of the task. 177 TEST_F(MemoryCacheTest, LiveResourceEvictionAtEndOfTask) 178 { 179 memoryCache()->setDelayBeforeLiveDecodedPrune(0); 180 const unsigned totalCapacity = 1; 181 const unsigned minDeadCapacity = 0; 182 const unsigned maxDeadCapacity = 0; 183 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 184 const char data[6] = "abcde"; 185 ResourcePtr<Resource> cachedDeadResource = 186 new Resource(ResourceRequest(""), Resource::Raw); 187 cachedDeadResource->appendData(data, 3); 188 ResourcePtr<Resource> cachedLiveResource = 189 new FakeDecodedResource(ResourceRequest(""), Resource::Raw); 190 MockImageResourceClient client; 191 cachedLiveResource->addClient(&client); 192 cachedLiveResource->appendData(data, 4); 193 194 class Task1 : public blink::WebThread::Task { 195 public: 196 Task1(const ResourcePtr<Resource>& live, const ResourcePtr<Resource>& dead) 197 : m_live(live) 198 , m_dead(dead) 199 { } 200 201 virtual void run() OVERRIDE 202 { 203 // The resource size has to be nonzero for this test to be meaningful, but 204 // we do not rely on it having any particular value. 205 ASSERT_GT(m_live->size(), 0u); 206 ASSERT_GT(m_dead->size(), 0u); 207 208 ASSERT_EQ(0u, memoryCache()->deadSize()); 209 ASSERT_EQ(0u, memoryCache()->liveSize()); 210 211 memoryCache()->add(m_dead.get()); 212 memoryCache()->add(m_live.get()); 213 memoryCache()->insertInLiveDecodedResourcesList(m_live.get()); 214 ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); 215 ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); 216 ASSERT_GT(m_live->decodedSize(), 0u); 217 218 memoryCache()->prune(); // Dead resources are pruned immediately 219 ASSERT_EQ(m_dead->size(), memoryCache()->deadSize()); 220 ASSERT_EQ(m_live->size(), memoryCache()->liveSize()); 221 ASSERT_GT(m_live->decodedSize(), 0u); 222 } 223 224 private: 225 ResourcePtr<Resource> m_live, m_dead; 226 }; 227 228 class Task2 : public blink::WebThread::Task { 229 public: 230 Task2(unsigned liveSizeWithoutDecode) 231 : m_liveSizeWithoutDecode(liveSizeWithoutDecode) { } 232 233 virtual void run() OVERRIDE 234 { 235 // Next task: now, the live resource was evicted. 236 ASSERT_EQ(0u, memoryCache()->deadSize()); 237 ASSERT_EQ(m_liveSizeWithoutDecode, memoryCache()->liveSize()); 238 blink::Platform::current()->currentThread()->exitRunLoop(); 239 } 240 241 private: 242 unsigned m_liveSizeWithoutDecode; 243 }; 244 245 246 blink::Platform::current()->currentThread()->postTask(new Task1(cachedLiveResource, cachedDeadResource)); 247 blink::Platform::current()->currentThread()->postTask(new Task2(cachedLiveResource->encodedSize() + cachedLiveResource->overheadSize())); 248 blink::Platform::current()->currentThread()->enterRunLoop(); 249 cachedLiveResource->removeClient(&client); 250 } 251 252 // Verifies that cached resources are evicted immediately after release when 253 // the total dead resource size is more than double the dead resource capacity. 254 TEST_F(MemoryCacheTest, ClientRemoval) 255 { 256 const char data[6] = "abcde"; 257 ResourcePtr<Resource> resource1 = 258 new FakeDecodedResource(ResourceRequest(""), Resource::Raw); 259 MockImageResourceClient client1; 260 resource1->addClient(&client1); 261 resource1->appendData(data, 4); 262 ResourcePtr<Resource> resource2 = 263 new FakeDecodedResource(ResourceRequest(""), Resource::Raw); 264 MockImageResourceClient client2; 265 resource2->addClient(&client2); 266 resource2->appendData(data, 4); 267 268 const unsigned minDeadCapacity = 0; 269 const unsigned maxDeadCapacity = resource1->size() - 1; 270 const unsigned totalCapacity = maxDeadCapacity; 271 memoryCache()->setCapacities(minDeadCapacity, maxDeadCapacity, totalCapacity); 272 memoryCache()->add(resource1.get()); 273 memoryCache()->add(resource2.get()); 274 // Call prune. There is nothing to prune, but this will initialize 275 // the prune timestamp, allowing future prunes to be deferred. 276 memoryCache()->prune(); 277 ASSERT_GT(resource1->decodedSize(), 0u); 278 ASSERT_GT(resource2->decodedSize(), 0u); 279 ASSERT_EQ(memoryCache()->deadSize(), 0u); 280 ASSERT_EQ(memoryCache()->liveSize(), resource1->size() + resource2->size()); 281 282 // Removing the client from resource1 should result in all resources 283 // remaining in cache since the prune is deferred. 284 resource1->removeClient(&client1); 285 ASSERT_GT(resource1->decodedSize(), 0u); 286 ASSERT_GT(resource2->decodedSize(), 0u); 287 ASSERT_EQ(memoryCache()->deadSize(), resource1->size()); 288 ASSERT_EQ(memoryCache()->liveSize(), resource2->size()); 289 ASSERT_TRUE(resource1->inCache()); 290 ASSERT_TRUE(resource2->inCache()); 291 292 // Removing the client from resource2 should result in immediate 293 // eviction of resource2 because we are over the prune deferral limit. 294 resource2->removeClient(&client2); 295 ASSERT_GT(resource1->decodedSize(), 0u); 296 ASSERT_GT(resource2->decodedSize(), 0u); 297 ASSERT_EQ(memoryCache()->deadSize(), resource1->size()); 298 ASSERT_EQ(memoryCache()->liveSize(), 0u); 299 ASSERT_TRUE(resource1->inCache()); 300 ASSERT_FALSE(resource2->inCache()); 301 } 302 303 // Verifies that CachedResources are evicted from the decode cache 304 // according to their DecodeCachePriority. 305 TEST_F(MemoryCacheTest, DecodeCacheOrder) 306 { 307 memoryCache()->setDelayBeforeLiveDecodedPrune(0); 308 memoryCache()->setMaxPruneDeferralDelay(0); 309 ResourcePtr<FakeDecodedResource> cachedImageLowPriority = 310 new FakeDecodedResource(ResourceRequest(""), Resource::Raw); 311 ResourcePtr<FakeDecodedResource> cachedImageHighPriority = 312 new FakeDecodedResource(ResourceRequest(""), Resource::Raw); 313 314 MockImageResourceClient clientLowPriority; 315 MockImageResourceClient clientHighPriority; 316 cachedImageLowPriority->addClient(&clientLowPriority); 317 cachedImageHighPriority->addClient(&clientHighPriority); 318 319 const char data[5] = "abcd"; 320 cachedImageLowPriority->appendData(data, 1); 321 cachedImageHighPriority->appendData(data, 4); 322 const unsigned lowPrioritySize = cachedImageLowPriority->size(); 323 const unsigned highPrioritySize = cachedImageHighPriority->size(); 324 const unsigned lowPriorityMockDecodeSize = cachedImageLowPriority->decodedSize(); 325 const unsigned highPriorityMockDecodeSize = cachedImageHighPriority->decodedSize(); 326 const unsigned totalSize = lowPrioritySize + highPrioritySize; 327 328 // Verify that the sizes are different to ensure that we can test eviction order. 329 ASSERT_GT(lowPrioritySize, 0u); 330 ASSERT_NE(lowPrioritySize, highPrioritySize); 331 ASSERT_GT(lowPriorityMockDecodeSize, 0u); 332 ASSERT_NE(lowPriorityMockDecodeSize, highPriorityMockDecodeSize); 333 334 ASSERT_EQ(memoryCache()->deadSize(), 0u); 335 ASSERT_EQ(memoryCache()->liveSize(), 0u); 336 337 // Add the items. The item added first would normally be evicted first. 338 memoryCache()->add(cachedImageHighPriority.get()); 339 ASSERT_EQ(memoryCache()->deadSize(), 0u); 340 ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize); 341 342 memoryCache()->add(cachedImageLowPriority.get()); 343 ASSERT_EQ(memoryCache()->deadSize(), 0u); 344 ASSERT_EQ(memoryCache()->liveSize(), highPrioritySize + lowPrioritySize); 345 346 // Insert all items in the decoded items list with the same priority 347 memoryCache()->insertInLiveDecodedResourcesList(cachedImageHighPriority.get()); 348 memoryCache()->insertInLiveDecodedResourcesList(cachedImageLowPriority.get()); 349 ASSERT_EQ(memoryCache()->deadSize(), 0u); 350 ASSERT_EQ(memoryCache()->liveSize(), totalSize); 351 352 // Now we will assign their priority and make sure they are moved to the correct buckets. 353 cachedImageLowPriority->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityLow); 354 cachedImageHighPriority->setCacheLiveResourcePriority(Resource::CacheLiveResourcePriorityHigh); 355 356 // Should first prune the LowPriority item. 357 memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); 358 memoryCache()->prune(); 359 ASSERT_EQ(memoryCache()->deadSize(), 0u); 360 ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize); 361 362 // Should prune the HighPriority item. 363 memoryCache()->setCapacities(memoryCache()->minDeadCapacity(), memoryCache()->liveSize() - 10, memoryCache()->liveSize() - 10); 364 memoryCache()->prune(); 365 ASSERT_EQ(memoryCache()->deadSize(), 0u); 366 ASSERT_EQ(memoryCache()->liveSize(), totalSize - lowPriorityMockDecodeSize - highPriorityMockDecodeSize); 367 } 368 } // namespace 369