1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/files/scoped_temp_dir.h"
7 #include "base/logging.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/pickle.h"
10 #include "base/sha1.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/task_runner.h"
13 #include "base/threading/platform_thread.h"
14 #include "base/time/time.h"
15 #include "net/disk_cache/simple/simple_index.h"
16 #include "net/disk_cache/simple/simple_index_file.h"
17 #include "net/disk_cache/simple/simple_util.h"
18 #include "testing/gtest/include/gtest/gtest.h"
22 const int64 kTestLastUsedTimeInternal
= 12345;
23 const base::Time kTestLastUsedTime
=
24 base::Time::FromInternalValue(kTestLastUsedTimeInternal
);
25 const uint64 kTestEntrySize
= 789;
26 const uint64 kKey1Hash
= disk_cache::simple_util::GetEntryHashKey("key1");
27 const uint64 kKey2Hash
= disk_cache::simple_util::GetEntryHashKey("key2");
28 const uint64 kKey3Hash
= disk_cache::simple_util::GetEntryHashKey("key3");
32 namespace disk_cache
{
34 class EntryMetadataTest
: public testing::Test
{
36 EntryMetadata
NewEntryMetadataWithValues() {
37 return EntryMetadata(kTestLastUsedTime
, kTestEntrySize
);
40 void CheckEntryMetadataValues(const EntryMetadata
& entry_metadata
) {
41 EXPECT_EQ(kTestLastUsedTime
, entry_metadata
.GetLastUsedTime());
42 EXPECT_EQ(kTestEntrySize
, entry_metadata
.GetEntrySize());
46 class MockSimpleIndexFile
: public SimpleIndexFile
,
47 public base::SupportsWeakPtr
<MockSimpleIndexFile
> {
50 : SimpleIndexFile(NULL
, NULL
, base::FilePath()),
52 load_index_entries_calls_(0),
53 doom_entry_set_calls_(0),
56 virtual void LoadIndexEntries(
57 base::Time cache_last_modified
,
58 const base::Closure
& callback
,
59 SimpleIndexLoadResult
* out_load_result
) OVERRIDE
{
60 load_callback_
= callback
;
61 load_result_
= out_load_result
;
62 ++load_index_entries_calls_
;
65 virtual void WriteToDisk(const SimpleIndex::EntrySet
& entry_set
,
67 const base::TimeTicks
& start
,
68 bool app_on_background
) OVERRIDE
{
70 disk_write_entry_set_
= entry_set
;
73 virtual void DoomEntrySet(
74 scoped_ptr
<std::vector
<uint64
> > entry_hashes
,
75 const base::Callback
<void(int)>& reply_callback
) OVERRIDE
{
76 last_doom_entry_hashes_
= *entry_hashes
.get();
77 last_doom_reply_callback_
= reply_callback
;
78 ++doom_entry_set_calls_
;
81 void GetAndResetDiskWriteEntrySet(SimpleIndex::EntrySet
* entry_set
) {
82 entry_set
->swap(disk_write_entry_set_
);
85 const base::Closure
& load_callback() const { return load_callback_
; }
86 SimpleIndexLoadResult
* load_result() const { return load_result_
; }
87 int load_index_entries_calls() const { return load_index_entries_calls_
; }
88 int disk_writes() const { return disk_writes_
; }
89 const std::vector
<uint64
>& last_doom_entry_hashes() const {
90 return last_doom_entry_hashes_
;
92 int doom_entry_set_calls() const { return doom_entry_set_calls_
; }
95 base::Closure load_callback_
;
96 SimpleIndexLoadResult
* load_result_
;
97 int load_index_entries_calls_
;
98 std::vector
<uint64
> last_doom_entry_hashes_
;
99 int doom_entry_set_calls_
;
100 base::Callback
<void(int)> last_doom_reply_callback_
;
102 SimpleIndex::EntrySet disk_write_entry_set_
;
105 class SimpleIndexTest
: public testing::Test
{
107 virtual void SetUp() OVERRIDE
{
108 scoped_ptr
<MockSimpleIndexFile
> index_file(new MockSimpleIndexFile());
109 index_file_
= index_file
->AsWeakPtr();
110 index_
.reset(new SimpleIndex(NULL
, base::FilePath(),
111 index_file
.PassAs
<SimpleIndexFile
>()));
113 index_
->Initialize(base::Time());
116 void WaitForTimeChange() {
117 base::Time
now(base::Time::Now());
120 base::PlatformThread::YieldCurrentThread();
121 } while (now
== base::Time::Now());
124 // Redirect to allow single "friend" declaration in base class.
125 bool GetEntryForTesting(const std::string
& key
, EntryMetadata
* metadata
) {
126 const uint64 hash_key
= simple_util::GetEntryHashKey(key
);
127 SimpleIndex::EntrySet::iterator it
= index_
->entries_set_
.find(hash_key
);
128 if (index_
->entries_set_
.end() == it
)
130 *metadata
= it
->second
;
134 void InsertIntoIndexFileReturn(const std::string
& key
,
135 base::Time last_used_time
,
137 uint64
hash_key(simple_util::GetEntryHashKey(key
));
138 index_file_
->load_result()->entries
.insert(std::make_pair(
139 hash_key
, EntryMetadata(last_used_time
, entry_size
)));
142 void ReturnIndexFile() {
143 index_file_
->load_result()->did_load
= true;
144 index_file_
->load_callback().Run();
147 // Non-const for timer manipulation.
148 SimpleIndex
* index() { return index_
.get(); }
149 const MockSimpleIndexFile
* index_file() const { return index_file_
.get(); }
152 scoped_ptr
<SimpleIndex
> index_
;
153 base::WeakPtr
<MockSimpleIndexFile
> index_file_
;
156 TEST_F(EntryMetadataTest
, Basics
) {
157 EntryMetadata entry_metadata
;
158 EXPECT_EQ(base::Time::FromInternalValue(0), entry_metadata
.GetLastUsedTime());
159 EXPECT_EQ(size_t(0), entry_metadata
.GetEntrySize());
161 entry_metadata
= NewEntryMetadataWithValues();
162 CheckEntryMetadataValues(entry_metadata
);
164 const base::Time new_time
= base::Time::FromInternalValue(5);
165 entry_metadata
.SetLastUsedTime(new_time
);
166 EXPECT_EQ(new_time
, entry_metadata
.GetLastUsedTime());
169 TEST_F(EntryMetadataTest
, Serialize
) {
170 EntryMetadata entry_metadata
= NewEntryMetadataWithValues();
173 entry_metadata
.Serialize(&pickle
);
175 PickleIterator
it(pickle
);
176 EntryMetadata new_entry_metadata
;
177 new_entry_metadata
.Deserialize(&it
);
178 CheckEntryMetadataValues(new_entry_metadata
);
181 TEST_F(SimpleIndexTest
, IndexSizeCorrectOnMerge
) {
182 typedef disk_cache::SimpleIndex::EntrySet EntrySet
;
183 index()->SetMaxSize(100);
184 index()->Insert("two");
185 index()->UpdateEntrySize("two", 2);
186 index()->Insert("five");
187 index()->UpdateEntrySize("five", 5);
188 index()->Insert("seven");
189 index()->UpdateEntrySize("seven", 7);
190 EXPECT_EQ(14U, index()->cache_size_
);
192 scoped_ptr
<SimpleIndexLoadResult
> result(new SimpleIndexLoadResult());
193 result
->did_load
= true;
194 index()->MergeInitializingSet(result
.Pass());
196 EXPECT_EQ(14U, index()->cache_size_
);
198 scoped_ptr
<SimpleIndexLoadResult
> result(new SimpleIndexLoadResult());
199 result
->did_load
= true;
200 const uint64 new_hash_key
= simple_util::GetEntryHashKey("eleven");
201 result
->entries
.insert(
202 std::make_pair(new_hash_key
, EntryMetadata(base::Time::Now(), 11)));
203 const uint64 redundant_hash_key
= simple_util::GetEntryHashKey("seven");
204 result
->entries
.insert(std::make_pair(redundant_hash_key
,
205 EntryMetadata(base::Time::Now(), 7)));
206 index()->MergeInitializingSet(result
.Pass());
208 EXPECT_EQ(2U + 5U + 7U + 11U, index()->cache_size_
);
211 // State of index changes as expected with an insert and a remove.
212 TEST_F(SimpleIndexTest
, BasicInsertRemove
) {
213 // Confirm blank state.
214 EntryMetadata metadata
;
215 EXPECT_EQ(base::Time(), metadata
.GetLastUsedTime());
216 EXPECT_EQ(0ul, metadata
.GetEntrySize());
218 // Confirm state after insert.
219 index()->Insert("key1");
220 EXPECT_TRUE(GetEntryForTesting("key1", &metadata
));
221 base::Time
now(base::Time::Now());
222 EXPECT_LT(now
- base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
223 EXPECT_GT(now
+ base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
224 EXPECT_EQ(0ul, metadata
.GetEntrySize());
226 // Confirm state after remove.
227 metadata
= EntryMetadata();
228 index()->Remove("key1");
229 EXPECT_FALSE(GetEntryForTesting("key1", &metadata
));
230 EXPECT_EQ(base::Time(), metadata
.GetLastUsedTime());
231 EXPECT_EQ(0ul, metadata
.GetEntrySize());
234 TEST_F(SimpleIndexTest
, Has
) {
235 // Confirm the base index has dispatched the request for index entries.
236 EXPECT_TRUE(index_file_
.get());
237 EXPECT_EQ(1, index_file_
->load_index_entries_calls());
239 // Confirm "Has()" always returns true before the callback is called.
240 EXPECT_TRUE(index()->Has(kKey1Hash
));
241 index()->Insert("key1");
242 EXPECT_TRUE(index()->Has(kKey1Hash
));
243 index()->Remove("key1");
244 // TODO(rdsmith): Maybe return false on explicitly removed entries?
245 EXPECT_TRUE(index()->Has(kKey1Hash
));
249 // Confirm "Has() returns conditionally now.
250 EXPECT_FALSE(index()->Has(kKey1Hash
));
251 index()->Insert("key1");
252 EXPECT_TRUE(index()->Has(kKey1Hash
));
253 index()->Remove("key1");
256 TEST_F(SimpleIndexTest
, UseIfExists
) {
257 // Confirm the base index has dispatched the request for index entries.
258 EXPECT_TRUE(index_file_
.get());
259 EXPECT_EQ(1, index_file_
->load_index_entries_calls());
261 // Confirm "UseIfExists()" always returns true before the callback is called
262 // and updates mod time if the entry was really there.
263 EntryMetadata metadata1
, metadata2
;
264 EXPECT_TRUE(index()->UseIfExists("key1"));
265 EXPECT_FALSE(GetEntryForTesting("key1", &metadata1
));
266 index()->Insert("key1");
267 EXPECT_TRUE(index()->UseIfExists("key1"));
268 EXPECT_TRUE(GetEntryForTesting("key1", &metadata1
));
270 EXPECT_TRUE(GetEntryForTesting("key1", &metadata2
));
271 EXPECT_EQ(metadata1
.GetLastUsedTime(), metadata2
.GetLastUsedTime());
272 EXPECT_TRUE(index()->UseIfExists("key1"));
273 EXPECT_TRUE(GetEntryForTesting("key1", &metadata2
));
274 EXPECT_LT(metadata1
.GetLastUsedTime(), metadata2
.GetLastUsedTime());
275 index()->Remove("key1");
276 EXPECT_TRUE(index()->UseIfExists("key1"));
280 // Confirm "UseIfExists() returns conditionally now
281 EXPECT_FALSE(index()->UseIfExists("key1"));
282 EXPECT_FALSE(GetEntryForTesting("key1", &metadata1
));
283 index()->Insert("key1");
284 EXPECT_TRUE(index()->UseIfExists("key1"));
285 EXPECT_TRUE(GetEntryForTesting("key1", &metadata1
));
287 EXPECT_TRUE(GetEntryForTesting("key1", &metadata2
));
288 EXPECT_EQ(metadata1
.GetLastUsedTime(), metadata2
.GetLastUsedTime());
289 EXPECT_TRUE(index()->UseIfExists("key1"));
290 EXPECT_TRUE(GetEntryForTesting("key1", &metadata2
));
291 EXPECT_LT(metadata1
.GetLastUsedTime(), metadata2
.GetLastUsedTime());
292 index()->Remove("key1");
293 EXPECT_FALSE(index()->UseIfExists("key1"));
296 TEST_F(SimpleIndexTest
, UpdateEntrySize
) {
297 base::Time
now(base::Time::Now());
299 index()->SetMaxSize(1000);
301 InsertIntoIndexFileReturn("key1",
302 now
- base::TimeDelta::FromDays(2),
306 EntryMetadata metadata
;
307 EXPECT_TRUE(GetEntryForTesting("key1", &metadata
));
308 EXPECT_EQ(now
- base::TimeDelta::FromDays(2), metadata
.GetLastUsedTime());
309 EXPECT_EQ(475u, metadata
.GetEntrySize());
311 index()->UpdateEntrySize("key1", 600u);
312 EXPECT_TRUE(GetEntryForTesting("key1", &metadata
));
313 EXPECT_EQ(600u, metadata
.GetEntrySize());
314 EXPECT_EQ(1, index()->GetEntryCount());
317 TEST_F(SimpleIndexTest
, GetEntryCount
) {
318 EXPECT_EQ(0, index()->GetEntryCount());
319 index()->Insert("key1");
320 EXPECT_EQ(1, index()->GetEntryCount());
321 index()->Insert("key2");
322 EXPECT_EQ(2, index()->GetEntryCount());
323 index()->Insert("key3");
324 EXPECT_EQ(3, index()->GetEntryCount());
325 index()->Insert("key3");
326 EXPECT_EQ(3, index()->GetEntryCount());
327 index()->Remove("key2");
328 EXPECT_EQ(2, index()->GetEntryCount());
329 index()->Insert("key4");
330 EXPECT_EQ(3, index()->GetEntryCount());
331 index()->Remove("key3");
332 EXPECT_EQ(2, index()->GetEntryCount());
333 index()->Remove("key3");
334 EXPECT_EQ(2, index()->GetEntryCount());
335 index()->Remove("key1");
336 EXPECT_EQ(1, index()->GetEntryCount());
337 index()->Remove("key4");
338 EXPECT_EQ(0, index()->GetEntryCount());
341 // Confirm that we get the results we expect from a simple init.
342 TEST_F(SimpleIndexTest
, BasicInit
) {
343 base::Time
now(base::Time::Now());
345 InsertIntoIndexFileReturn("key1",
346 now
- base::TimeDelta::FromDays(2),
348 InsertIntoIndexFileReturn("key2",
349 now
- base::TimeDelta::FromDays(3),
354 EntryMetadata metadata
;
355 EXPECT_TRUE(GetEntryForTesting("key1", &metadata
));
356 EXPECT_EQ(now
- base::TimeDelta::FromDays(2), metadata
.GetLastUsedTime());
357 EXPECT_EQ(10ul, metadata
.GetEntrySize());
358 EXPECT_TRUE(GetEntryForTesting("key2", &metadata
));
359 EXPECT_EQ(now
- base::TimeDelta::FromDays(3), metadata
.GetLastUsedTime());
360 EXPECT_EQ(100ul, metadata
.GetEntrySize());
363 // Remove something that's going to come in from the loaded index.
364 TEST_F(SimpleIndexTest
, RemoveBeforeInit
) {
365 index()->Remove("key1");
367 InsertIntoIndexFileReturn("key1",
368 base::Time::Now() - base::TimeDelta::FromDays(2),
372 EXPECT_FALSE(index()->Has(kKey1Hash
));
375 // Insert something that's going to come in from the loaded index; correct
377 TEST_F(SimpleIndexTest
, InsertBeforeInit
) {
378 index()->Insert("key1");
380 InsertIntoIndexFileReturn("key1",
381 base::Time::Now() - base::TimeDelta::FromDays(2),
385 EntryMetadata metadata
;
386 EXPECT_TRUE(GetEntryForTesting("key1", &metadata
));
387 base::Time
now(base::Time::Now());
388 EXPECT_LT(now
- base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
389 EXPECT_GT(now
+ base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
390 EXPECT_EQ(0ul, metadata
.GetEntrySize());
393 // Insert and Remove something that's going to come in from the loaded index.
394 TEST_F(SimpleIndexTest
, InsertRemoveBeforeInit
) {
395 index()->Insert("key1");
396 index()->Remove("key1");
398 InsertIntoIndexFileReturn("key1",
399 base::Time::Now() - base::TimeDelta::FromDays(2),
403 EXPECT_FALSE(index()->Has(kKey1Hash
));
406 // Insert and Remove something that's going to come in from the loaded index.
407 TEST_F(SimpleIndexTest
, RemoveInsertBeforeInit
) {
408 index()->Remove("key1");
409 index()->Insert("key1");
411 InsertIntoIndexFileReturn("key1",
412 base::Time::Now() - base::TimeDelta::FromDays(2),
416 EntryMetadata metadata
;
417 EXPECT_TRUE(GetEntryForTesting("key1", &metadata
));
418 base::Time
now(base::Time::Now());
419 EXPECT_LT(now
- base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
420 EXPECT_GT(now
+ base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
421 EXPECT_EQ(0ul, metadata
.GetEntrySize());
424 // Do all above tests at once + a non-conflict to test for cross-key
426 TEST_F(SimpleIndexTest
, AllInitConflicts
) {
427 base::Time
now(base::Time::Now());
429 index()->Remove("key1");
430 InsertIntoIndexFileReturn("key1",
431 now
- base::TimeDelta::FromDays(2),
433 index()->Insert("key2");
434 InsertIntoIndexFileReturn("key2",
435 now
- base::TimeDelta::FromDays(3),
437 index()->Insert("key3");
438 index()->Remove("key3");
439 InsertIntoIndexFileReturn("key3",
440 now
- base::TimeDelta::FromDays(4),
442 index()->Remove("key4");
443 index()->Insert("key4");
444 InsertIntoIndexFileReturn("key4",
445 now
- base::TimeDelta::FromDays(5),
447 InsertIntoIndexFileReturn("key5",
448 now
- base::TimeDelta::FromDays(6),
453 EXPECT_FALSE(index()->Has(kKey1Hash
));
455 EntryMetadata metadata
;
456 EXPECT_TRUE(GetEntryForTesting("key2", &metadata
));
457 EXPECT_LT(now
- base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
458 EXPECT_GT(now
+ base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
459 EXPECT_EQ(0ul, metadata
.GetEntrySize());
461 EXPECT_FALSE(index()->Has(kKey3Hash
));
463 EXPECT_TRUE(GetEntryForTesting("key4", &metadata
));
464 EXPECT_LT(now
- base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
465 EXPECT_GT(now
+ base::TimeDelta::FromMinutes(1), metadata
.GetLastUsedTime());
466 EXPECT_EQ(0ul, metadata
.GetEntrySize());
468 EXPECT_TRUE(GetEntryForTesting("key5", &metadata
));
469 EXPECT_EQ(now
- base::TimeDelta::FromDays(6), metadata
.GetLastUsedTime());
470 EXPECT_EQ(100000u, metadata
.GetEntrySize());
473 TEST_F(SimpleIndexTest
, BasicEviction
) {
474 base::Time
now(base::Time::Now());
475 index()->SetMaxSize(1000);
476 InsertIntoIndexFileReturn("key1",
477 now
- base::TimeDelta::FromDays(2),
479 index()->Insert("key2");
480 index()->UpdateEntrySize("key2", 475);
485 index()->Insert("key3");
486 // Confirm index is as expected: No eviction, everything there.
487 EXPECT_EQ(3, index()->GetEntryCount());
488 EXPECT_EQ(0, index_file()->doom_entry_set_calls());
489 EXPECT_TRUE(index()->Has(kKey1Hash
));
490 EXPECT_TRUE(index()->Has(kKey2Hash
));
491 EXPECT_TRUE(index()->Has(kKey3Hash
));
493 // Trigger an eviction, and make sure the right things are tossed.
494 // TODO(rdsmith): This is dependent on the innards of the implementation
495 // as to at exactly what point we trigger eviction. Not sure how to fix
497 index()->UpdateEntrySize("key3", 475);
498 EXPECT_EQ(1, index_file()->doom_entry_set_calls());
499 EXPECT_EQ(1, index()->GetEntryCount());
500 EXPECT_FALSE(index()->Has(kKey1Hash
));
501 EXPECT_FALSE(index()->Has(kKey2Hash
));
502 EXPECT_TRUE(index()->Has(kKey3Hash
));
503 ASSERT_EQ(2u, index_file_
->last_doom_entry_hashes().size());
506 // Confirm all the operations queue a disk write at some point in the
508 TEST_F(SimpleIndexTest
, DiskWriteQueued
) {
509 index()->SetMaxSize(1000);
512 EXPECT_FALSE(index()->write_to_disk_timer_
.IsRunning());
514 index()->Insert("key1");
515 EXPECT_TRUE(index()->write_to_disk_timer_
.IsRunning());
516 index()->write_to_disk_timer_
.Stop();
517 EXPECT_FALSE(index()->write_to_disk_timer_
.IsRunning());
519 index()->UseIfExists("key1");
520 EXPECT_TRUE(index()->write_to_disk_timer_
.IsRunning());
521 index()->write_to_disk_timer_
.Stop();
523 index()->UpdateEntrySize("key1", 20);
524 EXPECT_TRUE(index()->write_to_disk_timer_
.IsRunning());
525 index()->write_to_disk_timer_
.Stop();
527 index()->Remove("key1");
528 EXPECT_TRUE(index()->write_to_disk_timer_
.IsRunning());
529 index()->write_to_disk_timer_
.Stop();
532 TEST_F(SimpleIndexTest
, DiskWriteExecuted
) {
533 index()->SetMaxSize(1000);
536 EXPECT_FALSE(index()->write_to_disk_timer_
.IsRunning());
538 index()->Insert("key1");
539 index()->UpdateEntrySize("key1", 20);
540 EXPECT_TRUE(index()->write_to_disk_timer_
.IsRunning());
541 base::Closure
user_task(index()->write_to_disk_timer_
.user_task());
542 index()->write_to_disk_timer_
.Stop();
544 EXPECT_EQ(0, index_file_
->disk_writes());
546 EXPECT_EQ(1, index_file_
->disk_writes());
547 SimpleIndex::EntrySet entry_set
;
548 index_file_
->GetAndResetDiskWriteEntrySet(&entry_set
);
550 uint64
hash_key(simple_util::GetEntryHashKey("key1"));
551 base::Time
now(base::Time::Now());
552 ASSERT_EQ(1u, entry_set
.size());
553 EXPECT_EQ(hash_key
, entry_set
.begin()->first
);
554 const EntryMetadata
& entry1(entry_set
.begin()->second
);
555 EXPECT_LT(now
- base::TimeDelta::FromMinutes(1), entry1
.GetLastUsedTime());
556 EXPECT_GT(now
+ base::TimeDelta::FromMinutes(1), entry1
.GetLastUsedTime());
557 EXPECT_EQ(20u, entry1
.GetEntrySize());
560 TEST_F(SimpleIndexTest
, DiskWritePostponed
) {
561 index()->SetMaxSize(1000);
564 EXPECT_FALSE(index()->write_to_disk_timer_
.IsRunning());
566 index()->Insert("key1");
567 index()->UpdateEntrySize("key1", 20);
568 EXPECT_TRUE(index()->write_to_disk_timer_
.IsRunning());
569 base::TimeTicks
expected_trigger(
570 index()->write_to_disk_timer_
.desired_run_time());
573 EXPECT_EQ(expected_trigger
, index()->write_to_disk_timer_
.desired_run_time());
574 index()->Insert("key2");
575 index()->UpdateEntrySize("key2", 40);
576 EXPECT_TRUE(index()->write_to_disk_timer_
.IsRunning());
577 EXPECT_LT(expected_trigger
, index()->write_to_disk_timer_
.desired_run_time());
578 index()->write_to_disk_timer_
.Stop();
581 } // namespace disk_cache