Windows should animate when they are about to get docked at screen edges.
[chromium-blink-merge.git] / net / disk_cache / simple / simple_backend_impl.cc
blob2877c01f70138919e232a30c1f1ae3c4d4644a32
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 "net/disk_cache/simple/simple_backend_impl.h"
7 #include <algorithm>
8 #include <cstdlib>
10 #if defined(OS_POSIX)
11 #include <sys/resource.h>
12 #endif
14 #include "base/bind.h"
15 #include "base/callback.h"
16 #include "base/file_util.h"
17 #include "base/location.h"
18 #include "base/message_loop/message_loop_proxy.h"
19 #include "base/metrics/field_trial.h"
20 #include "base/metrics/histogram.h"
21 #include "base/metrics/sparse_histogram.h"
22 #include "base/single_thread_task_runner.h"
23 #include "base/sys_info.h"
24 #include "base/task_runner_util.h"
25 #include "base/threading/sequenced_worker_pool.h"
26 #include "base/time/time.h"
27 #include "net/base/net_errors.h"
28 #include "net/disk_cache/backend_impl.h"
29 #include "net/disk_cache/simple/simple_entry_format.h"
30 #include "net/disk_cache/simple/simple_entry_impl.h"
31 #include "net/disk_cache/simple/simple_index.h"
32 #include "net/disk_cache/simple/simple_index_file.h"
33 #include "net/disk_cache/simple/simple_synchronous_entry.h"
34 #include "net/disk_cache/simple/simple_util.h"
36 using base::Closure;
37 using base::FilePath;
38 using base::MessageLoopProxy;
39 using base::SequencedWorkerPool;
40 using base::SingleThreadTaskRunner;
41 using base::Time;
42 using base::DirectoryExists;
43 using file_util::CreateDirectory;
45 namespace {
47 // Maximum number of concurrent worker pool threads, which also is the limit
48 // on concurrent IO (as we use one thread per IO request).
49 const int kDefaultMaxWorkerThreads = 50;
51 const char kThreadNamePrefix[] = "SimpleCache";
53 // Cache size when all other size heuristics failed.
54 const uint64 kDefaultCacheSize = 80 * 1024 * 1024;
56 // Maximum fraction of the cache that one entry can consume.
57 const int kMaxFileRatio = 8;
59 // A global sequenced worker pool to use for launching all tasks.
60 SequencedWorkerPool* g_sequenced_worker_pool = NULL;
62 void MaybeCreateSequencedWorkerPool() {
63 if (!g_sequenced_worker_pool) {
64 int max_worker_threads = kDefaultMaxWorkerThreads;
66 const std::string thread_count_field_trial =
67 base::FieldTrialList::FindFullName("SimpleCacheMaxThreads");
68 if (!thread_count_field_trial.empty()) {
69 max_worker_threads =
70 std::max(1, std::atoi(thread_count_field_trial.c_str()));
73 g_sequenced_worker_pool = new SequencedWorkerPool(max_worker_threads,
74 kThreadNamePrefix);
75 g_sequenced_worker_pool->AddRef(); // Leak it.
79 bool g_fd_limit_histogram_has_been_populated = false;
81 void MaybeHistogramFdLimit() {
82 if (g_fd_limit_histogram_has_been_populated)
83 return;
85 // Used in histograms; add new entries at end.
86 enum FdLimitStatus {
87 FD_LIMIT_STATUS_UNSUPPORTED = 0,
88 FD_LIMIT_STATUS_FAILED = 1,
89 FD_LIMIT_STATUS_SUCCEEDED = 2,
90 FD_LIMIT_STATUS_MAX = 3
92 FdLimitStatus fd_limit_status = FD_LIMIT_STATUS_UNSUPPORTED;
93 int soft_fd_limit = 0;
94 int hard_fd_limit = 0;
96 #if defined(OS_POSIX)
97 struct rlimit nofile;
98 if (!getrlimit(RLIMIT_NOFILE, &nofile)) {
99 soft_fd_limit = nofile.rlim_cur;
100 hard_fd_limit = nofile.rlim_max;
101 fd_limit_status = FD_LIMIT_STATUS_SUCCEEDED;
102 } else {
103 fd_limit_status = FD_LIMIT_STATUS_FAILED;
105 #endif
107 UMA_HISTOGRAM_ENUMERATION("SimpleCache.FileDescriptorLimitStatus",
108 fd_limit_status, FD_LIMIT_STATUS_MAX);
109 if (fd_limit_status == FD_LIMIT_STATUS_SUCCEEDED) {
110 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleCache.FileDescriptorLimitSoft",
111 soft_fd_limit);
112 UMA_HISTOGRAM_SPARSE_SLOWLY("SimpleCache.FileDescriptorLimitHard",
113 hard_fd_limit);
116 g_fd_limit_histogram_has_been_populated = true;
119 // Must run on IO Thread.
120 void DeleteBackendImpl(disk_cache::Backend** backend,
121 const net::CompletionCallback& callback,
122 int result) {
123 DCHECK(*backend);
124 delete *backend;
125 *backend = NULL;
126 callback.Run(result);
129 // Detects if the files in the cache directory match the current disk cache
130 // backend type and version. If the directory contains no cache, occupies it
131 // with the fresh structure.
133 // There is a convention among disk cache backends: looking at the magic in the
134 // file "index" it should be sufficient to determine if the cache belongs to the
135 // currently running backend. The Simple Backend stores its index in the file
136 // "the-real-index" (see simple_index.cc) and the file "index" only signifies
137 // presence of the implementation's magic and version. There are two reasons for
138 // that:
139 // 1. Absence of the index is itself not a fatal error in the Simple Backend
140 // 2. The Simple Backend has pickled file format for the index making it hacky
141 // to have the magic in the right place.
142 bool FileStructureConsistent(const base::FilePath& path) {
143 if (!base::PathExists(path) && !file_util::CreateDirectory(path)) {
144 LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName();
145 return false;
147 const base::FilePath fake_index = path.AppendASCII("index");
148 base::PlatformFileError error;
149 base::PlatformFile fake_index_file = base::CreatePlatformFile(
150 fake_index,
151 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
152 NULL,
153 &error);
154 if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) {
155 base::PlatformFile file = base::CreatePlatformFile(
156 fake_index,
157 base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
158 NULL, &error);
159 disk_cache::SimpleFileHeader file_contents;
160 file_contents.initial_magic_number = disk_cache::kSimpleInitialMagicNumber;
161 file_contents.version = disk_cache::kSimpleVersion;
162 int bytes_written = base::WritePlatformFile(
163 file, 0, reinterpret_cast<char*>(&file_contents),
164 sizeof(file_contents));
165 if (!base::ClosePlatformFile(file) ||
166 bytes_written != sizeof(file_contents)) {
167 LOG(ERROR) << "Failed to write cache structure file: "
168 << path.LossyDisplayName();
169 return false;
171 return true;
172 } else if (error != base::PLATFORM_FILE_OK) {
173 LOG(ERROR) << "Could not open cache structure file: "
174 << path.LossyDisplayName();
175 return false;
176 } else {
177 disk_cache::SimpleFileHeader file_header;
178 int bytes_read = base::ReadPlatformFile(
179 fake_index_file, 0, reinterpret_cast<char*>(&file_header),
180 sizeof(file_header));
181 if (!base::ClosePlatformFile(fake_index_file) ||
182 bytes_read != sizeof(file_header) ||
183 file_header.initial_magic_number !=
184 disk_cache::kSimpleInitialMagicNumber ||
185 file_header.version != disk_cache::kSimpleVersion) {
186 LOG(ERROR) << "File structure does not match the disk cache backend.";
187 return false;
189 return true;
193 void CallCompletionCallback(const net::CompletionCallback& callback,
194 int error_code) {
195 DCHECK(!callback.is_null());
196 callback.Run(error_code);
199 void RecordIndexLoad(base::TimeTicks constructed_since, int result) {
200 const base::TimeDelta creation_to_index = base::TimeTicks::Now() -
201 constructed_since;
202 if (result == net::OK)
203 UMA_HISTOGRAM_TIMES("SimpleCache.CreationToIndex", creation_to_index);
204 else
205 UMA_HISTOGRAM_TIMES("SimpleCache.CreationToIndexFail", creation_to_index);
208 } // namespace
210 namespace disk_cache {
212 SimpleBackendImpl::SimpleBackendImpl(const FilePath& path,
213 int max_bytes,
214 net::CacheType type,
215 base::SingleThreadTaskRunner* cache_thread,
216 net::NetLog* net_log)
217 : path_(path),
218 cache_thread_(cache_thread),
219 orig_max_size_(max_bytes),
220 entry_operations_mode_(
221 type == net::DISK_CACHE ?
222 SimpleEntryImpl::OPTIMISTIC_OPERATIONS :
223 SimpleEntryImpl::NON_OPTIMISTIC_OPERATIONS),
224 net_log_(net_log) {
225 MaybeHistogramFdLimit();
228 SimpleBackendImpl::~SimpleBackendImpl() {
229 index_->WriteToDisk();
232 int SimpleBackendImpl::Init(const CompletionCallback& completion_callback) {
233 MaybeCreateSequencedWorkerPool();
235 worker_pool_ = g_sequenced_worker_pool->GetTaskRunnerWithShutdownBehavior(
236 SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
238 index_.reset(
239 new SimpleIndex(MessageLoopProxy::current().get(),
240 path_,
241 make_scoped_ptr(new SimpleIndexFile(
242 cache_thread_.get(), worker_pool_.get(), path_))));
243 index_->ExecuteWhenReady(base::Bind(&RecordIndexLoad,
244 base::TimeTicks::Now()));
246 PostTaskAndReplyWithResult(
247 cache_thread_,
248 FROM_HERE,
249 base::Bind(&SimpleBackendImpl::InitCacheStructureOnDisk, path_,
250 orig_max_size_),
251 base::Bind(&SimpleBackendImpl::InitializeIndex, AsWeakPtr(),
252 completion_callback));
253 return net::ERR_IO_PENDING;
256 bool SimpleBackendImpl::SetMaxSize(int max_bytes) {
257 orig_max_size_ = max_bytes;
258 return index_->SetMaxSize(max_bytes);
261 int SimpleBackendImpl::GetMaxFileSize() const {
262 return index_->max_size() / kMaxFileRatio;
265 void SimpleBackendImpl::OnDeactivated(const SimpleEntryImpl* entry) {
266 active_entries_.erase(entry->entry_hash());
269 net::CacheType SimpleBackendImpl::GetCacheType() const {
270 return net::DISK_CACHE;
273 int32 SimpleBackendImpl::GetEntryCount() const {
274 // TODO(pasko): Use directory file count when index is not ready.
275 return index_->GetEntryCount();
278 int SimpleBackendImpl::OpenEntry(const std::string& key,
279 Entry** entry,
280 const CompletionCallback& callback) {
281 scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key);
282 CompletionCallback backend_callback =
283 base::Bind(&SimpleBackendImpl::OnEntryOpenedFromKey,
284 AsWeakPtr(),
285 key,
286 entry,
287 simple_entry,
288 callback);
289 return simple_entry->OpenEntry(entry, backend_callback);
292 int SimpleBackendImpl::CreateEntry(const std::string& key,
293 Entry** entry,
294 const CompletionCallback& callback) {
295 DCHECK(key.size() > 0);
296 scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key);
297 return simple_entry->CreateEntry(entry, callback);
300 int SimpleBackendImpl::DoomEntry(const std::string& key,
301 const net::CompletionCallback& callback) {
302 scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key);
303 return simple_entry->DoomEntry(callback);
306 int SimpleBackendImpl::DoomAllEntries(const CompletionCallback& callback) {
307 return DoomEntriesBetween(Time(), Time(), callback);
310 void SimpleBackendImpl::IndexReadyForDoom(Time initial_time,
311 Time end_time,
312 const CompletionCallback& callback,
313 int result) {
314 if (result != net::OK) {
315 callback.Run(result);
316 return;
318 scoped_ptr<std::vector<uint64> > removed_key_hashes(
319 index_->RemoveEntriesBetween(initial_time, end_time).release());
321 // If any of the entries we are dooming are currently open, we need to remove
322 // them from |active_entries_|, so that attempts to create new entries will
323 // succeed and attempts to open them will fail.
324 for (int i = removed_key_hashes->size() - 1; i >= 0; --i) {
325 const uint64 entry_hash = (*removed_key_hashes)[i];
326 EntryMap::iterator it = active_entries_.find(entry_hash);
327 if (it == active_entries_.end())
328 continue;
329 SimpleEntryImpl* entry = it->second.get();
330 entry->Doom();
332 (*removed_key_hashes)[i] = removed_key_hashes->back();
333 removed_key_hashes->resize(removed_key_hashes->size() - 1);
336 PostTaskAndReplyWithResult(
337 worker_pool_, FROM_HERE,
338 base::Bind(&SimpleSynchronousEntry::DoomEntrySet,
339 base::Passed(&removed_key_hashes), path_),
340 base::Bind(&CallCompletionCallback, callback));
343 int SimpleBackendImpl::DoomEntriesBetween(
344 const Time initial_time,
345 const Time end_time,
346 const CompletionCallback& callback) {
347 return index_->ExecuteWhenReady(
348 base::Bind(&SimpleBackendImpl::IndexReadyForDoom, AsWeakPtr(),
349 initial_time, end_time, callback));
352 int SimpleBackendImpl::DoomEntriesSince(
353 const Time initial_time,
354 const CompletionCallback& callback) {
355 return DoomEntriesBetween(initial_time, Time(), callback);
358 int SimpleBackendImpl::OpenNextEntry(void** iter,
359 Entry** next_entry,
360 const CompletionCallback& callback) {
361 CompletionCallback get_next_entry =
362 base::Bind(&SimpleBackendImpl::GetNextEntryInIterator, AsWeakPtr(), iter,
363 next_entry, callback);
364 return index_->ExecuteWhenReady(get_next_entry);
367 void SimpleBackendImpl::EndEnumeration(void** iter) {
368 SimpleIndex::HashList* entry_list =
369 static_cast<SimpleIndex::HashList*>(*iter);
370 delete entry_list;
371 *iter = NULL;
374 void SimpleBackendImpl::GetStats(
375 std::vector<std::pair<std::string, std::string> >* stats) {
376 std::pair<std::string, std::string> item;
377 item.first = "Cache type";
378 item.second = "Simple Cache";
379 stats->push_back(item);
382 void SimpleBackendImpl::OnExternalCacheHit(const std::string& key) {
383 index_->UseIfExists(key);
386 void SimpleBackendImpl::InitializeIndex(const CompletionCallback& callback,
387 const DiskStatResult& result) {
388 if (result.net_error == net::OK) {
389 index_->SetMaxSize(result.max_size);
390 index_->Initialize(result.cache_dir_mtime);
392 callback.Run(result.net_error);
395 SimpleBackendImpl::DiskStatResult SimpleBackendImpl::InitCacheStructureOnDisk(
396 const base::FilePath& path,
397 uint64 suggested_max_size) {
398 DiskStatResult result;
399 result.max_size = suggested_max_size;
400 result.net_error = net::OK;
401 if (!FileStructureConsistent(path)) {
402 LOG(ERROR) << "Simple Cache Backend: wrong file structure on disk: "
403 << path.LossyDisplayName();
404 result.net_error = net::ERR_FAILED;
405 } else {
406 bool mtime_result =
407 disk_cache::simple_util::GetMTime(path, &result.cache_dir_mtime);
408 DCHECK(mtime_result);
409 if (!result.max_size) {
410 int64 available = base::SysInfo::AmountOfFreeDiskSpace(path);
411 if (available < 0)
412 result.max_size = kDefaultCacheSize;
413 else
414 // TODO(pasko): Move PreferedCacheSize() to cache_util.h. Also fix the
415 // spelling.
416 result.max_size = disk_cache::PreferedCacheSize(available);
418 DCHECK(result.max_size);
420 return result;
423 scoped_refptr<SimpleEntryImpl> SimpleBackendImpl::CreateOrFindActiveEntry(
424 const std::string& key) {
425 const uint64 entry_hash = simple_util::GetEntryHashKey(key);
427 std::pair<EntryMap::iterator, bool> insert_result =
428 active_entries_.insert(std::make_pair(entry_hash,
429 base::WeakPtr<SimpleEntryImpl>()));
430 EntryMap::iterator& it = insert_result.first;
431 if (insert_result.second)
432 DCHECK(!it->second.get());
433 if (!it->second.get()) {
434 SimpleEntryImpl* entry = new SimpleEntryImpl(
435 path_, entry_hash, entry_operations_mode_, this, net_log_);
436 entry->SetKey(key);
437 it->second = entry->AsWeakPtr();
439 DCHECK(it->second.get());
440 // It's possible, but unlikely, that we have an entry hash collision with a
441 // currently active entry.
442 if (key != it->second->key()) {
443 it->second->Doom();
444 DCHECK_EQ(0U, active_entries_.count(entry_hash));
445 return CreateOrFindActiveEntry(key);
447 return make_scoped_refptr(it->second.get());
450 int SimpleBackendImpl::OpenEntryFromHash(uint64 hash,
451 Entry** entry,
452 const CompletionCallback& callback) {
453 EntryMap::iterator has_active = active_entries_.find(hash);
454 if (has_active != active_entries_.end())
455 return OpenEntry(has_active->second->key(), entry, callback);
457 scoped_refptr<SimpleEntryImpl> simple_entry =
458 new SimpleEntryImpl(path_, hash, entry_operations_mode_, this, net_log_);
459 CompletionCallback backend_callback =
460 base::Bind(&SimpleBackendImpl::OnEntryOpenedFromHash,
461 AsWeakPtr(),
462 hash, entry, simple_entry, callback);
463 return simple_entry->OpenEntry(entry, backend_callback);
466 void SimpleBackendImpl::GetNextEntryInIterator(
467 void** iter,
468 Entry** next_entry,
469 const CompletionCallback& callback,
470 int error_code) {
471 if (error_code != net::OK) {
472 CallCompletionCallback(callback, error_code);
473 return;
475 if (*iter == NULL) {
476 *iter = index()->GetAllHashes().release();
478 SimpleIndex::HashList* entry_list =
479 static_cast<SimpleIndex::HashList*>(*iter);
480 while (entry_list->size() > 0) {
481 uint64 entry_hash = entry_list->back();
482 entry_list->pop_back();
483 if (index()->Has(entry_hash)) {
484 *next_entry = NULL;
485 CompletionCallback continue_iteration = base::Bind(
486 &SimpleBackendImpl::CheckIterationReturnValue,
487 AsWeakPtr(),
488 iter,
489 next_entry,
490 callback);
491 int error_code_open = OpenEntryFromHash(entry_hash,
492 next_entry,
493 continue_iteration);
494 if (error_code_open == net::ERR_IO_PENDING)
495 return;
496 if (error_code_open != net::ERR_FAILED) {
497 CallCompletionCallback(callback, error_code_open);
498 return;
502 CallCompletionCallback(callback, net::ERR_FAILED);
505 void SimpleBackendImpl::OnEntryOpenedFromHash(
506 uint64 hash,
507 Entry** entry,
508 scoped_refptr<SimpleEntryImpl> simple_entry,
509 const CompletionCallback& callback,
510 int error_code) {
511 if (error_code != net::OK) {
512 CallCompletionCallback(callback, error_code);
513 return;
515 DCHECK(*entry);
516 std::pair<EntryMap::iterator, bool> insert_result =
517 active_entries_.insert(std::make_pair(hash,
518 base::WeakPtr<SimpleEntryImpl>()));
519 EntryMap::iterator& it = insert_result.first;
520 const bool did_insert = insert_result.second;
521 if (did_insert) {
522 // There is no active entry corresponding to this hash. The entry created
523 // is put in the map of active entries and returned to the caller.
524 it->second = simple_entry->AsWeakPtr();
525 CallCompletionCallback(callback, error_code);
526 } else {
527 // The entry was made active with the key while the creation from hash
528 // occurred. The entry created from hash needs to be closed, and the one
529 // coming from the key returned to the caller.
530 simple_entry->Close();
531 it->second->OpenEntry(entry, callback);
535 void SimpleBackendImpl::OnEntryOpenedFromKey(
536 const std::string key,
537 Entry** entry,
538 scoped_refptr<SimpleEntryImpl> simple_entry,
539 const CompletionCallback& callback,
540 int error_code) {
541 int final_code = error_code;
542 if (final_code == net::OK) {
543 bool key_matches = key.compare(simple_entry->key()) == 0;
544 if (!key_matches) {
545 // TODO(clamy): Add a unit test to check this code path.
546 DLOG(WARNING) << "Key mismatch on open.";
547 simple_entry->Doom();
548 simple_entry->Close();
549 final_code = net::ERR_FAILED;
550 } else {
551 DCHECK_EQ(simple_entry->entry_hash(), simple_util::GetEntryHashKey(key));
553 UMA_HISTOGRAM_BOOLEAN("SimpleCache.KeyMatchedOnOpen", key_matches);
555 CallCompletionCallback(callback, final_code);
558 void SimpleBackendImpl::CheckIterationReturnValue(
559 void** iter,
560 Entry** entry,
561 const CompletionCallback& callback,
562 int error_code) {
563 if (error_code == net::ERR_FAILED) {
564 OpenNextEntry(iter, entry, callback);
565 return;
567 CallCompletionCallback(callback, error_code);
570 } // namespace disk_cache