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