simple_backend_impl.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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. 4 5#include "net/disk_cache/simple/simple_backend_impl.h" 6 7#include <algorithm> 8#include <cstdlib> 9 10#include "base/bind.h" 11#include "base/callback.h" 12#include "base/file_util.h" 13#include "base/location.h" 14#include "base/message_loop/message_loop_proxy.h" 15#include "base/metrics/field_trial.h" 16#include "base/metrics/histogram.h" 17#include "base/single_thread_task_runner.h" 18#include "base/sys_info.h" 19#include "base/threading/sequenced_worker_pool.h" 20#include "base/time.h" 21#include "net/base/net_errors.h" 22#include "net/disk_cache/backend_impl.h" 23#include "net/disk_cache/simple/simple_entry_format.h" 24#include "net/disk_cache/simple/simple_entry_impl.h" 25#include "net/disk_cache/simple/simple_index.h" 26#include "net/disk_cache/simple/simple_index_file.h" 27#include "net/disk_cache/simple/simple_synchronous_entry.h" 28#include "net/disk_cache/simple/simple_util.h" 29 30using base::Closure; 31using base::FilePath; 32using base::MessageLoopProxy; 33using base::SequencedWorkerPool; 34using base::SingleThreadTaskRunner; 35using base::Time; 36using file_util::DirectoryExists; 37using file_util::CreateDirectory; 38 39namespace { 40 41// Maximum number of concurrent worker pool threads, which also is the limit 42// on concurrent IO (as we use one thread per IO request). 43const int kDefaultMaxWorkerThreads = 50; 44 45const char kThreadNamePrefix[] = "SimpleCacheWorker"; 46 47// Cache size when all other size heuristics failed. 48const uint64 kDefaultCacheSize = 80 * 1024 * 1024; 49 50// Maximum fraction of the cache that one entry can consume. 51const int kMaxFileRatio = 8; 52 53// A global sequenced worker pool to use for launching all tasks. 54SequencedWorkerPool* g_sequenced_worker_pool = NULL; 55 56void MaybeCreateSequencedWorkerPool() { 57 if (!g_sequenced_worker_pool) { 58 int max_worker_threads = kDefaultMaxWorkerThreads; 59 60 const std::string thread_count_field_trial = 61 base::FieldTrialList::FindFullName("SimpleCacheMaxThreads"); 62 if (!thread_count_field_trial.empty()) { 63 max_worker_threads = 64 std::max(1, std::atoi(thread_count_field_trial.c_str())); 65 } 66 67 g_sequenced_worker_pool = new SequencedWorkerPool(max_worker_threads, 68 kThreadNamePrefix); 69 g_sequenced_worker_pool->AddRef(); // Leak it. 70 } 71} 72 73// Must run on IO Thread. 74void DeleteBackendImpl(disk_cache::Backend** backend, 75 const net::CompletionCallback& callback, 76 int result) { 77 DCHECK(*backend); 78 delete *backend; 79 *backend = NULL; 80 callback.Run(result); 81} 82 83// Detects if the files in the cache directory match the current disk cache 84// backend type and version. If the directory contains no cache, occupies it 85// with the fresh structure. 86// 87// There is a convention among disk cache backends: looking at the magic in the 88// file "index" it should be sufficient to determine if the cache belongs to the 89// currently running backend. The Simple Backend stores its index in the file 90// "the-real-index" (see simple_index.cc) and the file "index" only signifies 91// presence of the implementation's magic and version. There are two reasons for 92// that: 93// 1. Absence of the index is itself not a fatal error in the Simple Backend 94// 2. The Simple Backend has pickled file format for the index making it hacky 95// to have the magic in the right place. 96bool FileStructureConsistent(const base::FilePath& path) { 97 if (!file_util::PathExists(path) && !file_util::CreateDirectory(path)) { 98 LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName(); 99 return false; 100 } 101 const base::FilePath fake_index = path.AppendASCII("index"); 102 base::PlatformFileError error; 103 base::PlatformFile fake_index_file = base::CreatePlatformFile( 104 fake_index, 105 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, 106 NULL, 107 &error); 108 if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) { 109 base::PlatformFile file = base::CreatePlatformFile( 110 fake_index, 111 base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, 112 NULL, &error); 113 disk_cache::SimpleFileHeader file_contents; 114 file_contents.initial_magic_number = disk_cache::kSimpleInitialMagicNumber; 115 file_contents.version = disk_cache::kSimpleVersion; 116 int bytes_written = base::WritePlatformFile( 117 file, 0, reinterpret_cast<char*>(&file_contents), 118 sizeof(file_contents)); 119 if (!base::ClosePlatformFile(file) || 120 bytes_written != sizeof(file_contents)) { 121 LOG(ERROR) << "Failed to write cache structure file: " 122 << path.LossyDisplayName(); 123 return false; 124 } 125 return true; 126 } else if (error != base::PLATFORM_FILE_OK) { 127 LOG(ERROR) << "Could not open cache structure file: " 128 << path.LossyDisplayName(); 129 return false; 130 } else { 131 disk_cache::SimpleFileHeader file_header; 132 int bytes_read = base::ReadPlatformFile( 133 fake_index_file, 0, reinterpret_cast<char*>(&file_header), 134 sizeof(file_header)); 135 if (!base::ClosePlatformFile(fake_index_file) || 136 bytes_read != sizeof(file_header) || 137 file_header.initial_magic_number != 138 disk_cache::kSimpleInitialMagicNumber || 139 file_header.version != disk_cache::kSimpleVersion) { 140 LOG(ERROR) << "File structure does not match the disk cache backend."; 141 return false; 142 } 143 return true; 144 } 145} 146 147void CallCompletionCallback(const net::CompletionCallback& callback, 148 scoped_ptr<int> result) { 149 DCHECK(!callback.is_null()); 150 DCHECK(result); 151 callback.Run(*result); 152} 153 154void RecordIndexLoad(base::TimeTicks constructed_since, int result) { 155 const base::TimeDelta creation_to_index = base::TimeTicks::Now() - 156 constructed_since; 157 if (result == net::OK) 158 UMA_HISTOGRAM_TIMES("SimpleCache.CreationToIndex", creation_to_index); 159 else 160 UMA_HISTOGRAM_TIMES("SimpleCache.CreationToIndexFail", creation_to_index); 161} 162 163} // namespace 164 165namespace disk_cache { 166 167SimpleBackendImpl::SimpleBackendImpl(const FilePath& path, 168 int max_bytes, 169 net::CacheType type, 170 base::SingleThreadTaskRunner* cache_thread, 171 net::NetLog* net_log) 172 : path_(path), 173 cache_thread_(cache_thread), 174 orig_max_size_(max_bytes) { 175} 176 177SimpleBackendImpl::~SimpleBackendImpl() { 178 index_->WriteToDisk(); 179} 180 181int SimpleBackendImpl::Init(const CompletionCallback& completion_callback) { 182 MaybeCreateSequencedWorkerPool(); 183 184 worker_pool_ = g_sequenced_worker_pool->GetTaskRunnerWithShutdownBehavior( 185 SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); 186 187 index_.reset(new SimpleIndex(MessageLoopProxy::current(), path_, 188 make_scoped_ptr(new SimpleIndexFile( 189 cache_thread_.get(), worker_pool_.get(), 190 path_)))); 191 index_->ExecuteWhenReady(base::Bind(&RecordIndexLoad, 192 base::TimeTicks::Now())); 193 194 InitializeIndexCallback initialize_index_callback = 195 base::Bind(&SimpleBackendImpl::InitializeIndex, 196 base::Unretained(this), 197 completion_callback); 198 cache_thread_->PostTask( 199 FROM_HERE, 200 base::Bind(&SimpleBackendImpl::ProvideDirectorySuggestBetterCacheSize, 201 MessageLoopProxy::current(), // io_thread 202 path_, 203 initialize_index_callback, 204 orig_max_size_)); 205 return net::ERR_IO_PENDING; 206} 207 208bool SimpleBackendImpl::SetMaxSize(int max_bytes) { 209 orig_max_size_ = max_bytes; 210 return index_->SetMaxSize(max_bytes); 211} 212 213int SimpleBackendImpl::GetMaxFileSize() const { 214 return index_->max_size() / kMaxFileRatio; 215} 216 217void SimpleBackendImpl::OnDeactivated(const SimpleEntryImpl* entry) { 218 DCHECK_LT(0U, active_entries_.count(entry->entry_hash())); 219 active_entries_.erase(entry->entry_hash()); 220} 221 222net::CacheType SimpleBackendImpl::GetCacheType() const { 223 return net::DISK_CACHE; 224} 225 226int32 SimpleBackendImpl::GetEntryCount() const { 227 // TODO(pasko): Use directory file count when index is not ready. 228 return index_->GetEntryCount(); 229} 230 231int SimpleBackendImpl::OpenEntry(const std::string& key, 232 Entry** entry, 233 const CompletionCallback& callback) { 234 scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key); 235 return simple_entry->OpenEntry(entry, callback); 236} 237 238int SimpleBackendImpl::CreateEntry(const std::string& key, 239 Entry** entry, 240 const CompletionCallback& callback) { 241 scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key); 242 return simple_entry->CreateEntry(entry, callback); 243} 244 245int SimpleBackendImpl::DoomEntry(const std::string& key, 246 const net::CompletionCallback& callback) { 247 scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveEntry(key); 248 return simple_entry->DoomEntry(callback); 249} 250 251int SimpleBackendImpl::DoomAllEntries(const CompletionCallback& callback) { 252 return DoomEntriesBetween(Time(), Time(), callback); 253} 254 255void SimpleBackendImpl::IndexReadyForDoom(Time initial_time, 256 Time end_time, 257 const CompletionCallback& callback, 258 int result) { 259 if (result != net::OK) { 260 callback.Run(result); 261 return; 262 } 263 scoped_ptr<std::vector<uint64> > removed_key_hashes( 264 index_->RemoveEntriesBetween(initial_time, end_time).release()); 265 266 // If any of the entries we are dooming are currently open, we need to remove 267 // them from |active_entries_|, so that attempts to create new entries will 268 // succeed and attempts to open them will fail. 269 for (int i = removed_key_hashes->size() - 1; i >= 0; --i) { 270 const uint64 entry_hash = (*removed_key_hashes)[i]; 271 EntryMap::iterator it = active_entries_.find(entry_hash); 272 if (it == active_entries_.end()) 273 continue; 274 SimpleEntryImpl* entry = it->second.get(); 275 entry->Doom(); 276 277 (*removed_key_hashes)[i] = removed_key_hashes->back(); 278 removed_key_hashes->resize(removed_key_hashes->size() - 1); 279 } 280 281 scoped_ptr<int> new_result(new int()); 282 Closure task = base::Bind(&SimpleSynchronousEntry::DoomEntrySet, 283 base::Passed(&removed_key_hashes), path_, 284 new_result.get()); 285 Closure reply = base::Bind(&CallCompletionCallback, 286 callback, base::Passed(&new_result)); 287 worker_pool_->PostTaskAndReply(FROM_HERE, task, reply); 288} 289 290int SimpleBackendImpl::DoomEntriesBetween( 291 const Time initial_time, 292 const Time end_time, 293 const CompletionCallback& callback) { 294 return index_->ExecuteWhenReady( 295 base::Bind(&SimpleBackendImpl::IndexReadyForDoom, AsWeakPtr(), 296 initial_time, end_time, callback)); 297} 298 299int SimpleBackendImpl::DoomEntriesSince( 300 const Time initial_time, 301 const CompletionCallback& callback) { 302 return DoomEntriesBetween(initial_time, Time(), callback); 303} 304 305int SimpleBackendImpl::OpenNextEntry(void** iter, 306 Entry** next_entry, 307 const CompletionCallback& callback) { 308 NOTIMPLEMENTED(); 309 return net::ERR_FAILED; 310} 311 312void SimpleBackendImpl::EndEnumeration(void** iter) { 313 NOTIMPLEMENTED(); 314} 315 316void SimpleBackendImpl::GetStats( 317 std::vector<std::pair<std::string, std::string> >* stats) { 318 std::pair<std::string, std::string> item; 319 item.first = "Cache type"; 320 item.second = "Simple Cache"; 321 stats->push_back(item); 322} 323 324void SimpleBackendImpl::OnExternalCacheHit(const std::string& key) { 325 index_->UseIfExists(key); 326} 327 328void SimpleBackendImpl::InitializeIndex( 329 const CompletionCallback& callback, uint64 suggested_max_size, int result) { 330 if (result == net::OK) { 331 index_->SetMaxSize(suggested_max_size); 332 index_->Initialize(); 333 } 334 callback.Run(result); 335} 336 337// static 338void SimpleBackendImpl::ProvideDirectorySuggestBetterCacheSize( 339 SingleThreadTaskRunner* io_thread, 340 const base::FilePath& path, 341 const InitializeIndexCallback& initialize_index_callback, 342 uint64 suggested_max_size) { 343 int rv = net::OK; 344 uint64 max_size = suggested_max_size; 345 if (!FileStructureConsistent(path)) { 346 LOG(ERROR) << "Simple Cache Backend: wrong file structure on disk: " 347 << path.LossyDisplayName(); 348 rv = net::ERR_FAILED; 349 } else { 350 if (!max_size) { 351 int64 available = base::SysInfo::AmountOfFreeDiskSpace(path); 352 if (available < 0) 353 max_size = kDefaultCacheSize; 354 else 355 // TODO(pasko): Move PreferedCacheSize() to cache_util.h. Also fix the 356 // spelling. 357 max_size = PreferedCacheSize(available); 358 } 359 DCHECK(max_size); 360 } 361 io_thread->PostTask(FROM_HERE, 362 base::Bind(initialize_index_callback, max_size, rv)); 363} 364 365scoped_refptr<SimpleEntryImpl> SimpleBackendImpl::CreateOrFindActiveEntry( 366 const std::string& key) { 367 const uint64 entry_hash = simple_util::GetEntryHashKey(key); 368 369 std::pair<EntryMap::iterator, bool> insert_result = 370 active_entries_.insert(std::make_pair(entry_hash, 371 base::WeakPtr<SimpleEntryImpl>())); 372 EntryMap::iterator& it = insert_result.first; 373 if (insert_result.second) 374 DCHECK(!it->second.get()); 375 if (!it->second.get()) { 376 SimpleEntryImpl* entry = new SimpleEntryImpl(this, path_, key, entry_hash); 377 it->second = entry->AsWeakPtr(); 378 } 379 DCHECK(it->second.get()); 380 // It's possible, but unlikely, that we have an entry hash collision with a 381 // currently active entry. 382 if (key != it->second->key()) { 383 it->second->Doom(); 384 DCHECK_EQ(0U, active_entries_.count(entry_hash)); 385 return CreateOrFindActiveEntry(key); 386 } 387 return make_scoped_refptr(it->second.get()); 388} 389 390} // namespace disk_cache 391