Home | History | Annotate | Download | only in fetch
      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