mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2026-06-04 21:36:29 +03:00
Import chromium-64.0.3282.140
This commit is contained in:
4
net/disk_cache/OWNERS
Normal file
4
net/disk_cache/OWNERS
Normal file
@@ -0,0 +1,4 @@
|
||||
morlovich@chromium.org
|
||||
jkarlin@chromium.org
|
||||
|
||||
# COMPONENT: Internals>Network>Cache
|
||||
95
net/disk_cache/backend_cleanup_tracker.cc
Normal file
95
net/disk_cache/backend_cleanup_tracker.cc
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Internal helper used to sequence cleanup and reuse of cache directories
|
||||
// among different objects.
|
||||
|
||||
#include "net/disk_cache/backend_cleanup_tracker.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/sequenced_task_runner.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/task_scheduler/post_task.h"
|
||||
#include "base/threading/sequenced_task_runner_handle.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace {
|
||||
|
||||
using TrackerMap = std::unordered_map<base::FilePath, BackendCleanupTracker*>;
|
||||
struct AllBackendCleanupTrackers {
|
||||
TrackerMap map;
|
||||
|
||||
// Since clients can potentially call CreateCacheBackend from multiple
|
||||
// threads, we need to lock the map keeping track of cleanup trackers
|
||||
// for these backends. Our overall strategy is to have TryCreate
|
||||
// acts as an arbitrator --- whatever thread grabs one, gets to operate
|
||||
// on the tracker freely until it gets destroyed.
|
||||
base::Lock lock;
|
||||
};
|
||||
|
||||
static base::LazyInstance<AllBackendCleanupTrackers>::Leaky g_all_trackers;
|
||||
|
||||
} // namespace.
|
||||
|
||||
// static
|
||||
scoped_refptr<BackendCleanupTracker> BackendCleanupTracker::TryCreate(
|
||||
const base::FilePath& path,
|
||||
base::OnceClosure retry_closure) {
|
||||
AllBackendCleanupTrackers* all_trackers = g_all_trackers.Pointer();
|
||||
base::AutoLock lock(all_trackers->lock);
|
||||
|
||||
std::pair<TrackerMap::iterator, bool> insert_result =
|
||||
all_trackers->map.insert(
|
||||
std::pair<base::FilePath, BackendCleanupTracker*>(path, nullptr));
|
||||
if (insert_result.second) {
|
||||
insert_result.first->second = new BackendCleanupTracker(path);
|
||||
return insert_result.first->second;
|
||||
} else {
|
||||
insert_result.first->second->AddPostCleanupCallbackImpl(
|
||||
std::move(retry_closure));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void BackendCleanupTracker::AddPostCleanupCallback(base::OnceClosure cb) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(seq_checker_);
|
||||
// Despite the sequencing requirement we need to grab the table lock since
|
||||
// this may otherwise race against TryMakeContext.
|
||||
base::AutoLock lock(g_all_trackers.Get().lock);
|
||||
AddPostCleanupCallbackImpl(std::move(cb));
|
||||
}
|
||||
|
||||
void BackendCleanupTracker::AddPostCleanupCallbackImpl(base::OnceClosure cb) {
|
||||
post_cleanup_cbs_.push_back(
|
||||
std::make_pair(base::SequencedTaskRunnerHandle::Get(), std::move(cb)));
|
||||
}
|
||||
|
||||
BackendCleanupTracker::BackendCleanupTracker(const base::FilePath& path)
|
||||
: path_(path) {}
|
||||
|
||||
BackendCleanupTracker::~BackendCleanupTracker() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(seq_checker_);
|
||||
|
||||
{
|
||||
AllBackendCleanupTrackers* all_trackers = g_all_trackers.Pointer();
|
||||
base::AutoLock lock(all_trackers->lock);
|
||||
int rv = all_trackers->map.erase(path_);
|
||||
DCHECK_EQ(1, rv);
|
||||
}
|
||||
|
||||
while (!post_cleanup_cbs_.empty()) {
|
||||
post_cleanup_cbs_.back().first->PostTask(
|
||||
FROM_HERE, std::move(post_cleanup_cbs_.back().second));
|
||||
post_cleanup_cbs_.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
69
net/disk_cache/backend_cleanup_tracker.h
Normal file
69
net/disk_cache/backend_cleanup_tracker.h
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BACKEND_CLEANUP_TRACKER_H_
|
||||
#define NET_DISK_CACHE_BACKEND_CLEANUP_TRACKER_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/sequence_checker.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace base {
|
||||
class SequencedTaskRunner;
|
||||
} // namespace base
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Internal helper used to sequence cleanup and reuse of cache directories.
|
||||
// One of these is created before each backend, and is kept alive till both
|
||||
// the backend is destroyed and all of its work is done by its refcount,
|
||||
// which keeps track of outstanding work. That refcount is expected to only be
|
||||
// updated from the I/O thread or its equivalent.
|
||||
class NET_EXPORT_PRIVATE BackendCleanupTracker
|
||||
: public base::RefCounted<BackendCleanupTracker> {
|
||||
public:
|
||||
// Either returns a fresh cleanup tracker for |path| if none exists, or
|
||||
// will eventually post |retry_closure| to the calling thread,
|
||||
// and return null.
|
||||
static scoped_refptr<BackendCleanupTracker> TryCreate(
|
||||
const base::FilePath& path,
|
||||
base::OnceClosure retry_closure);
|
||||
|
||||
// Register a callback to be posted after all the work of associated
|
||||
// context is complete (which will result in destruction of this context).
|
||||
// Should only be called by owner, on its I/O-thread-like execution context,
|
||||
// and will in turn eventually post |cb| there.
|
||||
void AddPostCleanupCallback(base::OnceClosure cb);
|
||||
|
||||
private:
|
||||
friend class base::RefCounted<BackendCleanupTracker>;
|
||||
explicit BackendCleanupTracker(const base::FilePath& path);
|
||||
~BackendCleanupTracker();
|
||||
|
||||
void AddPostCleanupCallbackImpl(base::OnceClosure cb);
|
||||
|
||||
base::FilePath path_;
|
||||
|
||||
// Since it's possible that a different thread may want to create a
|
||||
// cache for a reused path, we keep track of runners contexts of
|
||||
// post-cleanup callbacks.
|
||||
std::vector<
|
||||
std::pair<scoped_refptr<base::SequencedTaskRunner>, base::OnceClosure>>
|
||||
post_cleanup_cbs_;
|
||||
|
||||
// We expect only TryMakeContext to be multithreaded, everything
|
||||
// else should be sequenced.
|
||||
SEQUENCE_CHECKER(seq_checker_);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BackendCleanupTracker);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BACKEND_CLEANUP_TRACKER_H_
|
||||
62
net/disk_cache/blockfile/addr.cc
Normal file
62
net/disk_cache/blockfile/addr.cc
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/addr.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
int Addr::start_block() const {
|
||||
DCHECK(is_block_file());
|
||||
return value_ & kStartBlockMask;
|
||||
}
|
||||
|
||||
int Addr::num_blocks() const {
|
||||
DCHECK(is_block_file() || !value_);
|
||||
return ((value_ & kNumBlocksMask) >> kNumBlocksOffset) + 1;
|
||||
}
|
||||
|
||||
bool Addr::SetFileNumber(int file_number) {
|
||||
DCHECK(is_separate_file());
|
||||
if (file_number & ~kFileNameMask)
|
||||
return false;
|
||||
value_ = kInitializedMask | file_number;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Addr::SanityCheck() const {
|
||||
if (!is_initialized())
|
||||
return !value_;
|
||||
|
||||
if (file_type() > BLOCK_4K)
|
||||
return false;
|
||||
|
||||
if (is_separate_file())
|
||||
return true;
|
||||
|
||||
return !reserved_bits();
|
||||
}
|
||||
|
||||
bool Addr::SanityCheckForEntry() const {
|
||||
if (!SanityCheck() || !is_initialized())
|
||||
return false;
|
||||
|
||||
if (is_separate_file() || file_type() != BLOCK_256)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Addr::SanityCheckForRankings() const {
|
||||
if (!SanityCheck() || !is_initialized())
|
||||
return false;
|
||||
|
||||
if (is_separate_file() || file_type() != RANKINGS || num_blocks() != 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
189
net/disk_cache/blockfile/addr.h
Normal file
189
net/disk_cache/blockfile/addr.h
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This is an internal class that handles the address of a cache record.
|
||||
// See net/disk_cache/disk_cache.h for the public interface of the cache.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_ADDR_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_ADDR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/disk_format_base.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
enum FileType {
|
||||
EXTERNAL = 0,
|
||||
RANKINGS = 1,
|
||||
BLOCK_256 = 2,
|
||||
BLOCK_1K = 3,
|
||||
BLOCK_4K = 4,
|
||||
BLOCK_FILES = 5,
|
||||
BLOCK_ENTRIES = 6,
|
||||
BLOCK_EVICTED = 7
|
||||
};
|
||||
|
||||
const int kMaxBlockSize = 4096 * 4;
|
||||
const int16_t kMaxBlockFile = 255;
|
||||
const int kMaxNumBlocks = 4;
|
||||
const int16_t kFirstAdditionalBlockFile = 4;
|
||||
|
||||
// Defines a storage address for a cache record
|
||||
//
|
||||
// Header:
|
||||
// 1000 0000 0000 0000 0000 0000 0000 0000 : initialized bit
|
||||
// 0111 0000 0000 0000 0000 0000 0000 0000 : file type
|
||||
//
|
||||
// File type values:
|
||||
// 0 = separate file on disk
|
||||
// 1 = rankings block file
|
||||
// 2 = 256 byte block file
|
||||
// 3 = 1k byte block file
|
||||
// 4 = 4k byte block file
|
||||
// 5 = external files block file
|
||||
// 6 = active entries block file
|
||||
// 7 = evicted entries block file
|
||||
//
|
||||
// If separate file:
|
||||
// 0000 1111 1111 1111 1111 1111 1111 1111 : file# 0 - 268,435,456 (2^28)
|
||||
//
|
||||
// If block file:
|
||||
// 0000 1100 0000 0000 0000 0000 0000 0000 : reserved bits
|
||||
// 0000 0011 0000 0000 0000 0000 0000 0000 : number of contiguous blocks 1-4
|
||||
// 0000 0000 1111 1111 0000 0000 0000 0000 : file selector 0 - 255
|
||||
// 0000 0000 0000 0000 1111 1111 1111 1111 : block# 0 - 65,535 (2^16)
|
||||
//
|
||||
// Note that an Addr can be used to "point" to a variety of different objects,
|
||||
// from a given type of entry to random blobs of data. Conceptually, an Addr is
|
||||
// just a number that someone can inspect to find out how to locate the desired
|
||||
// record. Most users will not care about the specific bits inside Addr, for
|
||||
// example, what parts of it point to a file number; only the code that has to
|
||||
// select a specific file would care about those specific bits.
|
||||
//
|
||||
// From a general point of view, an Addr has a total capacity of 2^24 entities,
|
||||
// in that it has 24 bits that can identify individual records. Note that the
|
||||
// address space is bigger for independent files (2^28), but that would not be
|
||||
// the general case.
|
||||
class NET_EXPORT_PRIVATE Addr {
|
||||
public:
|
||||
Addr() : value_(0) {}
|
||||
explicit Addr(CacheAddr address) : value_(address) {}
|
||||
Addr(FileType file_type, int max_blocks, int block_file, int index) {
|
||||
value_ = ((file_type << kFileTypeOffset) & kFileTypeMask) |
|
||||
(((max_blocks - 1) << kNumBlocksOffset) & kNumBlocksMask) |
|
||||
((block_file << kFileSelectorOffset) & kFileSelectorMask) |
|
||||
(index & kStartBlockMask) | kInitializedMask;
|
||||
}
|
||||
|
||||
CacheAddr value() const { return value_; }
|
||||
void set_value(CacheAddr address) {
|
||||
value_ = address;
|
||||
}
|
||||
|
||||
bool is_initialized() const {
|
||||
return (value_ & kInitializedMask) != 0;
|
||||
}
|
||||
|
||||
bool is_separate_file() const {
|
||||
return (value_ & kFileTypeMask) == 0;
|
||||
}
|
||||
|
||||
bool is_block_file() const {
|
||||
return !is_separate_file();
|
||||
}
|
||||
|
||||
FileType file_type() const {
|
||||
return static_cast<FileType>((value_ & kFileTypeMask) >> kFileTypeOffset);
|
||||
}
|
||||
|
||||
int FileNumber() const {
|
||||
if (is_separate_file())
|
||||
return value_ & kFileNameMask;
|
||||
else
|
||||
return ((value_ & kFileSelectorMask) >> kFileSelectorOffset);
|
||||
}
|
||||
|
||||
int start_block() const;
|
||||
int num_blocks() const;
|
||||
bool SetFileNumber(int file_number);
|
||||
int BlockSize() const {
|
||||
return BlockSizeForFileType(file_type());
|
||||
}
|
||||
|
||||
bool operator==(Addr other) const {
|
||||
return value_ == other.value_;
|
||||
}
|
||||
|
||||
bool operator!=(Addr other) const {
|
||||
return value_ != other.value_;
|
||||
}
|
||||
|
||||
static int BlockSizeForFileType(FileType file_type) {
|
||||
switch (file_type) {
|
||||
case RANKINGS:
|
||||
return 36;
|
||||
case BLOCK_256:
|
||||
return 256;
|
||||
case BLOCK_1K:
|
||||
return 1024;
|
||||
case BLOCK_4K:
|
||||
return 4096;
|
||||
case BLOCK_FILES:
|
||||
return 8;
|
||||
case BLOCK_ENTRIES:
|
||||
return 104;
|
||||
case BLOCK_EVICTED:
|
||||
return 48;
|
||||
case EXTERNAL:
|
||||
NOTREACHED();
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static FileType RequiredFileType(int size) {
|
||||
if (size < 1024)
|
||||
return BLOCK_256;
|
||||
else if (size < 4096)
|
||||
return BLOCK_1K;
|
||||
else if (size <= 4096 * 4)
|
||||
return BLOCK_4K;
|
||||
else
|
||||
return EXTERNAL;
|
||||
}
|
||||
|
||||
static int RequiredBlocks(int size, FileType file_type) {
|
||||
int block_size = BlockSizeForFileType(file_type);
|
||||
return (size + block_size - 1) / block_size;
|
||||
}
|
||||
|
||||
// Returns true if this address looks like a valid one.
|
||||
bool SanityCheck() const;
|
||||
bool SanityCheckForEntry() const;
|
||||
bool SanityCheckForRankings() const;
|
||||
|
||||
private:
|
||||
uint32_t reserved_bits() const { return value_ & kReservedBitsMask; }
|
||||
|
||||
static const uint32_t kInitializedMask = 0x80000000;
|
||||
static const uint32_t kFileTypeMask = 0x70000000;
|
||||
static const uint32_t kFileTypeOffset = 28;
|
||||
static const uint32_t kReservedBitsMask = 0x0c000000;
|
||||
static const uint32_t kNumBlocksMask = 0x03000000;
|
||||
static const uint32_t kNumBlocksOffset = 24;
|
||||
static const uint32_t kFileSelectorMask = 0x00ff0000;
|
||||
static const uint32_t kFileSelectorOffset = 16;
|
||||
static const uint32_t kStartBlockMask = 0x0000FFFF;
|
||||
static const uint32_t kFileNameMask = 0x0FFFFFFF;
|
||||
|
||||
CacheAddr value_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_ADDR_H_
|
||||
2141
net/disk_cache/blockfile/backend_impl.cc
Normal file
2141
net/disk_cache/blockfile/backend_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
434
net/disk_cache/blockfile/backend_impl.h
Normal file
434
net/disk_cache/blockfile/backend_impl.h
Normal file
@@ -0,0 +1,434 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface of the cache.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_BACKEND_IMPL_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_BACKEND_IMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/block_files.h"
|
||||
#include "net/disk_cache/blockfile/eviction.h"
|
||||
#include "net/disk_cache/blockfile/in_flight_backend_io.h"
|
||||
#include "net/disk_cache/blockfile/rankings.h"
|
||||
#include "net/disk_cache/blockfile/stats.h"
|
||||
#include "net/disk_cache/blockfile/stress_support.h"
|
||||
#include "net/disk_cache/blockfile/trace.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
|
||||
namespace base {
|
||||
class SingleThreadTaskRunner;
|
||||
} // namespace base
|
||||
|
||||
namespace net {
|
||||
class NetLog;
|
||||
} // namespace net
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class BackendCleanupTracker;
|
||||
struct Index;
|
||||
|
||||
enum BackendFlags {
|
||||
kNone = 0,
|
||||
kMask = 1, // A mask (for the index table) was specified.
|
||||
kMaxSize = 1 << 1, // A maximum size was provided.
|
||||
kUnitTestMode = 1 << 2, // We are modifying the behavior for testing.
|
||||
kUpgradeMode = 1 << 3, // This is the upgrade tool (dump).
|
||||
kNewEviction = 1 << 4, // Use of new eviction was specified.
|
||||
kNoRandom = 1 << 5, // Don't add randomness to the behavior.
|
||||
kNoLoadProtection = 1 << 6, // Don't act conservatively under load.
|
||||
kNoBuffering = 1 << 7 // Disable extended IO buffering.
|
||||
};
|
||||
|
||||
// This class implements the Backend interface. An object of this
|
||||
// class handles the operations of the cache for a particular profile.
|
||||
class NET_EXPORT_PRIVATE BackendImpl : public Backend {
|
||||
friend class Eviction;
|
||||
public:
|
||||
BackendImpl(const base::FilePath& path,
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker,
|
||||
const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread,
|
||||
net::NetLog* net_log);
|
||||
|
||||
// mask can be used to limit the usable size of the hash table, for testing.
|
||||
BackendImpl(const base::FilePath& path,
|
||||
uint32_t mask,
|
||||
const scoped_refptr<base::SingleThreadTaskRunner>& cache_thread,
|
||||
net::NetLog* net_log);
|
||||
|
||||
~BackendImpl() override;
|
||||
|
||||
// Performs general initialization for this current instance of the cache.
|
||||
int Init(const CompletionCallback& callback);
|
||||
|
||||
// Performs the actual initialization and final cleanup on destruction.
|
||||
int SyncInit();
|
||||
void CleanupCache();
|
||||
|
||||
// Synchronous implementation of the asynchronous interface.
|
||||
int SyncOpenEntry(const std::string& key, scoped_refptr<EntryImpl>* entry);
|
||||
int SyncCreateEntry(const std::string& key, scoped_refptr<EntryImpl>* entry);
|
||||
int SyncDoomEntry(const std::string& key);
|
||||
int SyncDoomAllEntries();
|
||||
int SyncDoomEntriesBetween(base::Time initial_time,
|
||||
base::Time end_time);
|
||||
int SyncCalculateSizeOfAllEntries();
|
||||
int SyncDoomEntriesSince(base::Time initial_time);
|
||||
int SyncOpenNextEntry(Rankings::Iterator* iterator,
|
||||
scoped_refptr<EntryImpl>* next_entry);
|
||||
void SyncEndEnumeration(std::unique_ptr<Rankings::Iterator> iterator);
|
||||
void SyncOnExternalCacheHit(const std::string& key);
|
||||
|
||||
// Open or create an entry for the given |key| or |iter|.
|
||||
scoped_refptr<EntryImpl> OpenEntryImpl(const std::string& key);
|
||||
scoped_refptr<EntryImpl> CreateEntryImpl(const std::string& key);
|
||||
scoped_refptr<EntryImpl> OpenNextEntryImpl(Rankings::Iterator* iter);
|
||||
|
||||
// Sets the maximum size for the total amount of data stored by this instance.
|
||||
bool SetMaxSize(int max_bytes);
|
||||
|
||||
// Sets the cache type for this backend.
|
||||
void SetType(net::CacheType type);
|
||||
|
||||
// Returns the full name for an external storage file.
|
||||
base::FilePath GetFileName(Addr address) const;
|
||||
|
||||
// Returns the actual file used to store a given (non-external) address.
|
||||
MappedFile* File(Addr address);
|
||||
|
||||
// Returns a weak pointer to the background queue.
|
||||
base::WeakPtr<InFlightBackendIO> GetBackgroundQueue();
|
||||
|
||||
// Creates an external storage file.
|
||||
bool CreateExternalFile(Addr* address);
|
||||
|
||||
// Creates a new storage block of size block_count.
|
||||
bool CreateBlock(FileType block_type, int block_count,
|
||||
Addr* block_address);
|
||||
|
||||
// Deletes a given storage block. deep set to true can be used to zero-fill
|
||||
// the related storage in addition of releasing the related block.
|
||||
void DeleteBlock(Addr block_address, bool deep);
|
||||
|
||||
// Retrieves a pointer to the LRU-related data.
|
||||
LruData* GetLruData();
|
||||
|
||||
// Updates the ranking information for an entry.
|
||||
void UpdateRank(EntryImpl* entry, bool modified);
|
||||
|
||||
// A node was recovered from a crash, it may not be on the index, so this
|
||||
// method checks it and takes the appropriate action.
|
||||
void RecoveredEntry(CacheRankingsBlock* rankings);
|
||||
|
||||
// Permanently deletes an entry, but still keeps track of it.
|
||||
void InternalDoomEntry(EntryImpl* entry);
|
||||
|
||||
#if defined(NET_BUILD_STRESS_CACHE)
|
||||
// Returns the address of the entry linked to the entry at a given |address|.
|
||||
CacheAddr GetNextAddr(Addr address);
|
||||
|
||||
// Verifies that |entry| is not currently reachable through the index.
|
||||
void NotLinked(EntryImpl* entry);
|
||||
#endif
|
||||
|
||||
// Removes all references to this entry.
|
||||
void RemoveEntry(EntryImpl* entry);
|
||||
|
||||
// This method must be called when an entry is released for the last time, so
|
||||
// the entry should not be used anymore. |address| is the cache address of the
|
||||
// entry.
|
||||
void OnEntryDestroyBegin(Addr address);
|
||||
|
||||
// This method must be called after all resources for an entry have been
|
||||
// released.
|
||||
void OnEntryDestroyEnd();
|
||||
|
||||
// If the data stored by the provided |rankings| points to an open entry,
|
||||
// returns a pointer to that entry, otherwise returns NULL. Note that this
|
||||
// method does NOT increase the ref counter for the entry.
|
||||
EntryImpl* GetOpenEntry(CacheRankingsBlock* rankings) const;
|
||||
|
||||
// Returns the id being used on this run of the cache.
|
||||
int32_t GetCurrentEntryId() const;
|
||||
|
||||
// Returns the maximum size for a file to reside on the cache.
|
||||
int MaxFileSize() const;
|
||||
|
||||
// A user data block is being created, extended or truncated.
|
||||
void ModifyStorageSize(int32_t old_size, int32_t new_size);
|
||||
|
||||
// Logs requests that are denied due to being too big.
|
||||
void TooMuchStorageRequested(int32_t size);
|
||||
|
||||
// Returns true if a temporary buffer is allowed to be extended.
|
||||
bool IsAllocAllowed(int current_size, int new_size);
|
||||
|
||||
// Tracks the release of |size| bytes by an entry buffer.
|
||||
void BufferDeleted(int size);
|
||||
|
||||
// Only intended for testing the two previous methods.
|
||||
int GetTotalBuffersSize() const {
|
||||
return buffer_bytes_;
|
||||
}
|
||||
|
||||
// Returns true if this instance seems to be under heavy load.
|
||||
bool IsLoaded() const;
|
||||
|
||||
// Returns the full histogram name, for the given base |name| and experiment,
|
||||
// and the current cache type. The name will be "DiskCache.t.name_e" where n
|
||||
// is the cache type and e the provided |experiment|.
|
||||
std::string HistogramName(const char* name, int experiment) const;
|
||||
|
||||
net::CacheType cache_type() const {
|
||||
return cache_type_;
|
||||
}
|
||||
|
||||
bool read_only() const {
|
||||
return read_only_;
|
||||
}
|
||||
|
||||
// Returns a weak pointer to this object.
|
||||
base::WeakPtr<BackendImpl> GetWeakPtr();
|
||||
|
||||
// Returns true if we should send histograms for this user again. The caller
|
||||
// must call this function only once per run (because it returns always the
|
||||
// same thing on a given run).
|
||||
bool ShouldReportAgain();
|
||||
|
||||
// Reports some data when we filled up the cache.
|
||||
void FirstEviction();
|
||||
|
||||
// Reports a critical error (and disables the cache).
|
||||
void CriticalError(int error);
|
||||
|
||||
// Reports an uncommon, recoverable error.
|
||||
void ReportError(int error);
|
||||
|
||||
// Called when an interesting event should be logged (counted).
|
||||
void OnEvent(Stats::Counters an_event);
|
||||
|
||||
// Keeps track of payload access (doesn't include metadata).
|
||||
void OnRead(int bytes);
|
||||
void OnWrite(int bytes);
|
||||
|
||||
// Timer callback to calculate usage statistics.
|
||||
void OnStatsTimer();
|
||||
|
||||
// Handles the pending asynchronous IO count.
|
||||
void IncrementIoCount();
|
||||
void DecrementIoCount();
|
||||
|
||||
// Sets internal parameters to enable unit testing mode.
|
||||
void SetUnitTestMode();
|
||||
|
||||
// Sets internal parameters to enable upgrade mode (for internal tools).
|
||||
void SetUpgradeMode();
|
||||
|
||||
// Sets the eviction algorithm to version 2.
|
||||
void SetNewEviction();
|
||||
|
||||
// Sets an explicit set of BackendFlags.
|
||||
void SetFlags(uint32_t flags);
|
||||
|
||||
// Clears the counter of references to test handling of corruptions.
|
||||
void ClearRefCountForTest();
|
||||
|
||||
// Sends a dummy operation through the operation queue, for unit tests.
|
||||
int FlushQueueForTest(const CompletionCallback& callback);
|
||||
|
||||
// Runs the provided task on the cache thread. The task will be automatically
|
||||
// deleted after it runs.
|
||||
int RunTaskForTest(const base::Closure& task,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
// Trims an entry (all if |empty| is true) from the list of deleted
|
||||
// entries. This method should be called directly on the cache thread.
|
||||
void TrimForTest(bool empty);
|
||||
|
||||
// Trims an entry (all if |empty| is true) from the list of deleted
|
||||
// entries. This method should be called directly on the cache thread.
|
||||
void TrimDeletedListForTest(bool empty);
|
||||
|
||||
// Only intended for testing
|
||||
base::RepeatingTimer* GetTimerForTest();
|
||||
|
||||
// Performs a simple self-check, and returns the number of dirty items
|
||||
// or an error code (negative value).
|
||||
int SelfCheck();
|
||||
|
||||
// Ensures the index is flushed to disk (a no-op on platforms with mmap).
|
||||
void FlushIndex();
|
||||
|
||||
// Ensures that the private cache thread completes work.
|
||||
static void FlushForTesting();
|
||||
|
||||
// Backend implementation.
|
||||
net::CacheType GetCacheType() const override;
|
||||
int32_t GetEntryCount() const override;
|
||||
int OpenEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) override;
|
||||
int CreateEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomEntry(const std::string& key,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomAllEntries(const CompletionCallback& callback) override;
|
||||
int DoomEntriesBetween(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomEntriesSince(base::Time initial_time,
|
||||
const CompletionCallback& callback) override;
|
||||
int CalculateSizeOfAllEntries(const CompletionCallback& callback) override;
|
||||
// NOTE: The blockfile Backend::Iterator::OpenNextEntry method does not modify
|
||||
// the last_used field of the entry, and therefore it does not impact the
|
||||
// eviction ranking of the entry. However, an enumeration will go through all
|
||||
// entries on the cache only if the cache is not modified while the
|
||||
// enumeration is taking place. Significantly altering the entry pointed by
|
||||
// the iterator (for example, deleting the entry) will invalidate the
|
||||
// iterator. Performing operations on an entry that modify the entry may
|
||||
// result in loops in the iteration, skipped entries or similar.
|
||||
std::unique_ptr<Iterator> CreateIterator() override;
|
||||
void GetStats(StatsItems* stats) override;
|
||||
void OnExternalCacheHit(const std::string& key) override;
|
||||
size_t DumpMemoryStats(
|
||||
base::trace_event::ProcessMemoryDump* pmd,
|
||||
const std::string& parent_absolute_name) const override;
|
||||
|
||||
private:
|
||||
using EntriesMap = std::unordered_map<CacheAddr, EntryImpl*>;
|
||||
class IteratorImpl;
|
||||
|
||||
// Creates a new backing file for the cache index.
|
||||
bool CreateBackingStore(disk_cache::File* file);
|
||||
bool InitBackingStore(bool* file_created);
|
||||
void AdjustMaxCacheSize(int table_len);
|
||||
|
||||
bool InitStats();
|
||||
void StoreStats();
|
||||
|
||||
// Deletes the cache and starts again.
|
||||
void RestartCache(bool failure);
|
||||
void PrepareForRestart();
|
||||
|
||||
// Creates a new entry object. Returns zero on success, or a disk_cache error
|
||||
// on failure.
|
||||
int NewEntry(Addr address, scoped_refptr<EntryImpl>* entry);
|
||||
|
||||
// Returns a given entry from the cache. The entry to match is determined by
|
||||
// key and hash, and the returned entry may be the matched one or it's parent
|
||||
// on the list of entries with the same hash (or bucket). To look for a parent
|
||||
// of a given entry, |entry_addr| should be grabbed from that entry, so that
|
||||
// if it doesn't match the entry on the index, we know that it was replaced
|
||||
// with a new entry; in this case |*match_error| will be set to true and the
|
||||
// return value will be NULL.
|
||||
scoped_refptr<EntryImpl> MatchEntry(const std::string& key,
|
||||
uint32_t hash,
|
||||
bool find_parent,
|
||||
Addr entry_addr,
|
||||
bool* match_error);
|
||||
|
||||
// Opens the next or previous entry on a single list. If successful,
|
||||
// |from_entry| will be updated to point to the new entry, otherwise it will
|
||||
// be set to NULL; in other words, it is used as an explicit iterator.
|
||||
bool OpenFollowingEntryFromList(Rankings::List list,
|
||||
CacheRankingsBlock** from_entry,
|
||||
scoped_refptr<EntryImpl>* next_entry);
|
||||
|
||||
// Returns the entry that is pointed by |next|, from the given |list|.
|
||||
scoped_refptr<EntryImpl> GetEnumeratedEntry(CacheRankingsBlock* next,
|
||||
Rankings::List list);
|
||||
|
||||
// Re-opens an entry that was previously deleted.
|
||||
scoped_refptr<EntryImpl> ResurrectEntry(
|
||||
scoped_refptr<EntryImpl> deleted_entry);
|
||||
|
||||
void DestroyInvalidEntry(EntryImpl* entry);
|
||||
|
||||
// Handles the used storage count.
|
||||
void AddStorageSize(int32_t bytes);
|
||||
void SubstractStorageSize(int32_t bytes);
|
||||
|
||||
// Update the number of referenced cache entries.
|
||||
void IncreaseNumRefs();
|
||||
void DecreaseNumRefs();
|
||||
void IncreaseNumEntries();
|
||||
void DecreaseNumEntries();
|
||||
|
||||
// Dumps current cache statistics to the log.
|
||||
void LogStats();
|
||||
|
||||
// Send UMA stats.
|
||||
void ReportStats();
|
||||
|
||||
// Upgrades the index file to version 2.1.
|
||||
void UpgradeTo2_1();
|
||||
|
||||
// Performs basic checks on the index file. Returns false on failure.
|
||||
bool CheckIndex();
|
||||
|
||||
// Part of the self test. Returns the number or dirty entries, or an error.
|
||||
int CheckAllEntries();
|
||||
|
||||
// Part of the self test. Returns false if the entry is corrupt.
|
||||
bool CheckEntry(EntryImpl* cache_entry);
|
||||
|
||||
// Returns the maximum total memory for the memory buffers.
|
||||
int MaxBuffersSize();
|
||||
|
||||
// We want this destroyed after every other field.
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker_;
|
||||
|
||||
InFlightBackendIO background_queue_; // The controller of pending operations.
|
||||
scoped_refptr<MappedFile> index_; // The main cache index.
|
||||
base::FilePath path_; // Path to the folder used as backing storage.
|
||||
Index* data_; // Pointer to the index data.
|
||||
BlockFiles block_files_; // Set of files used to store all data.
|
||||
Rankings rankings_; // Rankings to be able to trim the cache.
|
||||
uint32_t mask_; // Binary mask to map a hash to the hash table.
|
||||
int32_t max_size_; // Maximum data size for this instance.
|
||||
Eviction eviction_; // Handler of the eviction algorithm.
|
||||
EntriesMap open_entries_; // Map of open entries.
|
||||
int num_refs_; // Number of referenced cache entries.
|
||||
int max_refs_; // Max number of referenced cache entries.
|
||||
int num_pending_io_; // Number of pending IO operations.
|
||||
int entry_count_; // Number of entries accessed lately.
|
||||
int byte_count_; // Number of bytes read/written lately.
|
||||
int buffer_bytes_; // Total size of the temporary entries' buffers.
|
||||
int up_ticks_; // The number of timer ticks received (OnStatsTimer).
|
||||
net::CacheType cache_type_;
|
||||
int uma_report_; // Controls transmission of UMA data.
|
||||
uint32_t user_flags_; // Flags set by the user.
|
||||
bool init_; // controls the initialization of the system.
|
||||
bool restarted_;
|
||||
bool unit_test_;
|
||||
bool read_only_; // Prevents updates of the rankings data (used by tools).
|
||||
bool disabled_;
|
||||
bool new_eviction_; // What eviction algorithm should be used.
|
||||
bool first_timer_; // True if the timer has not been called.
|
||||
bool user_load_; // True if we see a high load coming from the caller.
|
||||
|
||||
net::NetLog* net_log_;
|
||||
|
||||
Stats stats_; // Usage statistics.
|
||||
std::unique_ptr<base::RepeatingTimer> timer_; // Usage timer.
|
||||
base::WaitableEvent done_; // Signals the end of background work.
|
||||
scoped_refptr<TraceObject> trace_object_; // Initializes internal tracing.
|
||||
base::WeakPtrFactory<BackendImpl> ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BackendImpl);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_BACKEND_IMPL_H_
|
||||
310
net/disk_cache/blockfile/bitmap.cc
Normal file
310
net/disk_cache/blockfile/bitmap.cc
Normal file
@@ -0,0 +1,310 @@
|
||||
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/bitmap.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns the number of trailing zeros.
|
||||
int FindLSBSetNonZero(uint32_t word) {
|
||||
// Get the LSB, put it on the exponent of a 32 bit float and remove the
|
||||
// mantisa and the bias. This code requires IEEE 32 bit float compliance.
|
||||
float f = static_cast<float>(word & -static_cast<int>(word));
|
||||
|
||||
// We use a union to go around strict-aliasing complains.
|
||||
union {
|
||||
float ieee_float;
|
||||
uint32_t as_uint;
|
||||
} x;
|
||||
|
||||
x.ieee_float = f;
|
||||
return (x.as_uint >> 23) - 0x7f;
|
||||
}
|
||||
|
||||
// Returns the index of the first bit set to |value| from |word|. This code
|
||||
// assumes that we'll be able to find that bit.
|
||||
int FindLSBNonEmpty(uint32_t word, bool value) {
|
||||
// If we are looking for 0, negate |word| and look for 1.
|
||||
if (!value)
|
||||
word = ~word;
|
||||
|
||||
return FindLSBSetNonZero(word);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
Bitmap::Bitmap(int num_bits, bool clear_bits)
|
||||
: num_bits_(num_bits),
|
||||
array_size_(RequiredArraySize(num_bits)),
|
||||
alloc_(true) {
|
||||
map_ = new uint32_t[array_size_];
|
||||
|
||||
// Initialize all of the bits.
|
||||
if (clear_bits)
|
||||
Clear();
|
||||
}
|
||||
|
||||
Bitmap::Bitmap(uint32_t* map, int num_bits, int num_words)
|
||||
: map_(map),
|
||||
num_bits_(num_bits),
|
||||
// If size is larger than necessary, trim because array_size_ is used
|
||||
// as a bound by various methods.
|
||||
array_size_(std::min(RequiredArraySize(num_bits), num_words)),
|
||||
alloc_(false) {}
|
||||
|
||||
Bitmap::~Bitmap() {
|
||||
if (alloc_)
|
||||
delete [] map_;
|
||||
}
|
||||
|
||||
void Bitmap::Resize(int num_bits, bool clear_bits) {
|
||||
DCHECK(alloc_ || !map_);
|
||||
const int old_maxsize = num_bits_;
|
||||
const int old_array_size = array_size_;
|
||||
array_size_ = RequiredArraySize(num_bits);
|
||||
|
||||
if (array_size_ != old_array_size) {
|
||||
uint32_t* new_map = new uint32_t[array_size_];
|
||||
// Always clear the unused bits in the last word.
|
||||
new_map[array_size_ - 1] = 0;
|
||||
memcpy(new_map, map_,
|
||||
sizeof(*map_) * std::min(array_size_, old_array_size));
|
||||
if (alloc_)
|
||||
delete[] map_; // No need to check for NULL.
|
||||
map_ = new_map;
|
||||
alloc_ = true;
|
||||
}
|
||||
|
||||
num_bits_ = num_bits;
|
||||
if (old_maxsize < num_bits_ && clear_bits) {
|
||||
SetRange(old_maxsize, num_bits_, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Bitmap::Set(int index, bool value) {
|
||||
DCHECK_LT(index, num_bits_);
|
||||
DCHECK_GE(index, 0);
|
||||
const int i = index & (kIntBits - 1);
|
||||
const int j = index / kIntBits;
|
||||
if (value)
|
||||
map_[j] |= (1 << i);
|
||||
else
|
||||
map_[j] &= ~(1 << i);
|
||||
}
|
||||
|
||||
bool Bitmap::Get(int index) const {
|
||||
DCHECK_LT(index, num_bits_);
|
||||
DCHECK_GE(index, 0);
|
||||
const int i = index & (kIntBits-1);
|
||||
const int j = index / kIntBits;
|
||||
return ((map_[j] & (1 << i)) != 0);
|
||||
}
|
||||
|
||||
void Bitmap::Toggle(int index) {
|
||||
DCHECK_LT(index, num_bits_);
|
||||
DCHECK_GE(index, 0);
|
||||
const int i = index & (kIntBits - 1);
|
||||
const int j = index / kIntBits;
|
||||
map_[j] ^= (1 << i);
|
||||
}
|
||||
|
||||
void Bitmap::SetMapElement(int array_index, uint32_t value) {
|
||||
DCHECK_LT(array_index, array_size_);
|
||||
DCHECK_GE(array_index, 0);
|
||||
map_[array_index] = value;
|
||||
}
|
||||
|
||||
uint32_t Bitmap::GetMapElement(int array_index) const {
|
||||
DCHECK_LT(array_index, array_size_);
|
||||
DCHECK_GE(array_index, 0);
|
||||
return map_[array_index];
|
||||
}
|
||||
|
||||
void Bitmap::SetMap(const uint32_t* map, int size) {
|
||||
memcpy(map_, map, std::min(size, array_size_) * sizeof(*map_));
|
||||
}
|
||||
|
||||
void Bitmap::SetRange(int begin, int end, bool value) {
|
||||
DCHECK_LE(begin, end);
|
||||
int start_offset = begin & (kIntBits - 1);
|
||||
if (start_offset) {
|
||||
// Set the bits in the first word.
|
||||
int len = std::min(end - begin, kIntBits - start_offset);
|
||||
SetWordBits(begin, len, value);
|
||||
begin += len;
|
||||
}
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
// Now set the bits in the last word.
|
||||
int end_offset = end & (kIntBits - 1);
|
||||
end -= end_offset;
|
||||
SetWordBits(end, end_offset, value);
|
||||
|
||||
// Set all the words in the middle.
|
||||
memset(map_ + (begin / kIntBits), (value ? 0xFF : 0x00),
|
||||
((end / kIntBits) - (begin / kIntBits)) * sizeof(*map_));
|
||||
}
|
||||
|
||||
// Return true if any bit between begin inclusive and end exclusive
|
||||
// is set. 0 <= begin <= end <= bits() is required.
|
||||
bool Bitmap::TestRange(int begin, int end, bool value) const {
|
||||
DCHECK_LT(begin, num_bits_);
|
||||
DCHECK_LE(end, num_bits_);
|
||||
DCHECK_LE(begin, end);
|
||||
DCHECK_GE(begin, 0);
|
||||
DCHECK_GE(end, 0);
|
||||
|
||||
// Return false immediately if the range is empty.
|
||||
if (begin >= end || end <= 0)
|
||||
return false;
|
||||
|
||||
// Calculate the indices of the words containing the first and last bits,
|
||||
// along with the positions of the bits within those words.
|
||||
int word = begin / kIntBits;
|
||||
int offset = begin & (kIntBits - 1);
|
||||
int last_word = (end - 1) / kIntBits;
|
||||
int last_offset = (end - 1) & (kIntBits - 1);
|
||||
|
||||
// If we are looking for zeros, negate the data from the map.
|
||||
uint32_t this_word = map_[word];
|
||||
if (!value)
|
||||
this_word = ~this_word;
|
||||
|
||||
// If the range spans multiple words, discard the extraneous bits of the
|
||||
// first word by shifting to the right, and then test the remaining bits.
|
||||
if (word < last_word) {
|
||||
if (this_word >> offset)
|
||||
return true;
|
||||
offset = 0;
|
||||
|
||||
word++;
|
||||
// Test each of the "middle" words that lies completely within the range.
|
||||
while (word < last_word) {
|
||||
this_word = map_[word++];
|
||||
if (!value)
|
||||
this_word = ~this_word;
|
||||
if (this_word)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Test the portion of the last word that lies within the range. (This logic
|
||||
// also handles the case where the entire range lies within a single word.)
|
||||
const uint32_t mask = ((2 << (last_offset - offset)) - 1) << offset;
|
||||
|
||||
this_word = map_[last_word];
|
||||
if (!value)
|
||||
this_word = ~this_word;
|
||||
|
||||
return (this_word & mask) != 0;
|
||||
}
|
||||
|
||||
bool Bitmap::FindNextBit(int* index, int limit, bool value) const {
|
||||
DCHECK_LT(*index, num_bits_);
|
||||
DCHECK_LE(limit, num_bits_);
|
||||
DCHECK_LE(*index, limit);
|
||||
DCHECK_GE(*index, 0);
|
||||
DCHECK_GE(limit, 0);
|
||||
|
||||
const int bit_index = *index;
|
||||
if (bit_index >= limit || limit <= 0)
|
||||
return false;
|
||||
|
||||
// From now on limit != 0, since if it was we would have returned false.
|
||||
int word_index = bit_index >> kLogIntBits;
|
||||
uint32_t one_word = map_[word_index];
|
||||
|
||||
// Simple optimization where we can immediately return true if the first
|
||||
// bit is set. This helps for cases where many bits are set, and doesn't
|
||||
// hurt too much if not.
|
||||
if (Get(bit_index) == value)
|
||||
return true;
|
||||
|
||||
const int first_bit_offset = bit_index & (kIntBits - 1);
|
||||
|
||||
// First word is special - we need to mask off leading bits.
|
||||
uint32_t mask = 0xFFFFFFFF << first_bit_offset;
|
||||
if (value) {
|
||||
one_word &= mask;
|
||||
} else {
|
||||
one_word |= ~mask;
|
||||
}
|
||||
|
||||
uint32_t empty_value = value ? 0 : 0xFFFFFFFF;
|
||||
|
||||
// Loop through all but the last word. Note that 'limit' is one
|
||||
// past the last bit we want to check, and we don't want to read
|
||||
// past the end of "words". E.g. if num_bits_ == 32 only words[0] is
|
||||
// valid, so we want to avoid reading words[1] when limit == 32.
|
||||
const int last_word_index = (limit - 1) >> kLogIntBits;
|
||||
while (word_index < last_word_index) {
|
||||
if (one_word != empty_value) {
|
||||
*index = (word_index << kLogIntBits) + FindLSBNonEmpty(one_word, value);
|
||||
return true;
|
||||
}
|
||||
one_word = map_[++word_index];
|
||||
}
|
||||
|
||||
// Last word is special - we may need to mask off trailing bits. Note that
|
||||
// 'limit' is one past the last bit we want to check, and if limit is a
|
||||
// multiple of 32 we want to check all bits in this word.
|
||||
const int last_bit_offset = (limit - 1) & (kIntBits - 1);
|
||||
mask = 0xFFFFFFFE << last_bit_offset;
|
||||
if (value) {
|
||||
one_word &= ~mask;
|
||||
} else {
|
||||
one_word |= mask;
|
||||
}
|
||||
if (one_word != empty_value) {
|
||||
*index = (word_index << kLogIntBits) + FindLSBNonEmpty(one_word, value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int Bitmap::FindBits(int* index, int limit, bool value) const {
|
||||
DCHECK_LT(*index, num_bits_);
|
||||
DCHECK_LE(limit, num_bits_);
|
||||
DCHECK_LE(*index, limit);
|
||||
DCHECK_GE(*index, 0);
|
||||
DCHECK_GE(limit, 0);
|
||||
|
||||
if (!FindNextBit(index, limit, value))
|
||||
return false;
|
||||
|
||||
// Now see how many bits have the same value.
|
||||
int end = *index;
|
||||
if (!FindNextBit(&end, limit, !value))
|
||||
return limit - *index;
|
||||
|
||||
return end - *index;
|
||||
}
|
||||
|
||||
void Bitmap::SetWordBits(int start, int len, bool value) {
|
||||
DCHECK_LT(len, kIntBits);
|
||||
DCHECK_GE(len, 0);
|
||||
if (!len)
|
||||
return;
|
||||
|
||||
int word = start / kIntBits;
|
||||
int offset = start % kIntBits;
|
||||
|
||||
uint32_t to_add = 0xffffffff << len;
|
||||
to_add = (~to_add) << offset;
|
||||
if (value) {
|
||||
map_[word] |= to_add;
|
||||
} else {
|
||||
map_[word] &= ~to_add;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
139
net/disk_cache/blockfile/bitmap.h
Normal file
139
net/disk_cache/blockfile/bitmap.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_BITMAP_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_BITMAP_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This class provides support for simple maps of bits.
|
||||
class NET_EXPORT_PRIVATE Bitmap {
|
||||
public:
|
||||
Bitmap() : map_(NULL), num_bits_(0), array_size_(0), alloc_(false) {}
|
||||
|
||||
// This constructor will allocate on a uint32_t boundary. If |clear_bits| is
|
||||
// false, the bitmap bits will not be initialized.
|
||||
Bitmap(int num_bits, bool clear_bits);
|
||||
|
||||
// Constructs a Bitmap with the actual storage provided by the caller. |map|
|
||||
// has to be valid until this object destruction. |num_bits| is the number of
|
||||
// bits in the bitmap, and |num_words| is the size of |map| in 32-bit words.
|
||||
Bitmap(uint32_t* map, int num_bits, int num_words);
|
||||
|
||||
~Bitmap();
|
||||
|
||||
// Resizes the bitmap.
|
||||
// If |num_bits| < Size(), the extra bits will be discarded.
|
||||
// If |num_bits| > Size(), the extra bits will be filled with zeros if
|
||||
// |clear_bits| is true.
|
||||
// This object cannot be using memory provided during construction.
|
||||
void Resize(int num_bits, bool clear_bits);
|
||||
|
||||
// Returns the number of bits in the bitmap.
|
||||
int Size() const { return num_bits_; }
|
||||
|
||||
// Returns the number of 32-bit words in the bitmap.
|
||||
int ArraySize() const { return array_size_; }
|
||||
|
||||
// Sets all the bits to true or false.
|
||||
void SetAll(bool value) {
|
||||
memset(map_, (value ? 0xFF : 0x00), array_size_ * sizeof(*map_));
|
||||
}
|
||||
|
||||
// Clears all bits in the bitmap
|
||||
void Clear() { SetAll(false); }
|
||||
|
||||
// Sets the value, gets the value or toggles the value of a given bit.
|
||||
void Set(int index, bool value);
|
||||
bool Get(int index) const;
|
||||
void Toggle(int index);
|
||||
|
||||
// Directly sets an element of the internal map. Requires |array_index| <
|
||||
// ArraySize();
|
||||
void SetMapElement(int array_index, uint32_t value);
|
||||
|
||||
// Gets an entry of the internal map. Requires array_index <
|
||||
// ArraySize()
|
||||
uint32_t GetMapElement(int array_index) const;
|
||||
|
||||
// Directly sets the whole internal map. |size| is the number of 32-bit words
|
||||
// to set from |map|. If |size| > array_size(), it ignores the end of |map|.
|
||||
void SetMap(const uint32_t* map, int size);
|
||||
|
||||
// Gets a pointer to the internal map.
|
||||
const uint32_t* GetMap() const { return map_; }
|
||||
|
||||
// Sets a range of bits to |value|.
|
||||
void SetRange(int begin, int end, bool value);
|
||||
|
||||
// Returns true if any bit between begin inclusive and end exclusive is set.
|
||||
// 0 <= |begin| <= |end| <= Size() is required.
|
||||
bool TestRange(int begin, int end, bool value) const;
|
||||
|
||||
// Scans bits starting at bit *|index|, looking for a bit set to |value|. If
|
||||
// it finds that bit before reaching bit index |limit|, sets *|index| to the
|
||||
// bit index and returns true. Otherwise returns false.
|
||||
// Requires |limit| <= Size().
|
||||
//
|
||||
// Note that to use these methods in a loop you must increment the index
|
||||
// after each use, as in:
|
||||
//
|
||||
// for (int index = 0 ; map.FindNextBit(&index, limit, value) ; ++index) {
|
||||
// DoSomethingWith(index);
|
||||
// }
|
||||
bool FindNextBit(int* index, int limit, bool value) const;
|
||||
|
||||
// Finds the first offset >= *|index| and < |limit| that has its bit set.
|
||||
// See FindNextBit() for more info.
|
||||
bool FindNextSetBitBeforeLimit(int* index, int limit) const {
|
||||
return FindNextBit(index, limit, true);
|
||||
}
|
||||
|
||||
// Finds the first offset >= *|index| that has its bit set.
|
||||
// See FindNextBit() for more info.
|
||||
bool FindNextSetBit(int *index) const {
|
||||
return FindNextSetBitBeforeLimit(index, num_bits_);
|
||||
}
|
||||
|
||||
// Scans bits starting at bit *|index|, looking for a bit set to |value|. If
|
||||
// it finds that bit before reaching bit index |limit|, sets *|index| to the
|
||||
// bit index and then counts the number of consecutive bits set to |value|
|
||||
// (before reaching |limit|), and returns that count. If no bit is found
|
||||
// returns 0. Requires |limit| <= Size().
|
||||
int FindBits(int* index, int limit, bool value) const;
|
||||
|
||||
// Returns number of allocated words required for a bitmap of size |num_bits|.
|
||||
static int RequiredArraySize(int num_bits) {
|
||||
// Force at least one allocated word.
|
||||
if (num_bits <= kIntBits)
|
||||
return 1;
|
||||
|
||||
return (num_bits + kIntBits - 1) >> kLogIntBits;
|
||||
}
|
||||
|
||||
private:
|
||||
static const int kIntBits = sizeof(uint32_t) * 8;
|
||||
static const int kLogIntBits = 5; // 2^5 == 32 bits per word.
|
||||
|
||||
// Sets |len| bits from |start| to |value|. All the bits to be set should be
|
||||
// stored in the same word, and len < kIntBits.
|
||||
void SetWordBits(int start, int len, bool value);
|
||||
|
||||
uint32_t* map_; // The bitmap.
|
||||
int num_bits_; // The upper bound of the bitmap.
|
||||
int array_size_; // The physical size (in uint32s) of the bitmap.
|
||||
bool alloc_; // Whether or not we allocated the memory.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Bitmap);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_BITMAP_H_
|
||||
724
net/disk_cache/blockfile/block_files.cc
Normal file
724
net/disk_cache/blockfile/block_files.cc
Normal file
@@ -0,0 +1,724 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/block_files.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/atomicops.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/disk_cache/blockfile/file_lock.h"
|
||||
#include "net/disk_cache/blockfile/stress_support.h"
|
||||
#include "net/disk_cache/blockfile/trace.h"
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
|
||||
using base::TimeTicks;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kBlockName[] = "data_";
|
||||
|
||||
// This array is used to perform a fast lookup of the nibble bit pattern to the
|
||||
// type of entry that can be stored there (number of consecutive blocks).
|
||||
const char s_types[16] = {4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// Returns the type of block (number of consecutive blocks that can be stored)
|
||||
// for a given nibble of the bitmap.
|
||||
inline int GetMapBlockType(uint32_t value) {
|
||||
value &= 0xf;
|
||||
return s_types[value];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
BlockHeader::BlockHeader() : header_(NULL) {
|
||||
}
|
||||
|
||||
BlockHeader::BlockHeader(BlockFileHeader* header) : header_(header) {
|
||||
}
|
||||
|
||||
BlockHeader::BlockHeader(MappedFile* file)
|
||||
: header_(reinterpret_cast<BlockFileHeader*>(file->buffer())) {
|
||||
}
|
||||
|
||||
BlockHeader::BlockHeader(const BlockHeader& other) = default;
|
||||
|
||||
BlockHeader::~BlockHeader() = default;
|
||||
|
||||
bool BlockHeader::CreateMapBlock(int size, int* index) {
|
||||
DCHECK(size > 0 && size <= kMaxNumBlocks);
|
||||
int target = 0;
|
||||
for (int i = size; i <= kMaxNumBlocks; i++) {
|
||||
if (header_->empty[i - 1]) {
|
||||
target = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
STRESS_NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
// We are going to process the map on 32-block chunks (32 bits), and on every
|
||||
// chunk, iterate through the 8 nibbles where the new block can be located.
|
||||
int current = header_->hints[target - 1];
|
||||
for (int i = 0; i < header_->max_entries / 32; i++, current++) {
|
||||
if (current == header_->max_entries / 32)
|
||||
current = 0;
|
||||
uint32_t map_block = header_->allocation_map[current];
|
||||
|
||||
for (int j = 0; j < 8; j++, map_block >>= 4) {
|
||||
if (GetMapBlockType(map_block) != target)
|
||||
continue;
|
||||
|
||||
disk_cache::FileLock lock(header_);
|
||||
int index_offset = j * 4 + 4 - target;
|
||||
*index = current * 32 + index_offset;
|
||||
STRESS_DCHECK(*index / 4 == (*index + size - 1) / 4);
|
||||
uint32_t to_add = ((1 << size) - 1) << index_offset;
|
||||
header_->num_entries++;
|
||||
|
||||
// Note that there is no race in the normal sense here, but if we enforce
|
||||
// the order of memory accesses between num_entries and allocation_map, we
|
||||
// can assert that even if we crash here, num_entries will never be less
|
||||
// than the actual number of used blocks.
|
||||
base::subtle::MemoryBarrier();
|
||||
header_->allocation_map[current] |= to_add;
|
||||
|
||||
header_->hints[target - 1] = current;
|
||||
header_->empty[target - 1]--;
|
||||
STRESS_DCHECK(header_->empty[target - 1] >= 0);
|
||||
if (target != size) {
|
||||
header_->empty[target - size - 1]++;
|
||||
}
|
||||
LOCAL_HISTOGRAM_TIMES("DiskCache.CreateBlock", TimeTicks::Now() - start);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// It is possible to have an undetected corruption (for example when the OS
|
||||
// crashes), fix it here.
|
||||
LOG(ERROR) << "Failing CreateMapBlock";
|
||||
FixAllocationCounters();
|
||||
return false;
|
||||
}
|
||||
|
||||
void BlockHeader::DeleteMapBlock(int index, int size) {
|
||||
if (size < 0 || size > kMaxNumBlocks) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
int byte_index = index / 8;
|
||||
uint8_t* byte_map = reinterpret_cast<uint8_t*>(header_->allocation_map);
|
||||
uint8_t map_block = byte_map[byte_index];
|
||||
|
||||
if (index % 8 >= 4)
|
||||
map_block >>= 4;
|
||||
|
||||
// See what type of block will be available after we delete this one.
|
||||
int bits_at_end = 4 - size - index % 4;
|
||||
uint8_t end_mask = (0xf << (4 - bits_at_end)) & 0xf;
|
||||
bool update_counters = (map_block & end_mask) == 0;
|
||||
uint8_t new_value = map_block & ~(((1 << size) - 1) << (index % 4));
|
||||
int new_type = GetMapBlockType(new_value);
|
||||
|
||||
disk_cache::FileLock lock(header_);
|
||||
STRESS_DCHECK((((1 << size) - 1) << (index % 8)) < 0x100);
|
||||
uint8_t to_clear = ((1 << size) - 1) << (index % 8);
|
||||
STRESS_DCHECK((byte_map[byte_index] & to_clear) == to_clear);
|
||||
byte_map[byte_index] &= ~to_clear;
|
||||
|
||||
if (update_counters) {
|
||||
if (bits_at_end)
|
||||
header_->empty[bits_at_end - 1]--;
|
||||
header_->empty[new_type - 1]++;
|
||||
STRESS_DCHECK(header_->empty[bits_at_end - 1] >= 0);
|
||||
}
|
||||
base::subtle::MemoryBarrier();
|
||||
header_->num_entries--;
|
||||
STRESS_DCHECK(header_->num_entries >= 0);
|
||||
LOCAL_HISTOGRAM_TIMES("DiskCache.DeleteBlock", TimeTicks::Now() - start);
|
||||
}
|
||||
|
||||
// Note that this is a simplified version of DeleteMapBlock().
|
||||
bool BlockHeader::UsedMapBlock(int index, int size) {
|
||||
if (size < 0 || size > kMaxNumBlocks)
|
||||
return false;
|
||||
|
||||
int byte_index = index / 8;
|
||||
uint8_t* byte_map = reinterpret_cast<uint8_t*>(header_->allocation_map);
|
||||
uint8_t map_block = byte_map[byte_index];
|
||||
|
||||
if (index % 8 >= 4)
|
||||
map_block >>= 4;
|
||||
|
||||
STRESS_DCHECK((((1 << size) - 1) << (index % 8)) < 0x100);
|
||||
uint8_t to_clear = ((1 << size) - 1) << (index % 8);
|
||||
return ((byte_map[byte_index] & to_clear) == to_clear);
|
||||
}
|
||||
|
||||
void BlockHeader::FixAllocationCounters() {
|
||||
for (int i = 0; i < kMaxNumBlocks; i++) {
|
||||
header_->hints[i] = 0;
|
||||
header_->empty[i] = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < header_->max_entries / 32; i++) {
|
||||
uint32_t map_block = header_->allocation_map[i];
|
||||
|
||||
for (int j = 0; j < 8; j++, map_block >>= 4) {
|
||||
int type = GetMapBlockType(map_block);
|
||||
if (type)
|
||||
header_->empty[type -1]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BlockHeader::NeedToGrowBlockFile(int block_count) const {
|
||||
bool have_space = false;
|
||||
int empty_blocks = 0;
|
||||
for (int i = 0; i < kMaxNumBlocks; i++) {
|
||||
empty_blocks += header_->empty[i] * (i + 1);
|
||||
if (i >= block_count - 1 && header_->empty[i])
|
||||
have_space = true;
|
||||
}
|
||||
|
||||
if (header_->next_file && (empty_blocks < kMaxBlocks / 10)) {
|
||||
// This file is almost full but we already created another one, don't use
|
||||
// this file yet so that it is easier to find empty blocks when we start
|
||||
// using this file again.
|
||||
return true;
|
||||
}
|
||||
return !have_space;
|
||||
}
|
||||
|
||||
bool BlockHeader::CanAllocate(int block_count) const {
|
||||
DCHECK_GT(block_count, 0);
|
||||
for (int i = block_count - 1; i < kMaxNumBlocks; i++) {
|
||||
if (header_->empty[i])
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int BlockHeader::EmptyBlocks() const {
|
||||
int empty_blocks = 0;
|
||||
for (int i = 0; i < kMaxNumBlocks; i++) {
|
||||
empty_blocks += header_->empty[i] * (i + 1);
|
||||
if (header_->empty[i] < 0)
|
||||
return 0;
|
||||
}
|
||||
return empty_blocks;
|
||||
}
|
||||
|
||||
int BlockHeader::MinimumAllocations() const {
|
||||
return header_->empty[kMaxNumBlocks - 1];
|
||||
}
|
||||
|
||||
int BlockHeader::Capacity() const {
|
||||
return header_->max_entries;
|
||||
}
|
||||
|
||||
bool BlockHeader::ValidateCounters() const {
|
||||
if (header_->max_entries < 0 || header_->max_entries > kMaxBlocks ||
|
||||
header_->num_entries < 0)
|
||||
return false;
|
||||
|
||||
int empty_blocks = EmptyBlocks();
|
||||
if (empty_blocks + header_->num_entries > header_->max_entries)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int BlockHeader::FileId() const {
|
||||
return header_->this_file;
|
||||
}
|
||||
|
||||
int BlockHeader::NextFileId() const {
|
||||
return header_->next_file;
|
||||
}
|
||||
|
||||
int BlockHeader::Size() const {
|
||||
return static_cast<int>(sizeof(*header_));
|
||||
}
|
||||
|
||||
BlockFileHeader* BlockHeader::Header() {
|
||||
return header_;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
BlockFiles::BlockFiles(const base::FilePath& path)
|
||||
: init_(false), zero_buffer_(NULL), path_(path) {
|
||||
}
|
||||
|
||||
BlockFiles::~BlockFiles() {
|
||||
if (zero_buffer_)
|
||||
delete[] zero_buffer_;
|
||||
CloseFiles();
|
||||
}
|
||||
|
||||
bool BlockFiles::Init(bool create_files) {
|
||||
DCHECK(!init_);
|
||||
if (init_)
|
||||
return false;
|
||||
|
||||
thread_checker_.reset(new base::ThreadChecker);
|
||||
|
||||
block_files_.resize(kFirstAdditionalBlockFile);
|
||||
for (int16_t i = 0; i < kFirstAdditionalBlockFile; i++) {
|
||||
if (create_files)
|
||||
if (!CreateBlockFile(i, static_cast<FileType>(i + 1), true))
|
||||
return false;
|
||||
|
||||
if (!OpenBlockFile(i))
|
||||
return false;
|
||||
|
||||
// Walk this chain of files removing empty ones.
|
||||
if (!RemoveEmptyFile(static_cast<FileType>(i + 1)))
|
||||
return false;
|
||||
}
|
||||
|
||||
init_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
MappedFile* BlockFiles::GetFile(Addr address) {
|
||||
DCHECK(thread_checker_->CalledOnValidThread());
|
||||
DCHECK_GE(block_files_.size(),
|
||||
static_cast<size_t>(kFirstAdditionalBlockFile));
|
||||
DCHECK(address.is_block_file() || !address.is_initialized());
|
||||
if (!address.is_initialized())
|
||||
return NULL;
|
||||
|
||||
int file_index = address.FileNumber();
|
||||
if (static_cast<unsigned int>(file_index) >= block_files_.size() ||
|
||||
!block_files_[file_index]) {
|
||||
// We need to open the file
|
||||
if (!OpenBlockFile(file_index))
|
||||
return NULL;
|
||||
}
|
||||
DCHECK_GE(block_files_.size(), static_cast<unsigned int>(file_index));
|
||||
return block_files_[file_index].get();
|
||||
}
|
||||
|
||||
bool BlockFiles::CreateBlock(FileType block_type, int block_count,
|
||||
Addr* block_address) {
|
||||
DCHECK(thread_checker_->CalledOnValidThread());
|
||||
DCHECK_NE(block_type, EXTERNAL);
|
||||
DCHECK_NE(block_type, BLOCK_FILES);
|
||||
DCHECK_NE(block_type, BLOCK_ENTRIES);
|
||||
DCHECK_NE(block_type, BLOCK_EVICTED);
|
||||
if (block_count < 1 || block_count > kMaxNumBlocks)
|
||||
return false;
|
||||
|
||||
if (!init_)
|
||||
return false;
|
||||
|
||||
MappedFile* file = FileForNewBlock(block_type, block_count);
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
ScopedFlush flush(file);
|
||||
BlockHeader file_header(file);
|
||||
|
||||
int index;
|
||||
if (!file_header.CreateMapBlock(block_count, &index))
|
||||
return false;
|
||||
|
||||
Addr address(block_type, block_count, file_header.FileId(), index);
|
||||
block_address->set_value(address.value());
|
||||
Trace("CreateBlock 0x%x", address.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlockFiles::DeleteBlock(Addr address, bool deep) {
|
||||
DCHECK(thread_checker_->CalledOnValidThread());
|
||||
if (!address.is_initialized() || address.is_separate_file())
|
||||
return;
|
||||
|
||||
if (!zero_buffer_) {
|
||||
zero_buffer_ = new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4];
|
||||
memset(zero_buffer_, 0, Addr::BlockSizeForFileType(BLOCK_4K) * 4);
|
||||
}
|
||||
MappedFile* file = GetFile(address);
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
Trace("DeleteBlock 0x%x", address.value());
|
||||
|
||||
size_t size = address.BlockSize() * address.num_blocks();
|
||||
size_t offset = address.start_block() * address.BlockSize() +
|
||||
kBlockHeaderSize;
|
||||
if (deep)
|
||||
file->Write(zero_buffer_, size, offset);
|
||||
|
||||
BlockHeader file_header(file);
|
||||
file_header.DeleteMapBlock(address.start_block(), address.num_blocks());
|
||||
file->Flush();
|
||||
|
||||
if (!file_header.Header()->num_entries) {
|
||||
// This file is now empty. Let's try to delete it.
|
||||
FileType type = Addr::RequiredFileType(file_header.Header()->entry_size);
|
||||
if (Addr::BlockSizeForFileType(RANKINGS) ==
|
||||
file_header.Header()->entry_size) {
|
||||
type = RANKINGS;
|
||||
}
|
||||
RemoveEmptyFile(type); // Ignore failures.
|
||||
}
|
||||
}
|
||||
|
||||
void BlockFiles::CloseFiles() {
|
||||
if (init_) {
|
||||
DCHECK(thread_checker_->CalledOnValidThread());
|
||||
}
|
||||
init_ = false;
|
||||
block_files_.clear();
|
||||
}
|
||||
|
||||
void BlockFiles::ReportStats() {
|
||||
DCHECK(thread_checker_->CalledOnValidThread());
|
||||
int used_blocks[kFirstAdditionalBlockFile];
|
||||
int load[kFirstAdditionalBlockFile];
|
||||
for (int i = 0; i < kFirstAdditionalBlockFile; i++) {
|
||||
GetFileStats(i, &used_blocks[i], &load[i]);
|
||||
}
|
||||
UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_0", used_blocks[0]);
|
||||
UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_1", used_blocks[1]);
|
||||
UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_2", used_blocks[2]);
|
||||
UMA_HISTOGRAM_COUNTS_1M("DiskCache.Blocks_3", used_blocks[3]);
|
||||
|
||||
UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_0", load[0], 101);
|
||||
UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_1", load[1], 101);
|
||||
UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_2", load[2], 101);
|
||||
UMA_HISTOGRAM_ENUMERATION("DiskCache.BlockLoad_3", load[3], 101);
|
||||
}
|
||||
|
||||
bool BlockFiles::IsValid(Addr address) {
|
||||
#ifdef NDEBUG
|
||||
return true;
|
||||
#else
|
||||
if (!address.is_initialized() || address.is_separate_file())
|
||||
return false;
|
||||
|
||||
MappedFile* file = GetFile(address);
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
BlockHeader header(file);
|
||||
bool rv = header.UsedMapBlock(address.start_block(), address.num_blocks());
|
||||
DCHECK(rv);
|
||||
|
||||
static bool read_contents = false;
|
||||
if (read_contents) {
|
||||
std::unique_ptr<char[]> buffer;
|
||||
buffer.reset(new char[Addr::BlockSizeForFileType(BLOCK_4K) * 4]);
|
||||
size_t size = address.BlockSize() * address.num_blocks();
|
||||
size_t offset = address.start_block() * address.BlockSize() +
|
||||
kBlockHeaderSize;
|
||||
bool ok = file->Read(buffer.get(), size, offset);
|
||||
DCHECK(ok);
|
||||
}
|
||||
|
||||
return rv;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BlockFiles::CreateBlockFile(int index, FileType file_type, bool force) {
|
||||
base::FilePath name = Name(index);
|
||||
int flags = force ? base::File::FLAG_CREATE_ALWAYS : base::File::FLAG_CREATE;
|
||||
flags |= base::File::FLAG_WRITE | base::File::FLAG_EXCLUSIVE_WRITE;
|
||||
|
||||
scoped_refptr<File> file(new File(base::File(name, flags)));
|
||||
if (!file->IsValid())
|
||||
return false;
|
||||
|
||||
BlockFileHeader header;
|
||||
memset(&header, 0, sizeof(header));
|
||||
header.magic = kBlockMagic;
|
||||
header.version = kBlockVersion2;
|
||||
header.entry_size = Addr::BlockSizeForFileType(file_type);
|
||||
header.this_file = static_cast<int16_t>(index);
|
||||
DCHECK(index <= std::numeric_limits<int16_t>::max() && index >= 0);
|
||||
|
||||
return file->Write(&header, sizeof(header), 0);
|
||||
}
|
||||
|
||||
bool BlockFiles::OpenBlockFile(int index) {
|
||||
if (block_files_.size() - 1 < static_cast<unsigned int>(index)) {
|
||||
DCHECK(index > 0);
|
||||
int to_add = index - static_cast<int>(block_files_.size()) + 1;
|
||||
block_files_.resize(block_files_.size() + to_add);
|
||||
}
|
||||
|
||||
base::FilePath name = Name(index);
|
||||
scoped_refptr<MappedFile> file(new MappedFile());
|
||||
|
||||
if (!file->Init(name, kBlockHeaderSize)) {
|
||||
LOG(ERROR) << "Failed to open " << name.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t file_len = file->GetLength();
|
||||
if (file_len < static_cast<size_t>(kBlockHeaderSize)) {
|
||||
LOG(ERROR) << "File too small " << name.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockHeader file_header(file.get());
|
||||
BlockFileHeader* header = file_header.Header();
|
||||
if (kBlockMagic != header->magic || kBlockVersion2 != header->version) {
|
||||
LOG(ERROR) << "Invalid file version or magic " << name.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header->updating || !file_header.ValidateCounters()) {
|
||||
// Last instance was not properly shutdown, or counters are out of sync.
|
||||
if (!FixBlockFileHeader(file.get())) {
|
||||
LOG(ERROR) << "Unable to fix block file " << name.value();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<int>(file_len) <
|
||||
header->max_entries * header->entry_size + kBlockHeaderSize) {
|
||||
LOG(ERROR) << "File too small " << name.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
// Load the links file into memory.
|
||||
if (!file->Preload())
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedFlush flush(file.get());
|
||||
DCHECK(!block_files_[index]);
|
||||
block_files_[index] = std::move(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockFiles::GrowBlockFile(MappedFile* file, BlockFileHeader* header) {
|
||||
if (kMaxBlocks == header->max_entries)
|
||||
return false;
|
||||
|
||||
ScopedFlush flush(file);
|
||||
DCHECK(!header->empty[3]);
|
||||
int new_size = header->max_entries + 1024;
|
||||
if (new_size > kMaxBlocks)
|
||||
new_size = kMaxBlocks;
|
||||
|
||||
int new_size_bytes = new_size * header->entry_size + sizeof(*header);
|
||||
|
||||
if (!file->SetLength(new_size_bytes)) {
|
||||
// Most likely we are trying to truncate the file, so the header is wrong.
|
||||
if (header->updating < 10 && !FixBlockFileHeader(file)) {
|
||||
// If we can't fix the file increase the lock guard so we'll pick it on
|
||||
// the next start and replace it.
|
||||
header->updating = 100;
|
||||
return false;
|
||||
}
|
||||
return (header->max_entries >= new_size);
|
||||
}
|
||||
|
||||
FileLock lock(header);
|
||||
header->empty[3] = (new_size - header->max_entries) / 4; // 4 blocks entries
|
||||
header->max_entries = new_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MappedFile* BlockFiles::FileForNewBlock(FileType block_type, int block_count) {
|
||||
static_assert(RANKINGS == 1, "invalid file type");
|
||||
MappedFile* file = block_files_[block_type - 1].get();
|
||||
BlockHeader file_header(file);
|
||||
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
while (file_header.NeedToGrowBlockFile(block_count)) {
|
||||
if (kMaxBlocks == file_header.Header()->max_entries) {
|
||||
file = NextFile(file);
|
||||
if (!file)
|
||||
return NULL;
|
||||
file_header = BlockHeader(file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!GrowBlockFile(file, file_header.Header()))
|
||||
return NULL;
|
||||
break;
|
||||
}
|
||||
LOCAL_HISTOGRAM_TIMES("DiskCache.GetFileForNewBlock",
|
||||
TimeTicks::Now() - start);
|
||||
return file;
|
||||
}
|
||||
|
||||
MappedFile* BlockFiles::NextFile(MappedFile* file) {
|
||||
ScopedFlush flush(file);
|
||||
BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
|
||||
int16_t new_file = header->next_file;
|
||||
if (!new_file) {
|
||||
// RANKINGS is not reported as a type for small entries, but we may be
|
||||
// extending the rankings block file.
|
||||
FileType type = Addr::RequiredFileType(header->entry_size);
|
||||
if (header->entry_size == Addr::BlockSizeForFileType(RANKINGS))
|
||||
type = RANKINGS;
|
||||
|
||||
new_file = CreateNextBlockFile(type);
|
||||
if (!new_file)
|
||||
return NULL;
|
||||
|
||||
FileLock lock(header);
|
||||
header->next_file = new_file;
|
||||
}
|
||||
|
||||
// Only the block_file argument is relevant for what we want.
|
||||
Addr address(BLOCK_256, 1, new_file, 0);
|
||||
return GetFile(address);
|
||||
}
|
||||
|
||||
int16_t BlockFiles::CreateNextBlockFile(FileType block_type) {
|
||||
for (int16_t i = kFirstAdditionalBlockFile; i <= kMaxBlockFile; i++) {
|
||||
if (CreateBlockFile(i, block_type, false))
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We walk the list of files for this particular block type, deleting the ones
|
||||
// that are empty.
|
||||
bool BlockFiles::RemoveEmptyFile(FileType block_type) {
|
||||
MappedFile* file = block_files_[block_type - 1].get();
|
||||
BlockFileHeader* header = reinterpret_cast<BlockFileHeader*>(file->buffer());
|
||||
|
||||
while (header->next_file) {
|
||||
// Only the block_file argument is relevant for what we want.
|
||||
Addr address(BLOCK_256, 1, header->next_file, 0);
|
||||
MappedFile* next_file = GetFile(address);
|
||||
if (!next_file)
|
||||
return false;
|
||||
|
||||
BlockFileHeader* next_header =
|
||||
reinterpret_cast<BlockFileHeader*>(next_file->buffer());
|
||||
if (!next_header->num_entries) {
|
||||
DCHECK_EQ(next_header->entry_size, header->entry_size);
|
||||
// Delete next_file and remove it from the chain.
|
||||
int file_index = header->next_file;
|
||||
header->next_file = next_header->next_file;
|
||||
DCHECK(block_files_.size() >= static_cast<unsigned int>(file_index));
|
||||
file->Flush();
|
||||
|
||||
// We get a new handle to the file and release the old one so that the
|
||||
// file gets unmmaped... so we can delete it.
|
||||
base::FilePath name = Name(file_index);
|
||||
scoped_refptr<File> this_file(new File(false));
|
||||
this_file->Init(name);
|
||||
block_files_[file_index] = NULL;
|
||||
|
||||
int failure = DeleteCacheFile(name) ? 0 : 1;
|
||||
UMA_HISTOGRAM_COUNTS_1M("DiskCache.DeleteFailed2", failure);
|
||||
if (failure)
|
||||
LOG(ERROR) << "Failed to delete " << name.value() << " from the cache.";
|
||||
continue;
|
||||
}
|
||||
|
||||
header = next_header;
|
||||
file = next_file;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note that we expect to be called outside of a FileLock... however, we cannot
|
||||
// DCHECK on header->updating because we may be fixing a crash.
|
||||
bool BlockFiles::FixBlockFileHeader(MappedFile* file) {
|
||||
ScopedFlush flush(file);
|
||||
BlockHeader file_header(file);
|
||||
int file_size = static_cast<int>(file->GetLength());
|
||||
if (file_size < file_header.Size())
|
||||
return false; // file_size > 2GB is also an error.
|
||||
|
||||
const int kMinHeaderBlockSize = 36;
|
||||
const int kMaxHeaderBlockSize = 4096;
|
||||
BlockFileHeader* header = file_header.Header();
|
||||
if (header->entry_size < kMinHeaderBlockSize ||
|
||||
header->entry_size > kMaxHeaderBlockSize || header->num_entries < 0)
|
||||
return false;
|
||||
|
||||
// Make sure that we survive crashes.
|
||||
header->updating = 1;
|
||||
int expected = header->entry_size * header->max_entries + file_header.Size();
|
||||
if (file_size != expected) {
|
||||
int max_expected = header->entry_size * kMaxBlocks + file_header.Size();
|
||||
if (file_size < expected || header->empty[3] || file_size > max_expected) {
|
||||
NOTREACHED();
|
||||
LOG(ERROR) << "Unexpected file size";
|
||||
return false;
|
||||
}
|
||||
// We were in the middle of growing the file.
|
||||
int num_entries = (file_size - file_header.Size()) / header->entry_size;
|
||||
header->max_entries = num_entries;
|
||||
}
|
||||
|
||||
file_header.FixAllocationCounters();
|
||||
int empty_blocks = file_header.EmptyBlocks();
|
||||
if (empty_blocks + header->num_entries > header->max_entries)
|
||||
header->num_entries = header->max_entries - empty_blocks;
|
||||
|
||||
if (!file_header.ValidateCounters())
|
||||
return false;
|
||||
|
||||
header->updating = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We are interested in the total number of blocks used by this file type, and
|
||||
// the max number of blocks that we can store (reported as the percentage of
|
||||
// used blocks). In order to find out the number of used blocks, we have to
|
||||
// substract the empty blocks from the total blocks for each file in the chain.
|
||||
void BlockFiles::GetFileStats(int index, int* used_count, int* load) {
|
||||
int max_blocks = 0;
|
||||
*used_count = 0;
|
||||
*load = 0;
|
||||
for (;;) {
|
||||
if (!block_files_[index] && !OpenBlockFile(index))
|
||||
return;
|
||||
|
||||
BlockFileHeader* header =
|
||||
reinterpret_cast<BlockFileHeader*>(block_files_[index]->buffer());
|
||||
|
||||
max_blocks += header->max_entries;
|
||||
int used = header->max_entries;
|
||||
for (int i = 0; i < kMaxNumBlocks; i++) {
|
||||
used -= header->empty[i] * (i + 1);
|
||||
DCHECK_GE(used, 0);
|
||||
}
|
||||
*used_count += used;
|
||||
|
||||
if (!header->next_file)
|
||||
break;
|
||||
index = header->next_file;
|
||||
}
|
||||
if (max_blocks)
|
||||
*load = *used_count * 100 / max_blocks;
|
||||
}
|
||||
|
||||
base::FilePath BlockFiles::Name(int index) {
|
||||
// The file format allows for 256 files.
|
||||
DCHECK(index < 256 && index >= 0);
|
||||
std::string tmp = base::StringPrintf("%s%d", kBlockName, index);
|
||||
return path_.AppendASCII(tmp);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
172
net/disk_cache/blockfile/block_files.h
Normal file
172
net/disk_cache/blockfile/block_files.h
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_BLOCK_FILES_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_BLOCK_FILES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/addr.h"
|
||||
#include "net/disk_cache/blockfile/disk_format_base.h"
|
||||
#include "net/disk_cache/blockfile/mapped_file.h"
|
||||
|
||||
namespace base {
|
||||
class ThreadChecker;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// An instance of this class represents the header of a block file in memory.
|
||||
// Note that this class doesn't perform any file operation (as in it only deals
|
||||
// with entities in memory).
|
||||
// The header of a block file (and hence, this object) is all that is needed to
|
||||
// perform common operations like allocating or releasing space for storage;
|
||||
// actual access to that storage, however, is not performed through this class.
|
||||
class NET_EXPORT_PRIVATE BlockHeader {
|
||||
public:
|
||||
BlockHeader();
|
||||
explicit BlockHeader(BlockFileHeader* header);
|
||||
explicit BlockHeader(MappedFile* file);
|
||||
BlockHeader(const BlockHeader& other);
|
||||
~BlockHeader();
|
||||
|
||||
// Creates a new entry of |size| blocks on the allocation map, updating the
|
||||
// apropriate counters.
|
||||
bool CreateMapBlock(int size, int* index);
|
||||
|
||||
// Deletes the block pointed by |index|.
|
||||
void DeleteMapBlock(int index, int block_size);
|
||||
|
||||
// Returns true if the specified block is used.
|
||||
bool UsedMapBlock(int index, int size);
|
||||
|
||||
// Restores the "empty counters" and allocation hints.
|
||||
void FixAllocationCounters();
|
||||
|
||||
// Returns true if the current block file should not be used as-is to store
|
||||
// more records. |block_count| is the number of blocks to allocate.
|
||||
bool NeedToGrowBlockFile(int block_count) const;
|
||||
|
||||
// Returns true if this block file can be used to store an extra record of
|
||||
// size |block_count|.
|
||||
bool CanAllocate(int block_count) const;
|
||||
|
||||
// Returns the number of empty blocks for this file.
|
||||
int EmptyBlocks() const;
|
||||
|
||||
// Returns the minumum number of allocations that can be satisfied.
|
||||
int MinimumAllocations() const;
|
||||
|
||||
// Returns the number of blocks that this file can store.
|
||||
int Capacity() const;
|
||||
|
||||
// Returns true if the counters look OK.
|
||||
bool ValidateCounters() const;
|
||||
|
||||
// Returns the identifiers of this and the next file (0 if there is none).
|
||||
int FileId() const;
|
||||
int NextFileId() const;
|
||||
|
||||
// Returns the size of the wrapped structure (BlockFileHeader).
|
||||
int Size() const;
|
||||
|
||||
// Returns a pointer to the underlying BlockFileHeader.
|
||||
BlockFileHeader* Header();
|
||||
|
||||
private:
|
||||
BlockFileHeader* header_;
|
||||
};
|
||||
|
||||
typedef std::vector<BlockHeader> BlockFilesBitmaps;
|
||||
|
||||
// This class handles the set of block-files open by the disk cache.
|
||||
class NET_EXPORT_PRIVATE BlockFiles {
|
||||
public:
|
||||
explicit BlockFiles(const base::FilePath& path);
|
||||
~BlockFiles();
|
||||
|
||||
// Performs the object initialization. create_files indicates if the backing
|
||||
// files should be created or just open.
|
||||
bool Init(bool create_files);
|
||||
|
||||
// Returns the file that stores a given address.
|
||||
MappedFile* GetFile(Addr address);
|
||||
|
||||
// Creates a new entry on a block file. block_type indicates the size of block
|
||||
// to be used (as defined on cache_addr.h), block_count is the number of
|
||||
// blocks to allocate, and block_address is the address of the new entry.
|
||||
bool CreateBlock(FileType block_type, int block_count, Addr* block_address);
|
||||
|
||||
// Removes an entry from the block files. If deep is true, the storage is zero
|
||||
// filled; otherwise the entry is removed but the data is not altered (must be
|
||||
// already zeroed).
|
||||
void DeleteBlock(Addr address, bool deep);
|
||||
|
||||
// Close all the files and set the internal state to be initializad again. The
|
||||
// cache is being purged.
|
||||
void CloseFiles();
|
||||
|
||||
// Sends UMA stats.
|
||||
void ReportStats();
|
||||
|
||||
// Returns true if the blocks pointed by a given address are currently used.
|
||||
// This method is only intended for debugging.
|
||||
bool IsValid(Addr address);
|
||||
|
||||
private:
|
||||
// Set force to true to overwrite the file if it exists.
|
||||
bool CreateBlockFile(int index, FileType file_type, bool force);
|
||||
bool OpenBlockFile(int index);
|
||||
|
||||
// Attemp to grow this file. Fails if the file cannot be extended anymore.
|
||||
bool GrowBlockFile(MappedFile* file, BlockFileHeader* header);
|
||||
|
||||
// Returns the appropriate file to use for a new block.
|
||||
MappedFile* FileForNewBlock(FileType block_type, int block_count);
|
||||
|
||||
// Returns the next block file on this chain, creating new files if needed.
|
||||
MappedFile* NextFile(MappedFile* file);
|
||||
|
||||
// Creates an empty block file and returns its index.
|
||||
int16_t CreateNextBlockFile(FileType block_type);
|
||||
|
||||
// Removes a chained block file that is now empty.
|
||||
bool RemoveEmptyFile(FileType block_type);
|
||||
|
||||
// Restores the header of a potentially inconsistent file.
|
||||
bool FixBlockFileHeader(MappedFile* file);
|
||||
|
||||
// Retrieves stats for the given file index.
|
||||
void GetFileStats(int index, int* used_count, int* load);
|
||||
|
||||
// Returns the filename for a given file index.
|
||||
base::FilePath Name(int index);
|
||||
|
||||
bool init_;
|
||||
char* zero_buffer_; // Buffer to speed-up cleaning deleted entries.
|
||||
base::FilePath path_; // Path to the backing folder.
|
||||
std::vector<scoped_refptr<MappedFile>> block_files_; // The actual files.
|
||||
std::unique_ptr<base::ThreadChecker> thread_checker_;
|
||||
|
||||
FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_ZeroSizeFile);
|
||||
FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_TruncatedFile);
|
||||
FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_InvalidFile);
|
||||
FRIEND_TEST_ALL_PREFIXES(DiskCacheTest, BlockFiles_Stats);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BlockFiles);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_BLOCK_FILES_H_
|
||||
15
net/disk_cache/blockfile/disk_format.cc
Normal file
15
net/disk_cache/blockfile/disk_format.cc
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/disk_format.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
IndexHeader::IndexHeader() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
magic = kIndexMagic;
|
||||
version = kCurrentVersion;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
156
net/disk_cache/blockfile/disk_format.h
Normal file
156
net/disk_cache/blockfile/disk_format.h
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// The cache is stored on disk as a collection of block-files, plus an index
|
||||
// file plus a collection of external files.
|
||||
//
|
||||
// Any data blob bigger than kMaxBlockSize (disk_cache/addr.h) will be stored in
|
||||
// a separate file named f_xxx where x is a hexadecimal number. Shorter data
|
||||
// will be stored as a series of blocks on a block-file. In any case, CacheAddr
|
||||
// represents the address of the data inside the cache.
|
||||
//
|
||||
// The index file is just a simple hash table that maps a particular entry to
|
||||
// a CacheAddr value. Linking for a given hash bucket is handled internally
|
||||
// by the cache entry.
|
||||
//
|
||||
// The last element of the cache is the block-file. A block file is a file
|
||||
// designed to store blocks of data of a given size. For more details see
|
||||
// disk_cache/disk_format_base.h
|
||||
//
|
||||
// A new cache is initialized with four block files (named data_0 through
|
||||
// data_3), each one dedicated to store blocks of a given size. The number at
|
||||
// the end of the file name is the block file number (in decimal).
|
||||
//
|
||||
// There are two "special" types of blocks: an entry and a rankings node. An
|
||||
// entry keeps track of all the information related to the same cache entry,
|
||||
// such as the key, hash value, data pointers etc. A rankings node keeps track
|
||||
// of the information that is updated frequently for a given entry, such as its
|
||||
// location on the LRU lists, last access time etc.
|
||||
//
|
||||
// The files that store internal information for the cache (blocks and index)
|
||||
// are at least partially memory mapped. They have a location that is signaled
|
||||
// every time the internal structures are modified, so it is possible to detect
|
||||
// (most of the time) when the process dies in the middle of an update.
|
||||
//
|
||||
// In order to prevent dirty data to be used as valid (after a crash), every
|
||||
// cache entry has a dirty identifier. Each running instance of the cache keeps
|
||||
// a separate identifier (maintained on the "this_id" header field) that is used
|
||||
// to mark every entry that is created or modified. When the entry is closed,
|
||||
// and all the data can be trusted, the dirty flag is cleared from the entry.
|
||||
// When the cache encounters an entry whose identifier is different than the one
|
||||
// being currently used, it means that the entry was not properly closed on a
|
||||
// previous run, so it is discarded.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_DISK_FORMAT_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_DISK_FORMAT_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/disk_format_base.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
const int kIndexTablesize = 0x10000;
|
||||
const uint32_t kIndexMagic = 0xC103CAC3;
|
||||
const uint32_t kCurrentVersion = 0x20000; // Version 2.0.
|
||||
|
||||
struct LruData {
|
||||
int32_t pad1[2];
|
||||
int32_t filled; // Flag to tell when we filled the cache.
|
||||
int32_t sizes[5];
|
||||
CacheAddr heads[5];
|
||||
CacheAddr tails[5];
|
||||
CacheAddr transaction; // In-flight operation target.
|
||||
int32_t operation; // Actual in-flight operation.
|
||||
int32_t operation_list; // In-flight operation list.
|
||||
int32_t pad2[7];
|
||||
};
|
||||
|
||||
// Header for the master index file.
|
||||
struct NET_EXPORT_PRIVATE IndexHeader {
|
||||
IndexHeader();
|
||||
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
int32_t num_entries; // Number of entries currently stored.
|
||||
int32_t num_bytes; // Total size of the stored data.
|
||||
int32_t last_file; // Last external file created.
|
||||
int32_t this_id; // Id for all entries being changed (dirty flag).
|
||||
CacheAddr stats; // Storage for usage data.
|
||||
int32_t table_len; // Actual size of the table (0 == kIndexTablesize).
|
||||
int32_t crash; // Signals a previous crash.
|
||||
int32_t experiment; // Id of an ongoing test.
|
||||
uint64_t create_time; // Creation time for this set of files.
|
||||
int32_t pad[52];
|
||||
LruData lru; // Eviction control data.
|
||||
};
|
||||
|
||||
// The structure of the whole index file.
|
||||
struct Index {
|
||||
IndexHeader header;
|
||||
CacheAddr table[kIndexTablesize]; // Default size. Actual size controlled
|
||||
// by header.table_len.
|
||||
};
|
||||
|
||||
// Main structure for an entry on the backing storage. If the key is longer than
|
||||
// what can be stored on this structure, it will be extended on consecutive
|
||||
// blocks (adding 256 bytes each time), up to 4 blocks (1024 - 32 - 1 chars).
|
||||
// After that point, the whole key will be stored as a data block or external
|
||||
// file.
|
||||
struct EntryStore {
|
||||
uint32_t hash; // Full hash of the key.
|
||||
CacheAddr next; // Next entry with the same hash or bucket.
|
||||
CacheAddr rankings_node; // Rankings node for this entry.
|
||||
int32_t reuse_count; // How often is this entry used.
|
||||
int32_t refetch_count; // How often is this fetched from the net.
|
||||
int32_t state; // Current state.
|
||||
uint64_t creation_time;
|
||||
int32_t key_len;
|
||||
CacheAddr long_key; // Optional address of a long key.
|
||||
int32_t data_size[4]; // We can store up to 4 data streams for each
|
||||
CacheAddr data_addr[4]; // entry.
|
||||
uint32_t flags; // Any combination of EntryFlags.
|
||||
int32_t pad[4];
|
||||
uint32_t self_hash; // The hash of EntryStore up to this point.
|
||||
char key[256 - 24 * 4]; // null terminated
|
||||
};
|
||||
|
||||
static_assert(sizeof(EntryStore) == 256, "bad EntryStore");
|
||||
const int kMaxInternalKeyLength = 4 * sizeof(EntryStore) -
|
||||
offsetof(EntryStore, key) - 1;
|
||||
|
||||
// Possible states for a given entry.
|
||||
enum EntryState {
|
||||
ENTRY_NORMAL = 0,
|
||||
ENTRY_EVICTED, // The entry was recently evicted from the cache.
|
||||
ENTRY_DOOMED // The entry was doomed.
|
||||
};
|
||||
|
||||
// Flags that can be applied to an entry.
|
||||
enum EntryFlags {
|
||||
PARENT_ENTRY = 1, // This entry has children (sparse) entries.
|
||||
CHILD_ENTRY = 1 << 1 // Child entry that stores sparse data.
|
||||
};
|
||||
|
||||
#pragma pack(push, 4)
|
||||
// Rankings information for a given entry.
|
||||
struct RankingsNode {
|
||||
uint64_t last_used; // LRU info.
|
||||
uint64_t last_modified; // LRU info.
|
||||
CacheAddr next; // LRU list.
|
||||
CacheAddr prev; // LRU list.
|
||||
CacheAddr contents; // Address of the EntryStore.
|
||||
int32_t dirty; // The entry is being modifyied.
|
||||
uint32_t self_hash; // RankingsNode's hash.
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(RankingsNode) == 36, "bad RankingsNode");
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_DISK_FORMAT_H_
|
||||
131
net/disk_cache/blockfile/disk_format_base.h
Normal file
131
net/disk_cache/blockfile/disk_format_base.h
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// For a general description of the files used by the cache see file_format.h.
|
||||
//
|
||||
// A block file is a file designed to store blocks of data of a given size. It
|
||||
// is able to store data that spans from one to four consecutive "blocks", and
|
||||
// it grows as needed to store up to approximately 65000 blocks. It has a fixed
|
||||
// size header used for book keeping such as tracking free of blocks on the
|
||||
// file. For example, a block-file for 1KB blocks will grow from 8KB when
|
||||
// totally empty to about 64MB when completely full. At that point, data blocks
|
||||
// of 1KB will be stored on a second block file that will store the next set of
|
||||
// 65000 blocks. The first file contains the number of the second file, and the
|
||||
// second file contains the number of a third file, created when the second file
|
||||
// reaches its limit. It is important to remember that no matter how long the
|
||||
// chain of files is, any given block can be located directly by its address,
|
||||
// which contains the file number and starting block inside the file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_DISK_FORMAT_BASE_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_DISK_FORMAT_BASE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
typedef uint32_t CacheAddr;
|
||||
|
||||
const uint32_t kBlockVersion2 = 0x20000; // Version 2.0.
|
||||
const uint32_t kBlockCurrentVersion = 0x30000; // Version 3.0.
|
||||
|
||||
const uint32_t kBlockMagic = 0xC104CAC3;
|
||||
const int kBlockHeaderSize = 8192; // Two pages: almost 64k entries
|
||||
const int kMaxBlocks = (kBlockHeaderSize - 80) * 8;
|
||||
const int kNumExtraBlocks = 1024; // How fast files grow.
|
||||
|
||||
// Bitmap to track used blocks on a block-file.
|
||||
typedef uint32_t AllocBitmap[kMaxBlocks / 32];
|
||||
|
||||
// A block-file is the file used to store information in blocks (could be
|
||||
// EntryStore blocks, RankingsNode blocks or user-data blocks).
|
||||
// We store entries that can expand for up to 4 consecutive blocks, and keep
|
||||
// counters of the number of blocks available for each type of entry. For
|
||||
// instance, an entry of 3 blocks is an entry of type 3. We also keep track of
|
||||
// where did we find the last entry of that type (to avoid searching the bitmap
|
||||
// from the beginning every time).
|
||||
// This Structure is the header of a block-file:
|
||||
struct BlockFileHeader {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
int16_t this_file; // Index of this file.
|
||||
int16_t next_file; // Next file when this one is full.
|
||||
int32_t entry_size; // Size of the blocks of this file.
|
||||
int32_t num_entries; // Number of stored entries.
|
||||
int32_t max_entries; // Current maximum number of entries.
|
||||
int32_t empty[4]; // Counters of empty entries for each type.
|
||||
int32_t hints[4]; // Last used position for each entry type.
|
||||
volatile int32_t updating; // Keep track of updates to the header.
|
||||
int32_t user[5];
|
||||
AllocBitmap allocation_map;
|
||||
};
|
||||
|
||||
static_assert(sizeof(BlockFileHeader) == kBlockHeaderSize, "bad header");
|
||||
|
||||
// Sparse data support:
|
||||
// We keep a two level hierarchy to enable sparse data for an entry: the first
|
||||
// level consists of using separate "child" entries to store ranges of 1 MB,
|
||||
// and the second level stores blocks of 1 KB inside each child entry.
|
||||
//
|
||||
// Whenever we need to access a particular sparse offset, we first locate the
|
||||
// child entry that stores that offset, so we discard the 20 least significant
|
||||
// bits of the offset, and end up with the child id. For instance, the child id
|
||||
// to store the first megabyte is 0, and the child that should store offset
|
||||
// 0x410000 has an id of 4.
|
||||
//
|
||||
// The child entry is stored the same way as any other entry, so it also has a
|
||||
// name (key). The key includes a signature to be able to identify children
|
||||
// created for different generations of the same resource. In other words, given
|
||||
// that a given sparse entry can have a large number of child entries, and the
|
||||
// resource can be invalidated and replaced with a new version at any time, it
|
||||
// is important to be sure that a given child actually belongs to certain entry.
|
||||
//
|
||||
// The full name of a child entry is composed with a prefix ("Range_"), and two
|
||||
// hexadecimal 64-bit numbers at the end, separated by semicolons. The first
|
||||
// number is the signature of the parent key, and the second number is the child
|
||||
// id as described previously. The signature itself is also stored internally by
|
||||
// the child and the parent entries. For example, a sparse entry with a key of
|
||||
// "sparse entry name", and a signature of 0x052AF76, may have a child entry
|
||||
// named "Range_sparse entry name:052af76:4", which stores data in the range
|
||||
// 0x400000 to 0x4FFFFF.
|
||||
//
|
||||
// Each child entry keeps track of all the 1 KB blocks that have been written
|
||||
// to the entry, but being a regular entry, it will happily return zeros for any
|
||||
// read that spans data not written before. The actual sparse data is stored in
|
||||
// one of the data streams of the child entry (at index 1), while the control
|
||||
// information is stored in another stream (at index 2), both by parents and
|
||||
// the children.
|
||||
|
||||
// This structure contains the control information for parent and child entries.
|
||||
// It is stored at offset 0 of the data stream with index 2.
|
||||
// It is possible to write to a child entry in a way that causes the last block
|
||||
// to be only partialy filled. In that case, last_block and last_block_len will
|
||||
// keep track of that block.
|
||||
struct SparseHeader {
|
||||
int64_t signature; // The parent and children signature.
|
||||
uint32_t magic; // Structure identifier (equal to kIndexMagic).
|
||||
int32_t parent_key_len; // Key length for the parent entry.
|
||||
int32_t last_block; // Index of the last written block.
|
||||
int32_t last_block_len; // Length of the last written block.
|
||||
int32_t dummy[10];
|
||||
};
|
||||
|
||||
// The SparseHeader will be followed by a bitmap, as described by this
|
||||
// structure.
|
||||
struct SparseData {
|
||||
SparseHeader header;
|
||||
uint32_t bitmap[32]; // Bitmap representation of known children (if this
|
||||
// is a parent entry), or used blocks (for child
|
||||
// entries. The size is fixed for child entries but
|
||||
// not for parents; it can be as small as 4 bytes
|
||||
// and as large as 8 KB.
|
||||
};
|
||||
|
||||
// The number of blocks stored by a child entry.
|
||||
const int kNumSparseBits = 1024;
|
||||
static_assert(sizeof(SparseData) == sizeof(SparseHeader) + kNumSparseBits / 8,
|
||||
"invalid SparseData bitmap");
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_DISK_FORMAT_BASE_H_
|
||||
1569
net/disk_cache/blockfile/entry_impl.cc
Normal file
1569
net/disk_cache/blockfile/entry_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
304
net/disk_cache/blockfile/entry_impl.h
Normal file
304
net/disk_cache/blockfile/entry_impl.h
Normal file
@@ -0,0 +1,304 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_ENTRY_IMPL_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_ENTRY_IMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/disk_format.h"
|
||||
#include "net/disk_cache/blockfile/storage_block-inl.h"
|
||||
#include "net/disk_cache/blockfile/storage_block.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/log/net_log_with_source.h"
|
||||
|
||||
namespace net {
|
||||
class NetLog;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class BackendImpl;
|
||||
class InFlightBackendIO;
|
||||
class SparseControl;
|
||||
typedef StorageBlock<EntryStore> CacheEntryBlock;
|
||||
typedef StorageBlock<RankingsNode> CacheRankingsBlock;
|
||||
|
||||
// This class implements the Entry interface. An object of this
|
||||
// class represents a single entry on the cache.
|
||||
class NET_EXPORT_PRIVATE EntryImpl
|
||||
: public Entry,
|
||||
public base::RefCounted<EntryImpl> {
|
||||
friend class base::RefCounted<EntryImpl>;
|
||||
friend class SparseControl;
|
||||
public:
|
||||
enum Operation {
|
||||
kRead,
|
||||
kWrite,
|
||||
kSparseRead,
|
||||
kSparseWrite,
|
||||
kAsyncIO,
|
||||
kReadAsync1,
|
||||
kWriteAsync1
|
||||
};
|
||||
|
||||
EntryImpl(BackendImpl* backend, Addr address, bool read_only);
|
||||
|
||||
// Background implementation of the Entry interface.
|
||||
void DoomImpl();
|
||||
int ReadDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
const CompletionCallback& callback);
|
||||
int WriteDataImpl(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
const CompletionCallback& callback, bool truncate);
|
||||
int ReadSparseDataImpl(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback);
|
||||
int WriteSparseDataImpl(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback);
|
||||
int GetAvailableRangeImpl(int64_t offset, int len, int64_t* start);
|
||||
void CancelSparseIOImpl();
|
||||
int ReadyForSparseIOImpl(const CompletionCallback& callback);
|
||||
|
||||
inline CacheEntryBlock* entry() {
|
||||
return &entry_;
|
||||
}
|
||||
|
||||
inline CacheRankingsBlock* rankings() {
|
||||
return &node_;
|
||||
}
|
||||
|
||||
uint32_t GetHash();
|
||||
|
||||
// Performs the initialization of a EntryImpl that will be added to the
|
||||
// cache.
|
||||
bool CreateEntry(Addr node_address, const std::string& key, uint32_t hash);
|
||||
|
||||
// Returns true if this entry matches the lookup arguments.
|
||||
bool IsSameEntry(const std::string& key, uint32_t hash);
|
||||
|
||||
// Permamently destroys this entry.
|
||||
void InternalDoom();
|
||||
|
||||
// Deletes this entry from disk. If |everything| is false, only the user data
|
||||
// will be removed, leaving the key and control data intact.
|
||||
void DeleteEntryData(bool everything);
|
||||
|
||||
// Returns the address of the next entry on the list of entries with the same
|
||||
// hash.
|
||||
CacheAddr GetNextAddress();
|
||||
|
||||
// Sets the address of the next entry on the list of entries with the same
|
||||
// hash.
|
||||
void SetNextAddress(Addr address);
|
||||
|
||||
// Reloads the rankings node information.
|
||||
bool LoadNodeAddress();
|
||||
|
||||
// Updates the stored data to reflect the run-time information for this entry.
|
||||
// Returns false if the data could not be updated. The purpose of this method
|
||||
// is to be able to detect entries that are currently in use.
|
||||
bool Update();
|
||||
|
||||
bool dirty() {
|
||||
return dirty_;
|
||||
}
|
||||
|
||||
bool doomed() {
|
||||
return doomed_;
|
||||
}
|
||||
|
||||
// Marks this entry as dirty (in memory) if needed. This is intended only for
|
||||
// entries that are being read from disk, to be called during loading.
|
||||
void SetDirtyFlag(int32_t current_id);
|
||||
|
||||
// Fixes this entry so it can be treated as valid (to delete it).
|
||||
void SetPointerForInvalidEntry(int32_t new_id);
|
||||
|
||||
// Returns true if this entry is so meesed up that not everything is going to
|
||||
// be removed.
|
||||
bool LeaveRankingsBehind();
|
||||
|
||||
// Returns false if the entry is clearly invalid.
|
||||
bool SanityCheck();
|
||||
bool DataSanityCheck();
|
||||
|
||||
// Attempts to make this entry reachable though the key.
|
||||
void FixForDelete();
|
||||
|
||||
// Handle the pending asynchronous IO count.
|
||||
void IncrementIoCount();
|
||||
void DecrementIoCount();
|
||||
|
||||
// This entry is being returned to the user. It is always called from the
|
||||
// primary thread (not the dedicated cache thread).
|
||||
void OnEntryCreated(BackendImpl* backend);
|
||||
|
||||
// Set the access times for this entry. This method provides support for
|
||||
// the upgrade tool.
|
||||
void SetTimes(base::Time last_used, base::Time last_modified);
|
||||
|
||||
// Generates a histogram for the time spent working on this operation.
|
||||
void ReportIOTime(Operation op, const base::TimeTicks& start);
|
||||
|
||||
// Logs a begin event and enables logging for the EntryImpl. Will also cause
|
||||
// an end event to be logged on destruction. The EntryImpl must have its key
|
||||
// initialized before this is called. |created| is true if the Entry was
|
||||
// created rather than opened.
|
||||
void BeginLogging(net::NetLog* net_log, bool created);
|
||||
|
||||
const net::NetLogWithSource& net_log() const;
|
||||
|
||||
// Returns the number of blocks needed to store an EntryStore.
|
||||
static int NumBlocksForEntry(int key_size);
|
||||
|
||||
// Entry interface.
|
||||
void Doom() override;
|
||||
void Close() override;
|
||||
std::string GetKey() const override;
|
||||
base::Time GetLastUsed() const override;
|
||||
base::Time GetLastModified() const override;
|
||||
int32_t GetDataSize(int index) const override;
|
||||
int ReadData(int index,
|
||||
int offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int WriteData(int index,
|
||||
int offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback,
|
||||
bool truncate) override;
|
||||
int ReadSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int WriteSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int GetAvailableRange(int64_t offset,
|
||||
int len,
|
||||
int64_t* start,
|
||||
const CompletionCallback& callback) override;
|
||||
bool CouldBeSparse() const override;
|
||||
void CancelSparseIO() override;
|
||||
int ReadyForSparseIO(const CompletionCallback& callback) override;
|
||||
|
||||
private:
|
||||
enum {
|
||||
kNumStreams = 3
|
||||
};
|
||||
class UserBuffer;
|
||||
|
||||
~EntryImpl() override;
|
||||
|
||||
// Do all the work for ReadDataImpl and WriteDataImpl. Implemented as
|
||||
// separate functions to make logging of results simpler.
|
||||
int InternalReadData(int index, int offset, IOBuffer* buf,
|
||||
int buf_len, const CompletionCallback& callback);
|
||||
int InternalWriteData(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
const CompletionCallback& callback, bool truncate);
|
||||
|
||||
// Initializes the storage for an internal or external data block.
|
||||
bool CreateDataBlock(int index, int size);
|
||||
|
||||
// Initializes the storage for an internal or external generic block.
|
||||
bool CreateBlock(int size, Addr* address);
|
||||
|
||||
// Deletes the data pointed by address, maybe backed by files_[index].
|
||||
// Note that most likely the caller should delete (and store) the reference to
|
||||
// |address| *before* calling this method because we don't want to have an
|
||||
// entry using an address that is already free.
|
||||
void DeleteData(Addr address, int index);
|
||||
|
||||
// Updates ranking information.
|
||||
void UpdateRank(bool modified);
|
||||
|
||||
// Returns a pointer to the file that stores the given address.
|
||||
File* GetBackingFile(Addr address, int index);
|
||||
|
||||
// Returns a pointer to the file that stores external data.
|
||||
File* GetExternalFile(Addr address, int index);
|
||||
|
||||
// Prepares the target file or buffer for a write of buf_len bytes at the
|
||||
// given offset.
|
||||
bool PrepareTarget(int index, int offset, int buf_len, bool truncate);
|
||||
|
||||
// Adjusts the internal buffer and file handle for a write that truncates this
|
||||
// stream.
|
||||
bool HandleTruncation(int index, int offset, int buf_len);
|
||||
|
||||
// Copies data from disk to the internal buffer.
|
||||
bool CopyToLocalBuffer(int index);
|
||||
|
||||
// Reads from a block data file to this object's memory buffer.
|
||||
bool MoveToLocalBuffer(int index);
|
||||
|
||||
// Loads the external file to this object's memory buffer.
|
||||
bool ImportSeparateFile(int index, int new_size);
|
||||
|
||||
// Makes sure that the internal buffer can handle the a write of |buf_len|
|
||||
// bytes to |offset|.
|
||||
bool PrepareBuffer(int index, int offset, int buf_len);
|
||||
|
||||
// Flushes the in-memory data to the backing storage. The data destination
|
||||
// is determined based on the current data length and |min_len|.
|
||||
bool Flush(int index, int min_len);
|
||||
|
||||
// Updates the size of a given data stream.
|
||||
void UpdateSize(int index, int old_size, int new_size);
|
||||
|
||||
// Initializes the sparse control object. Returns a net error code.
|
||||
int InitSparseData();
|
||||
|
||||
// Adds the provided |flags| to the current EntryFlags for this entry.
|
||||
void SetEntryFlags(uint32_t flags);
|
||||
|
||||
// Returns the current EntryFlags for this entry.
|
||||
uint32_t GetEntryFlags();
|
||||
|
||||
// Gets the data stored at the given index. If the information is in memory,
|
||||
// a buffer will be allocated and the data will be copied to it (the caller
|
||||
// can find out the size of the buffer before making this call). Otherwise,
|
||||
// the cache address of the data will be returned, and that address will be
|
||||
// removed from the regular book keeping of this entry so the caller is
|
||||
// responsible for deleting the block (or file) from the backing store at some
|
||||
// point; there is no need to report any storage-size change, only to do the
|
||||
// actual cleanup.
|
||||
void GetData(int index, char** buffer, Addr* address);
|
||||
|
||||
// Logs this entry to the internal trace buffer.
|
||||
void Log(const char* msg);
|
||||
|
||||
CacheEntryBlock entry_; // Key related information for this entry.
|
||||
CacheRankingsBlock node_; // Rankings related information for this entry.
|
||||
base::WeakPtr<BackendImpl> backend_; // Back pointer to the cache.
|
||||
base::WeakPtr<InFlightBackendIO> background_queue_; // In-progress queue.
|
||||
std::unique_ptr<UserBuffer> user_buffers_[kNumStreams]; // Stores user data.
|
||||
// Files to store external user data and key.
|
||||
scoped_refptr<File> files_[kNumStreams + 1];
|
||||
mutable std::string key_; // Copy of the key.
|
||||
int unreported_size_[kNumStreams]; // Bytes not reported yet to the backend.
|
||||
bool doomed_; // True if this entry was removed from the cache.
|
||||
bool read_only_; // True if not yet writing.
|
||||
bool dirty_; // True if we detected that this is a dirty entry.
|
||||
std::unique_ptr<SparseControl> sparse_; // Support for sparse entries.
|
||||
|
||||
net::NetLogWithSource net_log_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(EntryImpl);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_ENTRY_IMPL_H_
|
||||
33
net/disk_cache/blockfile/errors.h
Normal file
33
net/disk_cache/blockfile/errors.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Error codes reported by self tests or to UMA.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_ERRORS_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_ERRORS_H_
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
enum {
|
||||
ERR_NO_ERROR = 0,
|
||||
ERR_INIT_FAILED = -1,
|
||||
ERR_INVALID_TAIL = -2,
|
||||
ERR_INVALID_HEAD = -3,
|
||||
ERR_INVALID_PREV = -4,
|
||||
ERR_INVALID_NEXT = -5,
|
||||
ERR_INVALID_ENTRY = -6,
|
||||
ERR_INVALID_ADDRESS = -7,
|
||||
ERR_INVALID_LINKS = -8,
|
||||
ERR_NUM_ENTRIES_MISMATCH = -9,
|
||||
ERR_READ_FAILURE = -10,
|
||||
ERR_PREVIOUS_CRASH = -11,
|
||||
ERR_STORAGE_ERROR = -12,
|
||||
ERR_INVALID_MASK = -13,
|
||||
ERR_CACHE_DOOMED = -14, // Not really an error condition.
|
||||
ERR_CACHE_CREATED = -15 // Not really an error condition.
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_ERRORS_H_
|
||||
603
net/disk_cache/blockfile/eviction.cc
Normal file
603
net/disk_cache/blockfile/eviction.cc
Normal file
@@ -0,0 +1,603 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// The eviction policy is a very simple pure LRU, so the elements at the end of
|
||||
// the list are evicted until kCleanUpMargin free space is available. There is
|
||||
// only one list in use (Rankings::NO_USE), and elements are sent to the front
|
||||
// of the list whenever they are accessed.
|
||||
|
||||
// The new (in-development) eviction policy adds re-use as a factor to evict
|
||||
// an entry. The story so far:
|
||||
|
||||
// Entries are linked on separate lists depending on how often they are used.
|
||||
// When we see an element for the first time, it goes to the NO_USE list; if
|
||||
// the object is reused later on, we move it to the LOW_USE list, until it is
|
||||
// used kHighUse times, at which point it is moved to the HIGH_USE list.
|
||||
// Whenever an element is evicted, we move it to the DELETED list so that if the
|
||||
// element is accessed again, we remember the fact that it was already stored
|
||||
// and maybe in the future we don't evict that element.
|
||||
|
||||
// When we have to evict an element, first we try to use the last element from
|
||||
// the NO_USE list, then we move to the LOW_USE and only then we evict an entry
|
||||
// from the HIGH_USE. We attempt to keep entries on the cache for at least
|
||||
// kTargetTime hours (with frequently accessed items stored for longer periods),
|
||||
// but if we cannot do that, we fall-back to keep each list roughly the same
|
||||
// size so that we have a chance to see an element again and move it to another
|
||||
// list.
|
||||
|
||||
#include "net/disk_cache/blockfile/eviction.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/location.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/disk_cache/blockfile/backend_impl.h"
|
||||
#include "net/disk_cache/blockfile/disk_format.h"
|
||||
#include "net/disk_cache/blockfile/entry_impl.h"
|
||||
#include "net/disk_cache/blockfile/experiments.h"
|
||||
#include "net/disk_cache/blockfile/histogram_macros.h"
|
||||
#include "net/disk_cache/blockfile/trace.h"
|
||||
#include "net/disk_cache/blockfile/webfonts_histogram.h"
|
||||
|
||||
// Provide a BackendImpl object to macros from histogram_macros.h.
|
||||
#define CACHE_UMA_BACKEND_IMPL_OBJ backend_
|
||||
|
||||
using base::Time;
|
||||
using base::TimeTicks;
|
||||
|
||||
namespace {
|
||||
|
||||
const int kCleanUpMargin = 1024 * 1024;
|
||||
const int kHighUse = 10; // Reuse count to be on the HIGH_USE list.
|
||||
const int kTargetTime = 24 * 7; // Time to be evicted (hours since last use).
|
||||
const int kMaxDelayedTrims = 60;
|
||||
|
||||
int LowWaterAdjust(int high_water) {
|
||||
if (high_water < kCleanUpMargin)
|
||||
return 0;
|
||||
|
||||
return high_water - kCleanUpMargin;
|
||||
}
|
||||
|
||||
bool FallingBehind(int current_size, int max_size) {
|
||||
return current_size > max_size - kCleanUpMargin * 20;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// The real initialization happens during Init(), init_ is the only member that
|
||||
// has to be initialized here.
|
||||
Eviction::Eviction()
|
||||
: backend_(NULL),
|
||||
init_(false),
|
||||
ptr_factory_(this) {
|
||||
}
|
||||
|
||||
Eviction::~Eviction() = default;
|
||||
|
||||
void Eviction::Init(BackendImpl* backend) {
|
||||
// We grab a bunch of info from the backend to make the code a little cleaner
|
||||
// when we're actually doing work.
|
||||
backend_ = backend;
|
||||
rankings_ = &backend->rankings_;
|
||||
header_ = &backend_->data_->header;
|
||||
max_size_ = LowWaterAdjust(backend_->max_size_);
|
||||
index_size_ = backend->mask_ + 1;
|
||||
new_eviction_ = backend->new_eviction_;
|
||||
first_trim_ = true;
|
||||
trimming_ = false;
|
||||
delay_trim_ = false;
|
||||
trim_delays_ = 0;
|
||||
init_ = true;
|
||||
test_mode_ = false;
|
||||
}
|
||||
|
||||
void Eviction::Stop() {
|
||||
// It is possible for the backend initialization to fail, in which case this
|
||||
// object was never initialized... and there is nothing to do.
|
||||
if (!init_)
|
||||
return;
|
||||
|
||||
// We want to stop further evictions, so let's pretend that we are busy from
|
||||
// this point on.
|
||||
DCHECK(!trimming_);
|
||||
trimming_ = true;
|
||||
ptr_factory_.InvalidateWeakPtrs();
|
||||
}
|
||||
|
||||
void Eviction::TrimCache(bool empty) {
|
||||
if (backend_->disabled_ || trimming_)
|
||||
return;
|
||||
|
||||
if (!empty && !ShouldTrim())
|
||||
return PostDelayedTrim();
|
||||
|
||||
if (new_eviction_)
|
||||
return TrimCacheV2(empty);
|
||||
|
||||
Trace("*** Trim Cache ***");
|
||||
trimming_ = true;
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
Rankings::ScopedRankingsBlock node(rankings_);
|
||||
Rankings::ScopedRankingsBlock next(
|
||||
rankings_, rankings_->GetPrev(node.get(), Rankings::NO_USE));
|
||||
int deleted_entries = 0;
|
||||
int target_size = empty ? 0 : max_size_;
|
||||
while ((header_->num_bytes > target_size || test_mode_) && next.get()) {
|
||||
// The iterator could be invalidated within EvictEntry().
|
||||
if (!next->HasData())
|
||||
break;
|
||||
node.reset(next.release());
|
||||
next.reset(rankings_->GetPrev(node.get(), Rankings::NO_USE));
|
||||
if (node->Data()->dirty != backend_->GetCurrentEntryId() || empty) {
|
||||
// This entry is not being used by anybody.
|
||||
// Do NOT use node as an iterator after this point.
|
||||
rankings_->TrackRankingsBlock(node.get(), false);
|
||||
if (EvictEntry(node.get(), empty, Rankings::NO_USE) && !test_mode_)
|
||||
deleted_entries++;
|
||||
|
||||
if (!empty && test_mode_)
|
||||
break;
|
||||
}
|
||||
if (!empty && (deleted_entries > 20 ||
|
||||
(TimeTicks::Now() - start).InMilliseconds() > 20)) {
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&Eviction::TrimCache, ptr_factory_.GetWeakPtr(), false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
CACHE_UMA(AGE_MS, "TotalClearTimeV1", 0, start);
|
||||
} else {
|
||||
CACHE_UMA(AGE_MS, "TotalTrimTimeV1", 0, start);
|
||||
}
|
||||
CACHE_UMA(COUNTS, "TrimItemsV1", 0, deleted_entries);
|
||||
|
||||
trimming_ = false;
|
||||
Trace("*** Trim Cache end ***");
|
||||
return;
|
||||
}
|
||||
|
||||
void Eviction::UpdateRank(EntryImpl* entry, bool modified) {
|
||||
if (new_eviction_)
|
||||
return UpdateRankV2(entry, modified);
|
||||
|
||||
rankings_->UpdateRank(entry->rankings(), modified, GetListForEntry(entry));
|
||||
}
|
||||
|
||||
void Eviction::OnOpenEntry(EntryImpl* entry) {
|
||||
if (new_eviction_)
|
||||
return OnOpenEntryV2(entry);
|
||||
}
|
||||
|
||||
void Eviction::OnCreateEntry(EntryImpl* entry) {
|
||||
if (new_eviction_)
|
||||
return OnCreateEntryV2(entry);
|
||||
|
||||
rankings_->Insert(entry->rankings(), true, GetListForEntry(entry));
|
||||
}
|
||||
|
||||
void Eviction::OnDoomEntry(EntryImpl* entry) {
|
||||
if (new_eviction_)
|
||||
return OnDoomEntryV2(entry);
|
||||
|
||||
if (entry->LeaveRankingsBehind())
|
||||
return;
|
||||
|
||||
rankings_->Remove(entry->rankings(), GetListForEntry(entry), true);
|
||||
}
|
||||
|
||||
void Eviction::OnDestroyEntry(EntryImpl* entry) {
|
||||
if (new_eviction_)
|
||||
return OnDestroyEntryV2(entry);
|
||||
}
|
||||
|
||||
void Eviction::SetTestMode() {
|
||||
test_mode_ = true;
|
||||
}
|
||||
|
||||
void Eviction::TrimDeletedList(bool empty) {
|
||||
DCHECK(test_mode_ && new_eviction_);
|
||||
TrimDeleted(empty);
|
||||
}
|
||||
|
||||
void Eviction::PostDelayedTrim() {
|
||||
// Prevent posting multiple tasks.
|
||||
if (delay_trim_)
|
||||
return;
|
||||
delay_trim_ = true;
|
||||
trim_delays_++;
|
||||
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
|
||||
FROM_HERE, base::Bind(&Eviction::DelayedTrim, ptr_factory_.GetWeakPtr()),
|
||||
base::TimeDelta::FromMilliseconds(1000));
|
||||
}
|
||||
|
||||
void Eviction::DelayedTrim() {
|
||||
delay_trim_ = false;
|
||||
if (trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded())
|
||||
return PostDelayedTrim();
|
||||
|
||||
TrimCache(false);
|
||||
}
|
||||
|
||||
bool Eviction::ShouldTrim() {
|
||||
if (!FallingBehind(header_->num_bytes, max_size_) &&
|
||||
trim_delays_ < kMaxDelayedTrims && backend_->IsLoaded()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UMA_HISTOGRAM_COUNTS_1M("DiskCache.TrimDelays", trim_delays_);
|
||||
trim_delays_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Eviction::ShouldTrimDeleted() {
|
||||
int index_load = header_->num_entries * 100 / index_size_;
|
||||
|
||||
// If the index is not loaded, the deleted list will tend to double the size
|
||||
// of the other lists 3 lists (40% of the total). Otherwise, all lists will be
|
||||
// about the same size.
|
||||
int max_length = (index_load < 25) ? header_->num_entries * 2 / 5 :
|
||||
header_->num_entries / 4;
|
||||
return (!test_mode_ && header_->lru.sizes[Rankings::DELETED] > max_length);
|
||||
}
|
||||
|
||||
void Eviction::ReportTrimTimes(EntryImpl* entry) {
|
||||
if (first_trim_) {
|
||||
first_trim_ = false;
|
||||
if (backend_->ShouldReportAgain()) {
|
||||
CACHE_UMA(AGE, "TrimAge", 0, entry->GetLastUsed());
|
||||
ReportListStats();
|
||||
}
|
||||
|
||||
if (header_->lru.filled)
|
||||
return;
|
||||
|
||||
header_->lru.filled = 1;
|
||||
|
||||
if (header_->create_time) {
|
||||
// This is the first entry that we have to evict, generate some noise.
|
||||
backend_->FirstEviction();
|
||||
} else {
|
||||
// This is an old file, but we may want more reports from this user so
|
||||
// lets save some create_time. Conversion cannot fail here.
|
||||
const base::Time time_2009_3_1 =
|
||||
base::Time::FromInternalValue(12985574400000000);
|
||||
header_->create_time = time_2009_3_1.ToInternalValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rankings::List Eviction::GetListForEntry(EntryImpl* entry) {
|
||||
return Rankings::NO_USE;
|
||||
}
|
||||
|
||||
bool Eviction::EvictEntry(CacheRankingsBlock* node, bool empty,
|
||||
Rankings::List list) {
|
||||
scoped_refptr<EntryImpl> entry = backend_->GetEnumeratedEntry(node, list);
|
||||
if (!entry) {
|
||||
Trace("NewEntry failed on Trim 0x%x", node->address().value());
|
||||
return false;
|
||||
}
|
||||
|
||||
web_fonts_histogram::RecordEviction(entry.get());
|
||||
ReportTrimTimes(entry.get());
|
||||
if (empty || !new_eviction_) {
|
||||
entry->DoomImpl();
|
||||
} else {
|
||||
entry->DeleteEntryData(false);
|
||||
EntryStore* info = entry->entry()->Data();
|
||||
DCHECK_EQ(ENTRY_NORMAL, info->state);
|
||||
|
||||
rankings_->Remove(entry->rankings(), GetListForEntryV2(entry.get()), true);
|
||||
info->state = ENTRY_EVICTED;
|
||||
entry->entry()->Store();
|
||||
rankings_->Insert(entry->rankings(), true, Rankings::DELETED);
|
||||
}
|
||||
if (!empty)
|
||||
backend_->OnEvent(Stats::TRIM_ENTRY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void Eviction::TrimCacheV2(bool empty) {
|
||||
Trace("*** Trim Cache ***");
|
||||
trimming_ = true;
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
|
||||
const int kListsToSearch = 3;
|
||||
Rankings::ScopedRankingsBlock next[kListsToSearch];
|
||||
int list = Rankings::LAST_ELEMENT;
|
||||
|
||||
// Get a node from each list.
|
||||
bool done = false;
|
||||
for (int i = 0; i < kListsToSearch; i++) {
|
||||
next[i].set_rankings(rankings_);
|
||||
if (done)
|
||||
continue;
|
||||
next[i].reset(rankings_->GetPrev(NULL, static_cast<Rankings::List>(i)));
|
||||
if (!empty && NodeIsOldEnough(next[i].get(), i)) {
|
||||
list = static_cast<Rankings::List>(i);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we are not meeting the time targets lets move on to list length.
|
||||
if (!empty && Rankings::LAST_ELEMENT == list)
|
||||
list = SelectListByLength(next);
|
||||
|
||||
if (empty)
|
||||
list = 0;
|
||||
|
||||
Rankings::ScopedRankingsBlock node(rankings_);
|
||||
int deleted_entries = 0;
|
||||
int target_size = empty ? 0 : max_size_;
|
||||
|
||||
for (; list < kListsToSearch; list++) {
|
||||
while ((header_->num_bytes > target_size || test_mode_) &&
|
||||
next[list].get()) {
|
||||
// The iterator could be invalidated within EvictEntry().
|
||||
if (!next[list]->HasData())
|
||||
break;
|
||||
node.reset(next[list].release());
|
||||
next[list].reset(rankings_->GetPrev(node.get(),
|
||||
static_cast<Rankings::List>(list)));
|
||||
if (node->Data()->dirty != backend_->GetCurrentEntryId() || empty) {
|
||||
// This entry is not being used by anybody.
|
||||
// Do NOT use node as an iterator after this point.
|
||||
rankings_->TrackRankingsBlock(node.get(), false);
|
||||
if (EvictEntry(node.get(), empty, static_cast<Rankings::List>(list)))
|
||||
deleted_entries++;
|
||||
|
||||
if (!empty && test_mode_)
|
||||
break;
|
||||
}
|
||||
if (!empty && (deleted_entries > 20 ||
|
||||
(TimeTicks::Now() - start).InMilliseconds() > 20)) {
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&Eviction::TrimCache, ptr_factory_.GetWeakPtr(), false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty)
|
||||
list = kListsToSearch;
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
TrimDeleted(true);
|
||||
} else if (ShouldTrimDeleted()) {
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&Eviction::TrimDeleted, ptr_factory_.GetWeakPtr(), empty));
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
CACHE_UMA(AGE_MS, "TotalClearTimeV2", 0, start);
|
||||
} else {
|
||||
CACHE_UMA(AGE_MS, "TotalTrimTimeV2", 0, start);
|
||||
}
|
||||
CACHE_UMA(COUNTS, "TrimItemsV2", 0, deleted_entries);
|
||||
|
||||
Trace("*** Trim Cache end ***");
|
||||
trimming_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
void Eviction::UpdateRankV2(EntryImpl* entry, bool modified) {
|
||||
rankings_->UpdateRank(entry->rankings(), modified, GetListForEntryV2(entry));
|
||||
}
|
||||
|
||||
void Eviction::OnOpenEntryV2(EntryImpl* entry) {
|
||||
EntryStore* info = entry->entry()->Data();
|
||||
DCHECK_EQ(ENTRY_NORMAL, info->state);
|
||||
|
||||
if (info->reuse_count < std::numeric_limits<int32_t>::max()) {
|
||||
info->reuse_count++;
|
||||
entry->entry()->set_modified();
|
||||
|
||||
// We may need to move this to a new list.
|
||||
if (1 == info->reuse_count) {
|
||||
rankings_->Remove(entry->rankings(), Rankings::NO_USE, true);
|
||||
rankings_->Insert(entry->rankings(), false, Rankings::LOW_USE);
|
||||
entry->entry()->Store();
|
||||
} else if (kHighUse == info->reuse_count) {
|
||||
rankings_->Remove(entry->rankings(), Rankings::LOW_USE, true);
|
||||
rankings_->Insert(entry->rankings(), false, Rankings::HIGH_USE);
|
||||
entry->entry()->Store();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Eviction::OnCreateEntryV2(EntryImpl* entry) {
|
||||
EntryStore* info = entry->entry()->Data();
|
||||
switch (info->state) {
|
||||
case ENTRY_NORMAL: {
|
||||
DCHECK(!info->reuse_count);
|
||||
DCHECK(!info->refetch_count);
|
||||
break;
|
||||
};
|
||||
case ENTRY_EVICTED: {
|
||||
if (info->refetch_count < std::numeric_limits<int32_t>::max())
|
||||
info->refetch_count++;
|
||||
|
||||
if (info->refetch_count > kHighUse && info->reuse_count < kHighUse) {
|
||||
info->reuse_count = kHighUse;
|
||||
} else {
|
||||
info->reuse_count++;
|
||||
}
|
||||
info->state = ENTRY_NORMAL;
|
||||
entry->entry()->Store();
|
||||
rankings_->Remove(entry->rankings(), Rankings::DELETED, true);
|
||||
break;
|
||||
};
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
rankings_->Insert(entry->rankings(), true, GetListForEntryV2(entry));
|
||||
}
|
||||
|
||||
void Eviction::OnDoomEntryV2(EntryImpl* entry) {
|
||||
EntryStore* info = entry->entry()->Data();
|
||||
if (ENTRY_NORMAL != info->state)
|
||||
return;
|
||||
|
||||
if (entry->LeaveRankingsBehind()) {
|
||||
info->state = ENTRY_DOOMED;
|
||||
entry->entry()->Store();
|
||||
return;
|
||||
}
|
||||
|
||||
rankings_->Remove(entry->rankings(), GetListForEntryV2(entry), true);
|
||||
|
||||
info->state = ENTRY_DOOMED;
|
||||
entry->entry()->Store();
|
||||
rankings_->Insert(entry->rankings(), true, Rankings::DELETED);
|
||||
}
|
||||
|
||||
void Eviction::OnDestroyEntryV2(EntryImpl* entry) {
|
||||
if (entry->LeaveRankingsBehind())
|
||||
return;
|
||||
|
||||
rankings_->Remove(entry->rankings(), Rankings::DELETED, true);
|
||||
}
|
||||
|
||||
Rankings::List Eviction::GetListForEntryV2(EntryImpl* entry) {
|
||||
EntryStore* info = entry->entry()->Data();
|
||||
DCHECK_EQ(ENTRY_NORMAL, info->state);
|
||||
|
||||
if (!info->reuse_count)
|
||||
return Rankings::NO_USE;
|
||||
|
||||
if (info->reuse_count < kHighUse)
|
||||
return Rankings::LOW_USE;
|
||||
|
||||
return Rankings::HIGH_USE;
|
||||
}
|
||||
|
||||
// This is a minimal implementation that just discards the oldest nodes.
|
||||
// TODO(rvargas): Do something better here.
|
||||
void Eviction::TrimDeleted(bool empty) {
|
||||
Trace("*** Trim Deleted ***");
|
||||
if (backend_->disabled_)
|
||||
return;
|
||||
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
Rankings::ScopedRankingsBlock node(rankings_);
|
||||
Rankings::ScopedRankingsBlock next(
|
||||
rankings_, rankings_->GetPrev(node.get(), Rankings::DELETED));
|
||||
int deleted_entries = 0;
|
||||
while (next.get() &&
|
||||
(empty || (deleted_entries < 20 &&
|
||||
(TimeTicks::Now() - start).InMilliseconds() < 20))) {
|
||||
node.reset(next.release());
|
||||
next.reset(rankings_->GetPrev(node.get(), Rankings::DELETED));
|
||||
if (RemoveDeletedNode(node.get()))
|
||||
deleted_entries++;
|
||||
if (test_mode_)
|
||||
break;
|
||||
}
|
||||
|
||||
if (deleted_entries && !empty && ShouldTrimDeleted()) {
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&Eviction::TrimDeleted, ptr_factory_.GetWeakPtr(), false));
|
||||
}
|
||||
|
||||
CACHE_UMA(AGE_MS, "TotalTrimDeletedTime", 0, start);
|
||||
CACHE_UMA(COUNTS, "TrimDeletedItems", 0, deleted_entries);
|
||||
Trace("*** Trim Deleted end ***");
|
||||
return;
|
||||
}
|
||||
|
||||
bool Eviction::RemoveDeletedNode(CacheRankingsBlock* node) {
|
||||
scoped_refptr<EntryImpl> entry =
|
||||
backend_->GetEnumeratedEntry(node, Rankings::DELETED);
|
||||
if (!entry) {
|
||||
Trace("NewEntry failed on Trim 0x%x", node->address().value());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool doomed = (entry->entry()->Data()->state == ENTRY_DOOMED);
|
||||
entry->entry()->Data()->state = ENTRY_DOOMED;
|
||||
entry->DoomImpl();
|
||||
return !doomed;
|
||||
}
|
||||
|
||||
bool Eviction::NodeIsOldEnough(CacheRankingsBlock* node, int list) {
|
||||
if (!node)
|
||||
return false;
|
||||
|
||||
// If possible, we want to keep entries on each list at least kTargetTime
|
||||
// hours. Each successive list on the enumeration has 2x the target time of
|
||||
// the previous list.
|
||||
Time used = Time::FromInternalValue(node->Data()->last_used);
|
||||
int multiplier = 1 << list;
|
||||
return (Time::Now() - used).InHours() > kTargetTime * multiplier;
|
||||
}
|
||||
|
||||
int Eviction::SelectListByLength(Rankings::ScopedRankingsBlock* next) {
|
||||
int data_entries = header_->num_entries -
|
||||
header_->lru.sizes[Rankings::DELETED];
|
||||
// Start by having each list to be roughly the same size.
|
||||
if (header_->lru.sizes[0] > data_entries / 3)
|
||||
return 0;
|
||||
|
||||
int list = (header_->lru.sizes[1] > data_entries / 3) ? 1 : 2;
|
||||
|
||||
// Make sure that frequently used items are kept for a minimum time; we know
|
||||
// that this entry is not older than its current target, but it must be at
|
||||
// least older than the target for list 0 (kTargetTime), as long as we don't
|
||||
// exhaust list 0.
|
||||
if (!NodeIsOldEnough(next[list].get(), 0) &&
|
||||
header_->lru.sizes[0] > data_entries / 10)
|
||||
list = 0;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void Eviction::ReportListStats() {
|
||||
if (!new_eviction_)
|
||||
return;
|
||||
|
||||
Rankings::ScopedRankingsBlock last1(rankings_,
|
||||
rankings_->GetPrev(NULL, Rankings::NO_USE));
|
||||
Rankings::ScopedRankingsBlock last2(rankings_,
|
||||
rankings_->GetPrev(NULL, Rankings::LOW_USE));
|
||||
Rankings::ScopedRankingsBlock last3(rankings_,
|
||||
rankings_->GetPrev(NULL, Rankings::HIGH_USE));
|
||||
Rankings::ScopedRankingsBlock last4(rankings_,
|
||||
rankings_->GetPrev(NULL, Rankings::DELETED));
|
||||
|
||||
if (last1.get())
|
||||
CACHE_UMA(AGE, "NoUseAge", 0,
|
||||
Time::FromInternalValue(last1.get()->Data()->last_used));
|
||||
if (last2.get())
|
||||
CACHE_UMA(AGE, "LowUseAge", 0,
|
||||
Time::FromInternalValue(last2.get()->Data()->last_used));
|
||||
if (last3.get())
|
||||
CACHE_UMA(AGE, "HighUseAge", 0,
|
||||
Time::FromInternalValue(last3.get()->Data()->last_used));
|
||||
if (last4.get())
|
||||
CACHE_UMA(AGE, "DeletedAge", 0,
|
||||
Time::FromInternalValue(last4.get()->Data()->last_used));
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
91
net/disk_cache/blockfile/eviction.h
Normal file
91
net/disk_cache/blockfile/eviction.h
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_EVICTION_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_EVICTION_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "net/disk_cache/blockfile/rankings.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class BackendImpl;
|
||||
class EntryImpl;
|
||||
struct IndexHeader;
|
||||
|
||||
// This class implements the eviction algorithm for the cache and it is tightly
|
||||
// integrated with BackendImpl.
|
||||
class Eviction {
|
||||
public:
|
||||
Eviction();
|
||||
~Eviction();
|
||||
|
||||
void Init(BackendImpl* backend);
|
||||
void Stop();
|
||||
|
||||
// Deletes entries from the cache until the current size is below the limit.
|
||||
// If empty is true, the whole cache will be trimmed, regardless of being in
|
||||
// use.
|
||||
void TrimCache(bool empty);
|
||||
|
||||
// Updates the ranking information for an entry.
|
||||
void UpdateRank(EntryImpl* entry, bool modified);
|
||||
|
||||
// Notifications of interesting events for a given entry.
|
||||
void OnOpenEntry(EntryImpl* entry);
|
||||
void OnCreateEntry(EntryImpl* entry);
|
||||
void OnDoomEntry(EntryImpl* entry);
|
||||
void OnDestroyEntry(EntryImpl* entry);
|
||||
|
||||
// Testing interface.
|
||||
void SetTestMode();
|
||||
void TrimDeletedList(bool empty);
|
||||
|
||||
private:
|
||||
void PostDelayedTrim();
|
||||
void DelayedTrim();
|
||||
bool ShouldTrim();
|
||||
bool ShouldTrimDeleted();
|
||||
void ReportTrimTimes(EntryImpl* entry);
|
||||
Rankings::List GetListForEntry(EntryImpl* entry);
|
||||
bool EvictEntry(CacheRankingsBlock* node, bool empty, Rankings::List list);
|
||||
|
||||
// We'll just keep for a while a separate set of methods that implement the
|
||||
// new eviction algorithm. This code will replace the original methods when
|
||||
// finished.
|
||||
void TrimCacheV2(bool empty);
|
||||
void UpdateRankV2(EntryImpl* entry, bool modified);
|
||||
void OnOpenEntryV2(EntryImpl* entry);
|
||||
void OnCreateEntryV2(EntryImpl* entry);
|
||||
void OnDoomEntryV2(EntryImpl* entry);
|
||||
void OnDestroyEntryV2(EntryImpl* entry);
|
||||
Rankings::List GetListForEntryV2(EntryImpl* entry);
|
||||
void TrimDeleted(bool empty);
|
||||
bool RemoveDeletedNode(CacheRankingsBlock* node);
|
||||
|
||||
bool NodeIsOldEnough(CacheRankingsBlock* node, int list);
|
||||
int SelectListByLength(Rankings::ScopedRankingsBlock* next);
|
||||
void ReportListStats();
|
||||
|
||||
BackendImpl* backend_;
|
||||
Rankings* rankings_;
|
||||
IndexHeader* header_;
|
||||
int max_size_;
|
||||
int trim_delays_;
|
||||
int index_size_;
|
||||
bool new_eviction_;
|
||||
bool first_trim_;
|
||||
bool trimming_;
|
||||
bool delay_trim_;
|
||||
bool init_;
|
||||
bool test_mode_;
|
||||
base::WeakPtrFactory<Eviction> ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Eviction);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_EVICTION_H_
|
||||
27
net/disk_cache/blockfile/experiments.h
Normal file
27
net/disk_cache/blockfile/experiments.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_EXPERIMENTS_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_EXPERIMENTS_H_
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This lists the experiment groups that we care about. Only add new groups at
|
||||
// the end of the list, and always increase the number.
|
||||
enum {
|
||||
NO_EXPERIMENT = 0,
|
||||
EXPERIMENT_OLD_FILE1 = 3,
|
||||
EXPERIMENT_OLD_FILE2 = 4,
|
||||
EXPERIMENT_DELETED_LIST_OUT = 11,
|
||||
EXPERIMENT_DELETED_LIST_CONTROL = 12,
|
||||
EXPERIMENT_DELETED_LIST_IN = 13,
|
||||
EXPERIMENT_DELETED_LIST_OUT2 = 14,
|
||||
// There is no EXPERIMENT_SIMPLE_YES since this enum is used in the standard
|
||||
// backend only.
|
||||
EXPERIMENT_SIMPLE_CONTROL = 15,
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_EXPERIMENTS_H_
|
||||
16
net/disk_cache/blockfile/file.cc
Normal file
16
net/disk_cache/blockfile/file.cc
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/file.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Cross platform constructors. Platform specific code is in
|
||||
// file_{win,posix}.cc.
|
||||
|
||||
File::File() : init_(false), mixed_(false) {}
|
||||
|
||||
File::File(bool mixed_mode) : init_(false), mixed_(mixed_mode) {}
|
||||
|
||||
} // namespace disk_cache
|
||||
103
net/disk_cache/blockfile/file.h
Normal file
103
net/disk_cache/blockfile/file.h
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface of the cache.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_FILE_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_FILE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This interface is used to support asynchronous ReadData and WriteData calls.
|
||||
class FileIOCallback {
|
||||
public:
|
||||
// Notified of the actual number of bytes read or written. This value is
|
||||
// negative if an error occurred.
|
||||
virtual void OnFileIOComplete(int bytes_copied) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~FileIOCallback() {}
|
||||
};
|
||||
|
||||
// Simple wrapper around a file that allows asynchronous operations.
|
||||
class NET_EXPORT_PRIVATE File : public base::RefCounted<File> {
|
||||
friend class base::RefCounted<File>;
|
||||
public:
|
||||
File();
|
||||
// mixed_mode set to true enables regular synchronous operations for the file.
|
||||
explicit File(bool mixed_mode);
|
||||
|
||||
// Initializes the object to use the passed in file instead of opening it with
|
||||
// the Init() call. No asynchronous operations can be performed with this
|
||||
// object.
|
||||
explicit File(base::File file);
|
||||
|
||||
// Initializes the object to point to a given file. The file must aready exist
|
||||
// on disk, and allow shared read and write.
|
||||
bool Init(const base::FilePath& name);
|
||||
|
||||
// Returns true if the file was opened properly.
|
||||
bool IsValid() const;
|
||||
|
||||
// Performs synchronous IO.
|
||||
bool Read(void* buffer, size_t buffer_len, size_t offset);
|
||||
bool Write(const void* buffer, size_t buffer_len, size_t offset);
|
||||
|
||||
// Performs asynchronous IO. callback will be called when the IO completes,
|
||||
// as an APC on the thread that queued the operation.
|
||||
bool Read(void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed);
|
||||
bool Write(const void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed);
|
||||
|
||||
// Sets the file's length. The file is truncated or extended with zeros to
|
||||
// the new length.
|
||||
bool SetLength(size_t length);
|
||||
size_t GetLength();
|
||||
|
||||
// Blocks until |num_pending_io| IO operations complete.
|
||||
static void WaitForPendingIO(int* num_pending_io);
|
||||
|
||||
// Drops current pending operations without waiting for them to complete.
|
||||
static void DropPendingIO();
|
||||
|
||||
protected:
|
||||
virtual ~File();
|
||||
|
||||
// Returns the handle or file descriptor.
|
||||
base::PlatformFile platform_file() const;
|
||||
|
||||
private:
|
||||
// Performs the actual asynchronous write. If notify is set and there is no
|
||||
// callback, the call will be re-synchronized.
|
||||
bool AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed);
|
||||
|
||||
// Infrastructure for async IO.
|
||||
int DoRead(void* buffer, size_t buffer_len, size_t offset);
|
||||
int DoWrite(const void* buffer, size_t buffer_len, size_t offset);
|
||||
void OnOperationComplete(FileIOCallback* callback, int result);
|
||||
|
||||
bool init_;
|
||||
bool mixed_;
|
||||
base::File base_file_; // Regular, asynchronous IO handle.
|
||||
base::File sync_base_file_; // Synchronous IO handle.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(File);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_FILE_H_
|
||||
33
net/disk_cache/blockfile/file_block.h
Normal file
33
net/disk_cache/blockfile/file_block.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface of the cache.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_FILE_BLOCK_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_FILE_BLOCK_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This interface exposes common functionality for a single block of data
|
||||
// stored on a file-block, regardless of the real type or size of the block.
|
||||
// Used to simplify loading / storing the block from disk.
|
||||
class FileBlock {
|
||||
public:
|
||||
virtual ~FileBlock() {}
|
||||
|
||||
// Returns a pointer to the actual data.
|
||||
virtual void* buffer() const = 0;
|
||||
|
||||
// Returns the size of the block;
|
||||
virtual size_t size() const = 0;
|
||||
|
||||
// Returns the file offset of this block.
|
||||
virtual int offset() const = 0;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_FILE_BLOCK_H_
|
||||
307
net/disk_cache/blockfile/file_ios.cc
Normal file
307
net/disk_cache/blockfile/file_ios.cc
Normal file
@@ -0,0 +1,307 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/file.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/location.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/task_scheduler/post_task.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/blockfile/in_flight_io.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// This class represents a single asynchronous IO operation while it is being
|
||||
// bounced between threads.
|
||||
class FileBackgroundIO : public disk_cache::BackgroundIO {
|
||||
public:
|
||||
// Other than the actual parameters for the IO operation (including the
|
||||
// |callback| that must be notified at the end), we need the controller that
|
||||
// is keeping track of all operations. When done, we notify the controller
|
||||
// (we do NOT invoke the callback), in the worker thead that completed the
|
||||
// operation.
|
||||
FileBackgroundIO(disk_cache::File* file, const void* buf, size_t buf_len,
|
||||
size_t offset, disk_cache::FileIOCallback* callback,
|
||||
disk_cache::InFlightIO* controller)
|
||||
: disk_cache::BackgroundIO(controller), callback_(callback), file_(file),
|
||||
buf_(buf), buf_len_(buf_len), offset_(offset) {
|
||||
}
|
||||
|
||||
disk_cache::FileIOCallback* callback() {
|
||||
return callback_;
|
||||
}
|
||||
|
||||
disk_cache::File* file() {
|
||||
return file_;
|
||||
}
|
||||
|
||||
// Read and Write are the operations that can be performed asynchronously.
|
||||
// The actual parameters for the operation are setup in the constructor of
|
||||
// the object. Both methods should be called from a worker thread, by posting
|
||||
// a task to the WorkerPool (they are RunnableMethods). When finished,
|
||||
// controller->OnIOComplete() is called.
|
||||
void Read();
|
||||
void Write();
|
||||
|
||||
private:
|
||||
~FileBackgroundIO() override {}
|
||||
|
||||
disk_cache::FileIOCallback* callback_;
|
||||
|
||||
disk_cache::File* file_;
|
||||
const void* buf_;
|
||||
size_t buf_len_;
|
||||
size_t offset_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(FileBackgroundIO);
|
||||
};
|
||||
|
||||
|
||||
// The specialized controller that keeps track of current operations.
|
||||
class FileInFlightIO : public disk_cache::InFlightIO {
|
||||
public:
|
||||
FileInFlightIO() {}
|
||||
~FileInFlightIO() override {}
|
||||
|
||||
// These methods start an asynchronous operation. The arguments have the same
|
||||
// semantics of the File asynchronous operations, with the exception that the
|
||||
// operation never finishes synchronously.
|
||||
void PostRead(disk_cache::File* file, void* buf, size_t buf_len,
|
||||
size_t offset, disk_cache::FileIOCallback* callback);
|
||||
void PostWrite(disk_cache::File* file, const void* buf, size_t buf_len,
|
||||
size_t offset, disk_cache::FileIOCallback* callback);
|
||||
|
||||
protected:
|
||||
// Invokes the users' completion callback at the end of the IO operation.
|
||||
// |cancel| is true if the actual task posted to the thread is still
|
||||
// queued (because we are inside WaitForPendingIO), and false if said task is
|
||||
// the one performing the call.
|
||||
void OnOperationComplete(disk_cache::BackgroundIO* operation,
|
||||
bool cancel) override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(FileInFlightIO);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Runs on a worker thread.
|
||||
void FileBackgroundIO::Read() {
|
||||
if (file_->Read(const_cast<void*>(buf_), buf_len_, offset_)) {
|
||||
result_ = static_cast<int>(buf_len_);
|
||||
} else {
|
||||
result_ = net::ERR_CACHE_READ_FAILURE;
|
||||
}
|
||||
NotifyController();
|
||||
}
|
||||
|
||||
// Runs on a worker thread.
|
||||
void FileBackgroundIO::Write() {
|
||||
bool rv = file_->Write(buf_, buf_len_, offset_);
|
||||
|
||||
result_ = rv ? static_cast<int>(buf_len_) : net::ERR_CACHE_WRITE_FAILURE;
|
||||
NotifyController();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void FileInFlightIO::PostRead(disk_cache::File *file, void* buf, size_t buf_len,
|
||||
size_t offset, disk_cache::FileIOCallback *callback) {
|
||||
scoped_refptr<FileBackgroundIO> operation(
|
||||
new FileBackgroundIO(file, buf, buf_len, offset, callback, this));
|
||||
file->AddRef(); // Balanced on OnOperationComplete()
|
||||
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE,
|
||||
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
|
||||
base::Bind(&FileBackgroundIO::Read, operation.get()));
|
||||
OnOperationPosted(operation.get());
|
||||
}
|
||||
|
||||
void FileInFlightIO::PostWrite(disk_cache::File* file, const void* buf,
|
||||
size_t buf_len, size_t offset,
|
||||
disk_cache::FileIOCallback* callback) {
|
||||
scoped_refptr<FileBackgroundIO> operation(
|
||||
new FileBackgroundIO(file, buf, buf_len, offset, callback, this));
|
||||
file->AddRef(); // Balanced on OnOperationComplete()
|
||||
|
||||
base::PostTaskWithTraits(
|
||||
FROM_HERE,
|
||||
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
|
||||
base::Bind(&FileBackgroundIO::Write, operation.get()));
|
||||
OnOperationPosted(operation.get());
|
||||
}
|
||||
|
||||
// Runs on the IO thread.
|
||||
void FileInFlightIO::OnOperationComplete(disk_cache::BackgroundIO* operation,
|
||||
bool cancel) {
|
||||
FileBackgroundIO* op = static_cast<FileBackgroundIO*>(operation);
|
||||
|
||||
disk_cache::FileIOCallback* callback = op->callback();
|
||||
int bytes = operation->result();
|
||||
|
||||
// Release the references acquired in PostRead / PostWrite.
|
||||
op->file()->Release();
|
||||
callback->OnFileIOComplete(bytes);
|
||||
}
|
||||
|
||||
// A static object that will broker all async operations.
|
||||
FileInFlightIO* s_file_operations = NULL;
|
||||
|
||||
// Returns the current FileInFlightIO.
|
||||
FileInFlightIO* GetFileInFlightIO() {
|
||||
if (!s_file_operations) {
|
||||
s_file_operations = new FileInFlightIO;
|
||||
}
|
||||
return s_file_operations;
|
||||
}
|
||||
|
||||
// Deletes the current FileInFlightIO.
|
||||
void DeleteFileInFlightIO() {
|
||||
DCHECK(s_file_operations);
|
||||
delete s_file_operations;
|
||||
s_file_operations = NULL;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
File::File(base::File file)
|
||||
: init_(true), mixed_(true), base_file_(std::move(file)) {}
|
||||
|
||||
bool File::Init(const base::FilePath& name) {
|
||||
if (base_file_.IsValid())
|
||||
return false;
|
||||
|
||||
int flags = base::File::FLAG_OPEN | base::File::FLAG_READ |
|
||||
base::File::FLAG_WRITE;
|
||||
base_file_.Initialize(name, flags);
|
||||
return base_file_.IsValid();
|
||||
}
|
||||
|
||||
bool File::IsValid() const {
|
||||
return base_file_.IsValid();
|
||||
}
|
||||
|
||||
bool File::Read(void* buffer, size_t buffer_len, size_t offset) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (buffer_len > static_cast<size_t>(std::numeric_limits<int32_t>::max()) ||
|
||||
offset > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = base_file_.Read(offset, static_cast<char*>(buffer), buffer_len);
|
||||
return (static_cast<size_t>(ret) == buffer_len);
|
||||
}
|
||||
|
||||
bool File::Write(const void* buffer, size_t buffer_len, size_t offset) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (buffer_len > static_cast<size_t>(std::numeric_limits<int32_t>::max()) ||
|
||||
offset > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = base_file_.Write(offset, static_cast<const char*>(buffer),
|
||||
buffer_len);
|
||||
return (static_cast<size_t>(ret) == buffer_len);
|
||||
}
|
||||
|
||||
// We have to increase the ref counter of the file before performing the IO to
|
||||
// prevent the completion to happen with an invalid handle (if the file is
|
||||
// closed while the IO is in flight).
|
||||
bool File::Read(void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (!callback) {
|
||||
if (completed)
|
||||
*completed = true;
|
||||
return Read(buffer, buffer_len, offset);
|
||||
}
|
||||
|
||||
if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
|
||||
return false;
|
||||
|
||||
GetFileInFlightIO()->PostRead(this, buffer, buffer_len, offset, callback);
|
||||
|
||||
*completed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::Write(const void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (!callback) {
|
||||
if (completed)
|
||||
*completed = true;
|
||||
return Write(buffer, buffer_len, offset);
|
||||
}
|
||||
|
||||
return AsyncWrite(buffer, buffer_len, offset, callback, completed);
|
||||
}
|
||||
|
||||
bool File::SetLength(size_t length) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (length > std::numeric_limits<uint32_t>::max())
|
||||
return false;
|
||||
|
||||
return base_file_.SetLength(length);
|
||||
}
|
||||
|
||||
size_t File::GetLength() {
|
||||
DCHECK(base_file_.IsValid());
|
||||
int64_t len = base_file_.GetLength();
|
||||
|
||||
if (len < 0)
|
||||
return 0;
|
||||
if (len > static_cast<int64_t>(std::numeric_limits<uint32_t>::max()))
|
||||
return std::numeric_limits<uint32_t>::max();
|
||||
|
||||
return static_cast<size_t>(len);
|
||||
}
|
||||
|
||||
// Static.
|
||||
void File::WaitForPendingIO(int* num_pending_io) {
|
||||
// We may be running unit tests so we should allow be able to reset the
|
||||
// message loop.
|
||||
GetFileInFlightIO()->WaitForPendingIO();
|
||||
DeleteFileInFlightIO();
|
||||
}
|
||||
|
||||
// Static.
|
||||
void File::DropPendingIO() {
|
||||
GetFileInFlightIO()->DropPendingIO();
|
||||
DeleteFileInFlightIO();
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
}
|
||||
|
||||
base::PlatformFile File::platform_file() const {
|
||||
return base_file_.GetPlatformFile();
|
||||
}
|
||||
|
||||
bool File::AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
|
||||
return false;
|
||||
|
||||
GetFileInFlightIO()->PostWrite(this, buffer, buffer_len, offset, callback);
|
||||
|
||||
if (completed)
|
||||
*completed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
47
net/disk_cache/blockfile/file_lock.cc
Normal file
47
net/disk_cache/blockfile/file_lock.cc
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/file_lock.h"
|
||||
|
||||
#include "base/atomicops.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void Barrier() {
|
||||
#if !defined(COMPILER_MSVC)
|
||||
// VS uses memory barrier semantics for volatiles.
|
||||
base::subtle::MemoryBarrier();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
FileLock::FileLock(BlockFileHeader* header) {
|
||||
updating_ = &header->updating;
|
||||
(*updating_)++;
|
||||
Barrier();
|
||||
acquired_ = true;
|
||||
}
|
||||
|
||||
FileLock::~FileLock() {
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void FileLock::Lock() {
|
||||
if (acquired_)
|
||||
return;
|
||||
(*updating_)++;
|
||||
Barrier();
|
||||
}
|
||||
|
||||
void FileLock::Unlock() {
|
||||
if (!acquired_)
|
||||
return;
|
||||
Barrier();
|
||||
(*updating_)--;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
47
net/disk_cache/blockfile/file_lock.h
Normal file
47
net/disk_cache/blockfile/file_lock.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface of the cache.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_FILE_LOCK_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_FILE_LOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/disk_format_base.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This class implements a file lock that lives on the header of a memory mapped
|
||||
// file. This is NOT a thread related lock, it is a lock to detect corruption
|
||||
// of the file when the process crashes in the middle of an update.
|
||||
// The lock is acquired on the constructor and released on the destructor.
|
||||
// The typical use of the class is:
|
||||
// {
|
||||
// BlockFileHeader* header = GetFileHeader();
|
||||
// FileLock lock(header);
|
||||
// header->max_entries = num_entries;
|
||||
// // At this point the destructor is going to release the lock.
|
||||
// }
|
||||
// It is important to perform Lock() and Unlock() operations in the right order,
|
||||
// because otherwise the desired effect of the "lock" will not be achieved. If
|
||||
// the operations are inlined / optimized, the "locked" operations can happen
|
||||
// outside the lock.
|
||||
class NET_EXPORT_PRIVATE FileLock {
|
||||
public:
|
||||
explicit FileLock(BlockFileHeader* header);
|
||||
virtual ~FileLock();
|
||||
|
||||
// Virtual to make sure the compiler never inlines the calls.
|
||||
virtual void Lock();
|
||||
virtual void Unlock();
|
||||
private:
|
||||
bool acquired_;
|
||||
volatile int32_t* updating_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_FILE_LOCK_H_
|
||||
193
net/disk_cache/blockfile/file_posix.cc
Normal file
193
net/disk_cache/blockfile/file_posix.cc
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/file.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/location.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/task_runner_util.h"
|
||||
#include "base/threading/sequenced_worker_pool.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// The maximum number of threads for this pool.
|
||||
const int kMaxThreads = 5;
|
||||
|
||||
class FileWorkerPool : public base::SequencedWorkerPool {
|
||||
public:
|
||||
FileWorkerPool()
|
||||
: base::SequencedWorkerPool(kMaxThreads,
|
||||
"CachePool",
|
||||
base::TaskPriority::USER_BLOCKING) {}
|
||||
|
||||
protected:
|
||||
~FileWorkerPool() override = default;
|
||||
};
|
||||
|
||||
base::LazyInstance<FileWorkerPool>::Leaky s_worker_pool =
|
||||
LAZY_INSTANCE_INITIALIZER;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
File::File(base::File file)
|
||||
: init_(true), mixed_(true), base_file_(std::move(file)) {}
|
||||
|
||||
bool File::Init(const base::FilePath& name) {
|
||||
if (base_file_.IsValid())
|
||||
return false;
|
||||
|
||||
int flags = base::File::FLAG_OPEN | base::File::FLAG_READ |
|
||||
base::File::FLAG_WRITE;
|
||||
base_file_.Initialize(name, flags);
|
||||
return base_file_.IsValid();
|
||||
}
|
||||
|
||||
bool File::IsValid() const {
|
||||
return base_file_.IsValid();
|
||||
}
|
||||
|
||||
bool File::Read(void* buffer, size_t buffer_len, size_t offset) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (buffer_len > static_cast<size_t>(std::numeric_limits<int32_t>::max()) ||
|
||||
offset > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = base_file_.Read(offset, static_cast<char*>(buffer), buffer_len);
|
||||
return (static_cast<size_t>(ret) == buffer_len);
|
||||
}
|
||||
|
||||
bool File::Write(const void* buffer, size_t buffer_len, size_t offset) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (buffer_len > static_cast<size_t>(std::numeric_limits<int32_t>::max()) ||
|
||||
offset > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = base_file_.Write(offset, static_cast<const char*>(buffer),
|
||||
buffer_len);
|
||||
return (static_cast<size_t>(ret) == buffer_len);
|
||||
}
|
||||
|
||||
bool File::Read(void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (!callback) {
|
||||
if (completed)
|
||||
*completed = true;
|
||||
return Read(buffer, buffer_len, offset);
|
||||
}
|
||||
|
||||
if (buffer_len > static_cast<size_t>(std::numeric_limits<int32_t>::max()) ||
|
||||
offset > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
base::PostTaskAndReplyWithResult(
|
||||
s_worker_pool.Pointer(), FROM_HERE,
|
||||
base::Bind(&File::DoRead, base::Unretained(this), buffer, buffer_len,
|
||||
offset),
|
||||
base::Bind(&File::OnOperationComplete, this, callback));
|
||||
|
||||
*completed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::Write(const void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (!callback) {
|
||||
if (completed)
|
||||
*completed = true;
|
||||
return Write(buffer, buffer_len, offset);
|
||||
}
|
||||
|
||||
if (buffer_len > static_cast<size_t>(std::numeric_limits<int32_t>::max()) ||
|
||||
offset > static_cast<size_t>(std::numeric_limits<int32_t>::max())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
base::PostTaskAndReplyWithResult(
|
||||
s_worker_pool.Pointer(), FROM_HERE,
|
||||
base::Bind(&File::DoWrite, base::Unretained(this), buffer, buffer_len,
|
||||
offset),
|
||||
base::Bind(&File::OnOperationComplete, this, callback));
|
||||
|
||||
*completed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::SetLength(size_t length) {
|
||||
DCHECK(base_file_.IsValid());
|
||||
if (length > std::numeric_limits<uint32_t>::max())
|
||||
return false;
|
||||
|
||||
return base_file_.SetLength(length);
|
||||
}
|
||||
|
||||
size_t File::GetLength() {
|
||||
DCHECK(base_file_.IsValid());
|
||||
int64_t len = base_file_.GetLength();
|
||||
|
||||
if (len < 0)
|
||||
return 0;
|
||||
if (len > static_cast<int64_t>(std::numeric_limits<uint32_t>::max()))
|
||||
return std::numeric_limits<uint32_t>::max();
|
||||
|
||||
return static_cast<size_t>(len);
|
||||
}
|
||||
|
||||
// Static.
|
||||
void File::WaitForPendingIO(int* num_pending_io) {
|
||||
// We are running unit tests so we should wait for all callbacks. Sadly, the
|
||||
// worker pool only waits for tasks on the worker pool, not the "Reply" tasks
|
||||
// so we have to let the current message loop to run.
|
||||
s_worker_pool.Get().FlushForTesting();
|
||||
base::RunLoop().RunUntilIdle();
|
||||
}
|
||||
|
||||
// Static.
|
||||
void File::DropPendingIO() {
|
||||
}
|
||||
|
||||
File::~File() = default;
|
||||
|
||||
base::PlatformFile File::platform_file() const {
|
||||
return base_file_.GetPlatformFile();
|
||||
}
|
||||
|
||||
// Runs on a worker thread.
|
||||
int File::DoRead(void* buffer, size_t buffer_len, size_t offset) {
|
||||
if (Read(const_cast<void*>(buffer), buffer_len, offset))
|
||||
return static_cast<int>(buffer_len);
|
||||
|
||||
return net::ERR_CACHE_READ_FAILURE;
|
||||
}
|
||||
|
||||
// Runs on a worker thread.
|
||||
int File::DoWrite(const void* buffer, size_t buffer_len, size_t offset) {
|
||||
if (Write(const_cast<void*>(buffer), buffer_len, offset))
|
||||
return static_cast<int>(buffer_len);
|
||||
|
||||
return net::ERR_CACHE_WRITE_FAILURE;
|
||||
}
|
||||
|
||||
// This method actually makes sure that the last reference to the file doesn't
|
||||
// go away on the worker pool.
|
||||
void File::OnOperationComplete(FileIOCallback* callback, int result) {
|
||||
callback->OnFileIOComplete(result);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
256
net/disk_cache/blockfile/file_win.cc
Normal file
256
net/disk_cache/blockfile/file_win.cc
Normal file
@@ -0,0 +1,256 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/file.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Structure used for asynchronous operations.
|
||||
struct MyOverlapped {
|
||||
MyOverlapped(disk_cache::File* file, size_t offset,
|
||||
disk_cache::FileIOCallback* callback);
|
||||
~MyOverlapped() {}
|
||||
OVERLAPPED* overlapped() {
|
||||
return &context_.overlapped;
|
||||
}
|
||||
|
||||
base::MessageLoopForIO::IOContext context_;
|
||||
scoped_refptr<disk_cache::File> file_;
|
||||
disk_cache::FileIOCallback* callback_;
|
||||
};
|
||||
|
||||
static_assert(offsetof(MyOverlapped, context_) == 0,
|
||||
"should start with overlapped");
|
||||
|
||||
// Helper class to handle the IO completion notifications from the message loop.
|
||||
class CompletionHandler : public base::MessageLoopForIO::IOHandler {
|
||||
void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
|
||||
DWORD actual_bytes,
|
||||
DWORD error) override;
|
||||
};
|
||||
|
||||
static base::LazyInstance<CompletionHandler>::DestructorAtExit
|
||||
g_completion_handler = LAZY_INSTANCE_INITIALIZER;
|
||||
|
||||
void CompletionHandler::OnIOCompleted(
|
||||
base::MessageLoopForIO::IOContext* context,
|
||||
DWORD actual_bytes,
|
||||
DWORD error) {
|
||||
MyOverlapped* data = reinterpret_cast<MyOverlapped*>(context);
|
||||
|
||||
if (error) {
|
||||
DCHECK(!actual_bytes);
|
||||
actual_bytes = static_cast<DWORD>(net::ERR_CACHE_READ_FAILURE);
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
if (data->callback_)
|
||||
data->callback_->OnFileIOComplete(static_cast<int>(actual_bytes));
|
||||
|
||||
delete data;
|
||||
}
|
||||
|
||||
MyOverlapped::MyOverlapped(disk_cache::File* file, size_t offset,
|
||||
disk_cache::FileIOCallback* callback) {
|
||||
context_.overlapped.Offset = static_cast<DWORD>(offset);
|
||||
file_ = file;
|
||||
callback_ = callback;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
File::File(base::File file)
|
||||
: init_(true), mixed_(true), sync_base_file_(std::move(file)) {}
|
||||
|
||||
bool File::Init(const base::FilePath& name) {
|
||||
DCHECK(!init_);
|
||||
if (init_)
|
||||
return false;
|
||||
|
||||
DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
||||
DWORD access = GENERIC_READ | GENERIC_WRITE | DELETE;
|
||||
base_file_ =
|
||||
base::File(CreateFile(name.value().c_str(), access, sharing, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL));
|
||||
|
||||
if (!base_file_.IsValid())
|
||||
return false;
|
||||
|
||||
base::MessageLoopForIO::current()->RegisterIOHandler(
|
||||
base_file_.GetPlatformFile(), g_completion_handler.Pointer());
|
||||
|
||||
init_ = true;
|
||||
sync_base_file_ =
|
||||
base::File(CreateFile(name.value().c_str(), access, sharing, NULL,
|
||||
OPEN_EXISTING, 0, NULL));
|
||||
|
||||
if (!sync_base_file_.IsValid())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::IsValid() const {
|
||||
if (!init_)
|
||||
return false;
|
||||
return base_file_.IsValid() || sync_base_file_.IsValid();
|
||||
}
|
||||
|
||||
bool File::Read(void* buffer, size_t buffer_len, size_t offset) {
|
||||
DCHECK(init_);
|
||||
if (buffer_len > ULONG_MAX || offset > LONG_MAX)
|
||||
return false;
|
||||
|
||||
int ret = sync_base_file_.Read(offset, static_cast<char*>(buffer),
|
||||
buffer_len);
|
||||
return static_cast<int>(buffer_len) == ret;
|
||||
}
|
||||
|
||||
bool File::Write(const void* buffer, size_t buffer_len, size_t offset) {
|
||||
DCHECK(init_);
|
||||
if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
|
||||
return false;
|
||||
|
||||
int ret = sync_base_file_.Write(offset, static_cast<const char*>(buffer),
|
||||
buffer_len);
|
||||
return static_cast<int>(buffer_len) == ret;
|
||||
}
|
||||
|
||||
// We have to increase the ref counter of the file before performing the IO to
|
||||
// prevent the completion to happen with an invalid handle (if the file is
|
||||
// closed while the IO is in flight).
|
||||
bool File::Read(void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(init_);
|
||||
if (!callback) {
|
||||
if (completed)
|
||||
*completed = true;
|
||||
return Read(buffer, buffer_len, offset);
|
||||
}
|
||||
|
||||
if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
|
||||
return false;
|
||||
|
||||
MyOverlapped* data = new MyOverlapped(this, offset, callback);
|
||||
DWORD size = static_cast<DWORD>(buffer_len);
|
||||
|
||||
DWORD actual;
|
||||
if (!ReadFile(base_file_.GetPlatformFile(), buffer, size, &actual,
|
||||
data->overlapped())) {
|
||||
*completed = false;
|
||||
if (GetLastError() == ERROR_IO_PENDING)
|
||||
return true;
|
||||
delete data;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The operation completed already. We'll be called back anyway.
|
||||
*completed = (actual == size);
|
||||
DCHECK_EQ(size, actual);
|
||||
data->callback_ = NULL;
|
||||
data->file_ = NULL; // There is no reason to hold on to this anymore.
|
||||
return *completed;
|
||||
}
|
||||
|
||||
bool File::Write(const void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(init_);
|
||||
if (!callback) {
|
||||
if (completed)
|
||||
*completed = true;
|
||||
return Write(buffer, buffer_len, offset);
|
||||
}
|
||||
|
||||
return AsyncWrite(buffer, buffer_len, offset, callback, completed);
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
}
|
||||
|
||||
base::PlatformFile File::platform_file() const {
|
||||
DCHECK(init_);
|
||||
return base_file_.IsValid() ? base_file_.GetPlatformFile() :
|
||||
sync_base_file_.GetPlatformFile();
|
||||
}
|
||||
|
||||
bool File::AsyncWrite(const void* buffer, size_t buffer_len, size_t offset,
|
||||
FileIOCallback* callback, bool* completed) {
|
||||
DCHECK(init_);
|
||||
DCHECK(callback);
|
||||
DCHECK(completed);
|
||||
if (buffer_len > ULONG_MAX || offset > ULONG_MAX)
|
||||
return false;
|
||||
|
||||
MyOverlapped* data = new MyOverlapped(this, offset, callback);
|
||||
DWORD size = static_cast<DWORD>(buffer_len);
|
||||
|
||||
DWORD actual;
|
||||
if (!WriteFile(base_file_.GetPlatformFile(), buffer, size, &actual,
|
||||
data->overlapped())) {
|
||||
*completed = false;
|
||||
if (GetLastError() == ERROR_IO_PENDING)
|
||||
return true;
|
||||
delete data;
|
||||
return false;
|
||||
}
|
||||
|
||||
// The operation completed already. We'll be called back anyway.
|
||||
*completed = (actual == size);
|
||||
DCHECK_EQ(size, actual);
|
||||
data->callback_ = NULL;
|
||||
data->file_ = NULL; // There is no reason to hold on to this anymore.
|
||||
return *completed;
|
||||
}
|
||||
|
||||
bool File::SetLength(size_t length) {
|
||||
DCHECK(init_);
|
||||
if (length > ULONG_MAX)
|
||||
return false;
|
||||
|
||||
DWORD size = static_cast<DWORD>(length);
|
||||
HANDLE file = platform_file();
|
||||
if (INVALID_SET_FILE_POINTER == SetFilePointer(file, size, NULL, FILE_BEGIN))
|
||||
return false;
|
||||
|
||||
return TRUE == SetEndOfFile(file);
|
||||
}
|
||||
|
||||
size_t File::GetLength() {
|
||||
DCHECK(init_);
|
||||
LARGE_INTEGER size;
|
||||
HANDLE file = platform_file();
|
||||
if (!GetFileSizeEx(file, &size))
|
||||
return 0;
|
||||
if (size.HighPart)
|
||||
return ULONG_MAX;
|
||||
|
||||
return static_cast<size_t>(size.LowPart);
|
||||
}
|
||||
|
||||
// Static.
|
||||
void File::WaitForPendingIO(int* num_pending_io) {
|
||||
while (*num_pending_io) {
|
||||
// Asynchronous IO operations may be in flight and the completion may end
|
||||
// up calling us back so let's wait for them.
|
||||
base::MessageLoopForIO::IOHandler* handler = g_completion_handler.Pointer();
|
||||
base::MessageLoopForIO::current()->WaitForIOCompletion(100, handler);
|
||||
}
|
||||
}
|
||||
|
||||
// Static.
|
||||
void File::DropPendingIO() {
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
107
net/disk_cache/blockfile/histogram_macros.h
Normal file
107
net/disk_cache/blockfile/histogram_macros.h
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file contains macros to simplify histogram reporting from the disk
|
||||
// cache. The main issue is that we want to have separate histograms for each
|
||||
// type of cache (regular vs. media, etc), without adding the complexity of
|
||||
// keeping track of a potentially large number of histogram objects that have to
|
||||
// survive the backend object that created them.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_HISTOGRAM_MACROS_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_HISTOGRAM_MACROS_H_
|
||||
|
||||
#include "base/metrics/histogram.h"
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// These histograms follow the definition of UMA_HISTOGRAMN_XXX except that
|
||||
// the counter is not cached locally.
|
||||
|
||||
#define CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, min, max, bucket_count) \
|
||||
do { \
|
||||
base::HistogramBase* counter = base::Histogram::FactoryGet( \
|
||||
name, min, max, bucket_count, \
|
||||
base::Histogram::kUmaTargetedHistogramFlag); \
|
||||
counter->Add(sample); \
|
||||
} while (0)
|
||||
|
||||
#define CACHE_HISTOGRAM_COUNTS(name, sample) CACHE_HISTOGRAM_CUSTOM_COUNTS( \
|
||||
name, sample, 1, 1000000, 50)
|
||||
|
||||
#define CACHE_HISTOGRAM_COUNTS_10000(name, sample) \
|
||||
CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 10000, 50)
|
||||
|
||||
#define CACHE_HISTOGRAM_COUNTS_50000(name, sample) \
|
||||
CACHE_HISTOGRAM_CUSTOM_COUNTS(name, sample, 1, 50000000, 50)
|
||||
|
||||
#define CACHE_HISTOGRAM_CUSTOM_TIMES(name, sample, min, max, bucket_count) \
|
||||
do { \
|
||||
base::HistogramBase* counter = base::Histogram::FactoryTimeGet( \
|
||||
name, min, max, bucket_count, \
|
||||
base::Histogram::kUmaTargetedHistogramFlag); \
|
||||
counter->AddTime(sample); \
|
||||
} while (0)
|
||||
|
||||
#define CACHE_HISTOGRAM_TIMES(name, sample) CACHE_HISTOGRAM_CUSTOM_TIMES( \
|
||||
name, sample, base::TimeDelta::FromMilliseconds(1), \
|
||||
base::TimeDelta::FromSeconds(10), 50)
|
||||
|
||||
#define CACHE_HISTOGRAM_ENUMERATION(name, sample, boundary_value) do { \
|
||||
base::HistogramBase* counter = base::LinearHistogram::FactoryGet( \
|
||||
name, 1, boundary_value, boundary_value + 1, \
|
||||
base::Histogram::kUmaTargetedHistogramFlag); \
|
||||
counter->Add(sample); \
|
||||
} while (0)
|
||||
|
||||
#define CACHE_HISTOGRAM_PERCENTAGE(name, under_one_hundred) \
|
||||
CACHE_HISTOGRAM_ENUMERATION(name, under_one_hundred, 101)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// HISTOGRAM_HOURS will collect time related data with a granularity of hours
|
||||
// and normal values of a few months.
|
||||
#define CACHE_HISTOGRAM_HOURS CACHE_HISTOGRAM_COUNTS_10000
|
||||
|
||||
// HISTOGRAM_AGE will collect time elapsed since |initial_time|, with a
|
||||
// granularity of hours and normal values of a few months.
|
||||
#define CACHE_HISTOGRAM_AGE(name, initial_time) \
|
||||
CACHE_HISTOGRAM_COUNTS_10000(name, \
|
||||
(base::Time::Now() - initial_time).InHours())
|
||||
|
||||
// HISTOGRAM_AGE_MS will collect time elapsed since |initial_time|, with the
|
||||
// normal resolution of the UMA_HISTOGRAM_TIMES.
|
||||
#define CACHE_HISTOGRAM_AGE_MS(name, initial_time)\
|
||||
CACHE_HISTOGRAM_TIMES(name, base::TimeTicks::Now() - initial_time)
|
||||
|
||||
#define CACHE_HISTOGRAM_CACHE_ERROR(name, sample) \
|
||||
CACHE_HISTOGRAM_ENUMERATION(name, sample, 50)
|
||||
|
||||
// Generates a UMA histogram of the given type, generating the proper name for
|
||||
// it (asking backend_->HistogramName), and adding the provided sample.
|
||||
// For example, to generate a regualar UMA_HISTOGRAM_COUNTS, this macro would
|
||||
// be used as:
|
||||
// CACHE_UMA(COUNTS, "MyName", 0, 20);
|
||||
// CACHE_UMA(COUNTS, "MyExperiment", 530, 55);
|
||||
// which roughly translates to:
|
||||
// UMA_HISTOGRAM_COUNTS_1M("DiskCache.2.MyName", 20); // "2" is the CacheType.
|
||||
// UMA_HISTOGRAM_COUNTS_1M("DiskCache.2.MyExperiment_530", 55);
|
||||
//
|
||||
#define CACHE_UMA(type, name, experiment, sample) {\
|
||||
const std::string my_name =\
|
||||
CACHE_UMA_BACKEND_IMPL_OBJ->HistogramName(name, experiment);\
|
||||
switch (CACHE_UMA_BACKEND_IMPL_OBJ->cache_type()) {\
|
||||
default:\
|
||||
NOTREACHED();\
|
||||
/* Fall-through. */\
|
||||
case net::DISK_CACHE:\
|
||||
case net::MEDIA_CACHE:\
|
||||
case net::APP_CACHE:\
|
||||
case net::SHADER_CACHE:\
|
||||
case net::PNACL_CACHE:\
|
||||
CACHE_HISTOGRAM_##type(my_name.data(), sample);\
|
||||
break;\
|
||||
}\
|
||||
}
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_HISTOGRAM_MACROS_H_
|
||||
563
net/disk_cache/blockfile/in_flight_backend_io.cc
Normal file
563
net/disk_cache/blockfile/in_flight_backend_io.cc
Normal file
@@ -0,0 +1,563 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/in_flight_backend_io.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/bind_helpers.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/location.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/blockfile/backend_impl.h"
|
||||
#include "net/disk_cache/blockfile/entry_impl.h"
|
||||
#include "net/disk_cache/blockfile/histogram_macros.h"
|
||||
|
||||
// Provide a BackendImpl object to macros from histogram_macros.h.
|
||||
#define CACHE_UMA_BACKEND_IMPL_OBJ backend_
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace {
|
||||
|
||||
// Used to leak a strong reference to an EntryImpl to the user of disk_cache.
|
||||
EntryImpl* LeakEntryImpl(scoped_refptr<EntryImpl> entry) {
|
||||
// Balanced on OP_CLOSE_ENTRY handling in BackendIO::ExecuteBackendOperation.
|
||||
if (entry)
|
||||
entry->AddRef();
|
||||
return entry.get();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BackendIO::BackendIO(InFlightIO* controller, BackendImpl* backend,
|
||||
const net::CompletionCallback& callback)
|
||||
: BackgroundIO(controller),
|
||||
backend_(backend),
|
||||
callback_(callback),
|
||||
operation_(OP_NONE),
|
||||
entry_ptr_(NULL),
|
||||
iterator_(NULL),
|
||||
entry_(NULL),
|
||||
index_(0),
|
||||
offset_(0),
|
||||
buf_len_(0),
|
||||
truncate_(false),
|
||||
offset64_(0),
|
||||
start_(NULL) {
|
||||
start_time_ = base::TimeTicks::Now();
|
||||
}
|
||||
|
||||
// Runs on the background thread.
|
||||
void BackendIO::ExecuteOperation() {
|
||||
if (IsEntryOperation())
|
||||
return ExecuteEntryOperation();
|
||||
|
||||
ExecuteBackendOperation();
|
||||
}
|
||||
|
||||
// Runs on the background thread.
|
||||
void BackendIO::OnIOComplete(int result) {
|
||||
DCHECK(IsEntryOperation());
|
||||
DCHECK_NE(result, net::ERR_IO_PENDING);
|
||||
result_ = result;
|
||||
NotifyController();
|
||||
}
|
||||
|
||||
// Runs on the primary thread.
|
||||
void BackendIO::OnDone(bool cancel) {
|
||||
if (IsEntryOperation()) {
|
||||
CACHE_UMA(TIMES, "TotalIOTime", 0, ElapsedTime());
|
||||
}
|
||||
|
||||
if (!ReturnsEntry())
|
||||
return;
|
||||
|
||||
if (result() == net::OK) {
|
||||
static_cast<EntryImpl*>(*entry_ptr_)->OnEntryCreated(backend_);
|
||||
if (cancel)
|
||||
(*entry_ptr_)->Close();
|
||||
}
|
||||
}
|
||||
|
||||
bool BackendIO::IsEntryOperation() {
|
||||
return operation_ > OP_MAX_BACKEND;
|
||||
}
|
||||
|
||||
void BackendIO::Init() {
|
||||
operation_ = OP_INIT;
|
||||
}
|
||||
|
||||
void BackendIO::OpenEntry(const std::string& key, Entry** entry) {
|
||||
operation_ = OP_OPEN;
|
||||
key_ = key;
|
||||
entry_ptr_ = entry;
|
||||
}
|
||||
|
||||
void BackendIO::CreateEntry(const std::string& key, Entry** entry) {
|
||||
operation_ = OP_CREATE;
|
||||
key_ = key;
|
||||
entry_ptr_ = entry;
|
||||
}
|
||||
|
||||
void BackendIO::DoomEntry(const std::string& key) {
|
||||
operation_ = OP_DOOM;
|
||||
key_ = key;
|
||||
}
|
||||
|
||||
void BackendIO::DoomAllEntries() {
|
||||
operation_ = OP_DOOM_ALL;
|
||||
}
|
||||
|
||||
void BackendIO::DoomEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time) {
|
||||
operation_ = OP_DOOM_BETWEEN;
|
||||
initial_time_ = initial_time;
|
||||
end_time_ = end_time;
|
||||
}
|
||||
|
||||
void BackendIO::DoomEntriesSince(const base::Time initial_time) {
|
||||
operation_ = OP_DOOM_SINCE;
|
||||
initial_time_ = initial_time;
|
||||
}
|
||||
|
||||
void BackendIO::CalculateSizeOfAllEntries() {
|
||||
operation_ = OP_SIZE_ALL;
|
||||
}
|
||||
|
||||
void BackendIO::OpenNextEntry(Rankings::Iterator* iterator,
|
||||
Entry** next_entry) {
|
||||
operation_ = OP_OPEN_NEXT;
|
||||
iterator_ = iterator;
|
||||
entry_ptr_ = next_entry;
|
||||
}
|
||||
|
||||
void BackendIO::EndEnumeration(std::unique_ptr<Rankings::Iterator> iterator) {
|
||||
operation_ = OP_END_ENUMERATION;
|
||||
scoped_iterator_ = std::move(iterator);
|
||||
}
|
||||
|
||||
void BackendIO::OnExternalCacheHit(const std::string& key) {
|
||||
operation_ = OP_ON_EXTERNAL_CACHE_HIT;
|
||||
key_ = key;
|
||||
}
|
||||
|
||||
void BackendIO::CloseEntryImpl(EntryImpl* entry) {
|
||||
operation_ = OP_CLOSE_ENTRY;
|
||||
entry_ = entry;
|
||||
}
|
||||
|
||||
void BackendIO::DoomEntryImpl(EntryImpl* entry) {
|
||||
operation_ = OP_DOOM_ENTRY;
|
||||
entry_ = entry;
|
||||
}
|
||||
|
||||
void BackendIO::FlushQueue() {
|
||||
operation_ = OP_FLUSH_QUEUE;
|
||||
}
|
||||
|
||||
void BackendIO::RunTask(const base::Closure& task) {
|
||||
operation_ = OP_RUN_TASK;
|
||||
task_ = task;
|
||||
}
|
||||
|
||||
void BackendIO::ReadData(EntryImpl* entry, int index, int offset,
|
||||
net::IOBuffer* buf, int buf_len) {
|
||||
operation_ = OP_READ;
|
||||
entry_ = entry;
|
||||
index_ = index;
|
||||
offset_ = offset;
|
||||
buf_ = buf;
|
||||
buf_len_ = buf_len;
|
||||
}
|
||||
|
||||
void BackendIO::WriteData(EntryImpl* entry, int index, int offset,
|
||||
net::IOBuffer* buf, int buf_len, bool truncate) {
|
||||
operation_ = OP_WRITE;
|
||||
entry_ = entry;
|
||||
index_ = index;
|
||||
offset_ = offset;
|
||||
buf_ = buf;
|
||||
buf_len_ = buf_len;
|
||||
truncate_ = truncate;
|
||||
}
|
||||
|
||||
void BackendIO::ReadSparseData(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len) {
|
||||
operation_ = OP_READ_SPARSE;
|
||||
entry_ = entry;
|
||||
offset64_ = offset;
|
||||
buf_ = buf;
|
||||
buf_len_ = buf_len;
|
||||
}
|
||||
|
||||
void BackendIO::WriteSparseData(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len) {
|
||||
operation_ = OP_WRITE_SPARSE;
|
||||
entry_ = entry;
|
||||
offset64_ = offset;
|
||||
buf_ = buf;
|
||||
buf_len_ = buf_len;
|
||||
}
|
||||
|
||||
void BackendIO::GetAvailableRange(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
int len,
|
||||
int64_t* start) {
|
||||
operation_ = OP_GET_RANGE;
|
||||
entry_ = entry;
|
||||
offset64_ = offset;
|
||||
buf_len_ = len;
|
||||
start_ = start;
|
||||
}
|
||||
|
||||
void BackendIO::CancelSparseIO(EntryImpl* entry) {
|
||||
operation_ = OP_CANCEL_IO;
|
||||
entry_ = entry;
|
||||
}
|
||||
|
||||
void BackendIO::ReadyForSparseIO(EntryImpl* entry) {
|
||||
operation_ = OP_IS_READY;
|
||||
entry_ = entry;
|
||||
}
|
||||
|
||||
BackendIO::~BackendIO() = default;
|
||||
|
||||
bool BackendIO::ReturnsEntry() {
|
||||
return operation_ == OP_OPEN || operation_ == OP_CREATE ||
|
||||
operation_ == OP_OPEN_NEXT;
|
||||
}
|
||||
|
||||
base::TimeDelta BackendIO::ElapsedTime() const {
|
||||
return base::TimeTicks::Now() - start_time_;
|
||||
}
|
||||
|
||||
// Runs on the background thread.
|
||||
void BackendIO::ExecuteBackendOperation() {
|
||||
switch (operation_) {
|
||||
case OP_INIT:
|
||||
result_ = backend_->SyncInit();
|
||||
break;
|
||||
case OP_OPEN: {
|
||||
scoped_refptr<EntryImpl> entry;
|
||||
result_ = backend_->SyncOpenEntry(key_, &entry);
|
||||
*entry_ptr_ = LeakEntryImpl(std::move(entry));
|
||||
break;
|
||||
}
|
||||
case OP_CREATE: {
|
||||
scoped_refptr<EntryImpl> entry;
|
||||
result_ = backend_->SyncCreateEntry(key_, &entry);
|
||||
*entry_ptr_ = LeakEntryImpl(std::move(entry));
|
||||
break;
|
||||
}
|
||||
case OP_DOOM:
|
||||
result_ = backend_->SyncDoomEntry(key_);
|
||||
break;
|
||||
case OP_DOOM_ALL:
|
||||
result_ = backend_->SyncDoomAllEntries();
|
||||
break;
|
||||
case OP_DOOM_BETWEEN:
|
||||
result_ = backend_->SyncDoomEntriesBetween(initial_time_, end_time_);
|
||||
break;
|
||||
case OP_DOOM_SINCE:
|
||||
result_ = backend_->SyncDoomEntriesSince(initial_time_);
|
||||
break;
|
||||
case OP_SIZE_ALL:
|
||||
result_ = backend_->SyncCalculateSizeOfAllEntries();
|
||||
break;
|
||||
case OP_OPEN_NEXT: {
|
||||
scoped_refptr<EntryImpl> entry;
|
||||
result_ = backend_->SyncOpenNextEntry(iterator_, &entry);
|
||||
*entry_ptr_ = LeakEntryImpl(std::move(entry));
|
||||
break;
|
||||
}
|
||||
case OP_END_ENUMERATION:
|
||||
backend_->SyncEndEnumeration(std::move(scoped_iterator_));
|
||||
result_ = net::OK;
|
||||
break;
|
||||
case OP_ON_EXTERNAL_CACHE_HIT:
|
||||
backend_->SyncOnExternalCacheHit(key_);
|
||||
result_ = net::OK;
|
||||
break;
|
||||
case OP_CLOSE_ENTRY:
|
||||
// Collect the reference to |entry_| to balance with the AddRef() in
|
||||
// LeakEntryImpl.
|
||||
entry_->Release();
|
||||
result_ = net::OK;
|
||||
break;
|
||||
case OP_DOOM_ENTRY:
|
||||
entry_->DoomImpl();
|
||||
result_ = net::OK;
|
||||
break;
|
||||
case OP_FLUSH_QUEUE:
|
||||
result_ = net::OK;
|
||||
break;
|
||||
case OP_RUN_TASK:
|
||||
task_.Run();
|
||||
result_ = net::OK;
|
||||
break;
|
||||
default:
|
||||
NOTREACHED() << "Invalid Operation";
|
||||
result_ = net::ERR_UNEXPECTED;
|
||||
}
|
||||
DCHECK_NE(net::ERR_IO_PENDING, result_);
|
||||
NotifyController();
|
||||
}
|
||||
|
||||
// Runs on the background thread.
|
||||
void BackendIO::ExecuteEntryOperation() {
|
||||
switch (operation_) {
|
||||
case OP_READ:
|
||||
result_ =
|
||||
entry_->ReadDataImpl(index_, offset_, buf_.get(), buf_len_,
|
||||
base::Bind(&BackendIO::OnIOComplete, this));
|
||||
break;
|
||||
case OP_WRITE:
|
||||
result_ =
|
||||
entry_->WriteDataImpl(index_, offset_, buf_.get(), buf_len_,
|
||||
base::Bind(&BackendIO::OnIOComplete, this),
|
||||
truncate_);
|
||||
break;
|
||||
case OP_READ_SPARSE:
|
||||
result_ = entry_->ReadSparseDataImpl(
|
||||
offset64_, buf_.get(), buf_len_,
|
||||
base::Bind(&BackendIO::OnIOComplete, this));
|
||||
break;
|
||||
case OP_WRITE_SPARSE:
|
||||
result_ = entry_->WriteSparseDataImpl(
|
||||
offset64_, buf_.get(), buf_len_,
|
||||
base::Bind(&BackendIO::OnIOComplete, this));
|
||||
break;
|
||||
case OP_GET_RANGE:
|
||||
result_ = entry_->GetAvailableRangeImpl(offset64_, buf_len_, start_);
|
||||
break;
|
||||
case OP_CANCEL_IO:
|
||||
entry_->CancelSparseIOImpl();
|
||||
result_ = net::OK;
|
||||
break;
|
||||
case OP_IS_READY:
|
||||
result_ = entry_->ReadyForSparseIOImpl(
|
||||
base::Bind(&BackendIO::OnIOComplete, this));
|
||||
break;
|
||||
default:
|
||||
NOTREACHED() << "Invalid Operation";
|
||||
result_ = net::ERR_UNEXPECTED;
|
||||
}
|
||||
buf_ = NULL;
|
||||
if (result_ != net::ERR_IO_PENDING)
|
||||
NotifyController();
|
||||
}
|
||||
|
||||
InFlightBackendIO::InFlightBackendIO(
|
||||
BackendImpl* backend,
|
||||
const scoped_refptr<base::SingleThreadTaskRunner>& background_thread)
|
||||
: backend_(backend),
|
||||
background_thread_(background_thread),
|
||||
ptr_factory_(this) {
|
||||
}
|
||||
|
||||
InFlightBackendIO::~InFlightBackendIO() = default;
|
||||
|
||||
void InFlightBackendIO::Init(const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->Init();
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::OpenEntry(const std::string& key, Entry** entry,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->OpenEntry(key, entry);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::CreateEntry(const std::string& key, Entry** entry,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->CreateEntry(key, entry);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::DoomEntry(const std::string& key,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->DoomEntry(key);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::DoomAllEntries(
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->DoomAllEntries();
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::DoomEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->DoomEntriesBetween(initial_time, end_time);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::CalculateSizeOfAllEntries(
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->CalculateSizeOfAllEntries();
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::DoomEntriesSince(
|
||||
const base::Time initial_time, const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->DoomEntriesSince(initial_time);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::OpenNextEntry(Rankings::Iterator* iterator,
|
||||
Entry** next_entry,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->OpenNextEntry(iterator, next_entry);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::EndEnumeration(
|
||||
std::unique_ptr<Rankings::Iterator> iterator) {
|
||||
scoped_refptr<BackendIO> operation(
|
||||
new BackendIO(this, backend_, net::CompletionCallback()));
|
||||
operation->EndEnumeration(std::move(iterator));
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::OnExternalCacheHit(const std::string& key) {
|
||||
scoped_refptr<BackendIO> operation(
|
||||
new BackendIO(this, backend_, net::CompletionCallback()));
|
||||
operation->OnExternalCacheHit(key);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::CloseEntryImpl(EntryImpl* entry) {
|
||||
scoped_refptr<BackendIO> operation(
|
||||
new BackendIO(this, backend_, net::CompletionCallback()));
|
||||
operation->CloseEntryImpl(entry);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::DoomEntryImpl(EntryImpl* entry) {
|
||||
scoped_refptr<BackendIO> operation(
|
||||
new BackendIO(this, backend_, net::CompletionCallback()));
|
||||
operation->DoomEntryImpl(entry);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::FlushQueue(const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->FlushQueue();
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::RunTask(
|
||||
const base::Closure& task, const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->RunTask(task);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::ReadData(EntryImpl* entry, int index, int offset,
|
||||
net::IOBuffer* buf, int buf_len,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->ReadData(entry, index, offset, buf, buf_len);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::WriteData(EntryImpl* entry, int index, int offset,
|
||||
net::IOBuffer* buf, int buf_len,
|
||||
bool truncate,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->WriteData(entry, index, offset, buf, buf_len, truncate);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::ReadSparseData(
|
||||
EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->ReadSparseData(entry, offset, buf, buf_len);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::WriteSparseData(
|
||||
EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->WriteSparseData(entry, offset, buf, buf_len);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::GetAvailableRange(
|
||||
EntryImpl* entry,
|
||||
int64_t offset,
|
||||
int len,
|
||||
int64_t* start,
|
||||
const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->GetAvailableRange(entry, offset, len, start);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::CancelSparseIO(EntryImpl* entry) {
|
||||
scoped_refptr<BackendIO> operation(
|
||||
new BackendIO(this, backend_, net::CompletionCallback()));
|
||||
operation->CancelSparseIO(entry);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::ReadyForSparseIO(
|
||||
EntryImpl* entry, const net::CompletionCallback& callback) {
|
||||
scoped_refptr<BackendIO> operation(new BackendIO(this, backend_, callback));
|
||||
operation->ReadyForSparseIO(entry);
|
||||
PostOperation(FROM_HERE, operation.get());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::WaitForPendingIO() {
|
||||
InFlightIO::WaitForPendingIO();
|
||||
}
|
||||
|
||||
void InFlightBackendIO::OnOperationComplete(BackgroundIO* operation,
|
||||
bool cancel) {
|
||||
BackendIO* op = static_cast<BackendIO*>(operation);
|
||||
op->OnDone(cancel);
|
||||
|
||||
if (!op->callback().is_null() && (!cancel || op->IsEntryOperation()))
|
||||
op->callback().Run(op->result());
|
||||
}
|
||||
|
||||
void InFlightBackendIO::PostOperation(const base::Location& from_here,
|
||||
BackendIO* operation) {
|
||||
background_thread_->PostTask(
|
||||
from_here, base::Bind(&BackendIO::ExecuteOperation, operation));
|
||||
OnOperationPosted(operation);
|
||||
}
|
||||
|
||||
base::WeakPtr<InFlightBackendIO> InFlightBackendIO::GetWeakPtr() {
|
||||
return ptr_factory_.GetWeakPtr();
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
243
net/disk_cache/blockfile/in_flight_backend_io.h
Normal file
243
net/disk_cache/blockfile/in_flight_backend_io.h
Normal file
@@ -0,0 +1,243 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_IN_FLIGHT_BACKEND_IO_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_IN_FLIGHT_BACKEND_IO_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/base/completion_callback.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
#include "net/disk_cache/blockfile/in_flight_io.h"
|
||||
#include "net/disk_cache/blockfile/rankings.h"
|
||||
|
||||
namespace base {
|
||||
class Location;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class BackendImpl;
|
||||
class Entry;
|
||||
class EntryImpl;
|
||||
|
||||
// This class represents a single asynchronous disk cache IO operation while it
|
||||
// is being bounced between threads.
|
||||
class BackendIO : public BackgroundIO {
|
||||
public:
|
||||
BackendIO(InFlightIO* controller, BackendImpl* backend,
|
||||
const net::CompletionCallback& callback);
|
||||
|
||||
// Runs the actual operation on the background thread.
|
||||
void ExecuteOperation();
|
||||
|
||||
// Callback implementation.
|
||||
void OnIOComplete(int result);
|
||||
|
||||
// Called when we are finishing this operation. If |cancel| is true, the user
|
||||
// callback will not be invoked.
|
||||
void OnDone(bool cancel);
|
||||
|
||||
// Returns true if this operation is directed to an entry (vs. the backend).
|
||||
bool IsEntryOperation();
|
||||
|
||||
net::CompletionCallback callback() const { return callback_; }
|
||||
|
||||
// The operations we proxy:
|
||||
void Init();
|
||||
void OpenEntry(const std::string& key, Entry** entry);
|
||||
void CreateEntry(const std::string& key, Entry** entry);
|
||||
void DoomEntry(const std::string& key);
|
||||
void DoomAllEntries();
|
||||
void DoomEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time);
|
||||
void DoomEntriesSince(const base::Time initial_time);
|
||||
void CalculateSizeOfAllEntries();
|
||||
void OpenNextEntry(Rankings::Iterator* iterator, Entry** next_entry);
|
||||
void EndEnumeration(std::unique_ptr<Rankings::Iterator> iterator);
|
||||
void OnExternalCacheHit(const std::string& key);
|
||||
void CloseEntryImpl(EntryImpl* entry);
|
||||
void DoomEntryImpl(EntryImpl* entry);
|
||||
void FlushQueue(); // Dummy operation.
|
||||
void RunTask(const base::Closure& task);
|
||||
void ReadData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
|
||||
int buf_len);
|
||||
void WriteData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
|
||||
int buf_len, bool truncate);
|
||||
void ReadSparseData(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len);
|
||||
void WriteSparseData(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len);
|
||||
void GetAvailableRange(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
int len,
|
||||
int64_t* start);
|
||||
void CancelSparseIO(EntryImpl* entry);
|
||||
void ReadyForSparseIO(EntryImpl* entry);
|
||||
|
||||
private:
|
||||
// There are two types of operations to proxy: regular backend operations are
|
||||
// executed sequentially (queued by the message loop). On the other hand,
|
||||
// operations targeted to a given entry can be long lived and support multiple
|
||||
// simultaneous users (multiple reads or writes to the same entry), and they
|
||||
// are subject to throttling, so we keep an explicit queue.
|
||||
enum Operation {
|
||||
OP_NONE = 0,
|
||||
OP_INIT,
|
||||
OP_OPEN,
|
||||
OP_CREATE,
|
||||
OP_DOOM,
|
||||
OP_DOOM_ALL,
|
||||
OP_DOOM_BETWEEN,
|
||||
OP_DOOM_SINCE,
|
||||
OP_SIZE_ALL,
|
||||
OP_OPEN_NEXT,
|
||||
OP_END_ENUMERATION,
|
||||
OP_ON_EXTERNAL_CACHE_HIT,
|
||||
OP_CLOSE_ENTRY,
|
||||
OP_DOOM_ENTRY,
|
||||
OP_FLUSH_QUEUE,
|
||||
OP_RUN_TASK,
|
||||
OP_MAX_BACKEND,
|
||||
OP_READ,
|
||||
OP_WRITE,
|
||||
OP_READ_SPARSE,
|
||||
OP_WRITE_SPARSE,
|
||||
OP_GET_RANGE,
|
||||
OP_CANCEL_IO,
|
||||
OP_IS_READY
|
||||
};
|
||||
|
||||
~BackendIO() override;
|
||||
|
||||
// Returns true if this operation returns an entry.
|
||||
bool ReturnsEntry();
|
||||
|
||||
// Returns the time that has passed since the operation was created.
|
||||
base::TimeDelta ElapsedTime() const;
|
||||
|
||||
void ExecuteBackendOperation();
|
||||
void ExecuteEntryOperation();
|
||||
|
||||
BackendImpl* backend_;
|
||||
net::CompletionCallback callback_;
|
||||
Operation operation_;
|
||||
|
||||
// The arguments of all the operations we proxy:
|
||||
std::string key_;
|
||||
Entry** entry_ptr_;
|
||||
base::Time initial_time_;
|
||||
base::Time end_time_;
|
||||
Rankings::Iterator* iterator_;
|
||||
std::unique_ptr<Rankings::Iterator> scoped_iterator_;
|
||||
EntryImpl* entry_;
|
||||
int index_;
|
||||
int offset_;
|
||||
scoped_refptr<net::IOBuffer> buf_;
|
||||
int buf_len_;
|
||||
bool truncate_;
|
||||
int64_t offset64_;
|
||||
int64_t* start_;
|
||||
base::TimeTicks start_time_;
|
||||
base::Closure task_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BackendIO);
|
||||
};
|
||||
|
||||
// The specialized controller that keeps track of current operations.
|
||||
class InFlightBackendIO : public InFlightIO {
|
||||
public:
|
||||
InFlightBackendIO(
|
||||
BackendImpl* backend,
|
||||
const scoped_refptr<base::SingleThreadTaskRunner>& background_thread);
|
||||
~InFlightBackendIO() override;
|
||||
|
||||
// Proxied operations.
|
||||
void Init(const net::CompletionCallback& callback);
|
||||
void OpenEntry(const std::string& key, Entry** entry,
|
||||
const net::CompletionCallback& callback);
|
||||
void CreateEntry(const std::string& key, Entry** entry,
|
||||
const net::CompletionCallback& callback);
|
||||
void DoomEntry(const std::string& key,
|
||||
const net::CompletionCallback& callback);
|
||||
void DoomAllEntries(const net::CompletionCallback& callback);
|
||||
void DoomEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time,
|
||||
const net::CompletionCallback& callback);
|
||||
void DoomEntriesSince(const base::Time initial_time,
|
||||
const net::CompletionCallback& callback);
|
||||
void CalculateSizeOfAllEntries(const net::CompletionCallback& callback);
|
||||
void OpenNextEntry(Rankings::Iterator* iterator, Entry** next_entry,
|
||||
const net::CompletionCallback& callback);
|
||||
void EndEnumeration(std::unique_ptr<Rankings::Iterator> iterator);
|
||||
void OnExternalCacheHit(const std::string& key);
|
||||
void CloseEntryImpl(EntryImpl* entry);
|
||||
void DoomEntryImpl(EntryImpl* entry);
|
||||
void FlushQueue(const net::CompletionCallback& callback);
|
||||
void RunTask(const base::Closure& task,
|
||||
const net::CompletionCallback& callback);
|
||||
void ReadData(EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
|
||||
int buf_len, const net::CompletionCallback& callback);
|
||||
void WriteData(
|
||||
EntryImpl* entry, int index, int offset, net::IOBuffer* buf,
|
||||
int buf_len, bool truncate, const net::CompletionCallback& callback);
|
||||
void ReadSparseData(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const net::CompletionCallback& callback);
|
||||
void WriteSparseData(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const net::CompletionCallback& callback);
|
||||
void GetAvailableRange(EntryImpl* entry,
|
||||
int64_t offset,
|
||||
int len,
|
||||
int64_t* start,
|
||||
const net::CompletionCallback& callback);
|
||||
void CancelSparseIO(EntryImpl* entry);
|
||||
void ReadyForSparseIO(EntryImpl* entry,
|
||||
const net::CompletionCallback& callback);
|
||||
|
||||
// Blocks until all operations are cancelled or completed.
|
||||
void WaitForPendingIO();
|
||||
|
||||
scoped_refptr<base::SingleThreadTaskRunner> background_thread() {
|
||||
return background_thread_;
|
||||
}
|
||||
|
||||
// Returns true if the current sequence is the background thread.
|
||||
bool BackgroundIsCurrentSequence() {
|
||||
return background_thread_->RunsTasksInCurrentSequence();
|
||||
}
|
||||
|
||||
base::WeakPtr<InFlightBackendIO> GetWeakPtr();
|
||||
|
||||
protected:
|
||||
void OnOperationComplete(BackgroundIO* operation, bool cancel) override;
|
||||
|
||||
private:
|
||||
void PostOperation(const base::Location& from_here, BackendIO* operation);
|
||||
BackendImpl* backend_;
|
||||
scoped_refptr<base::SingleThreadTaskRunner> background_thread_;
|
||||
base::WeakPtrFactory<InFlightBackendIO> ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InFlightBackendIO);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_IN_FLIGHT_BACKEND_IO_H_
|
||||
110
net/disk_cache/blockfile/in_flight_io.cc
Normal file
110
net/disk_cache/blockfile/in_flight_io.cc
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/in_flight_io.h"
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/location.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/task_runner.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
BackgroundIO::BackgroundIO(InFlightIO* controller)
|
||||
: result_(-1),
|
||||
io_completed_(base::WaitableEvent::ResetPolicy::MANUAL,
|
||||
base::WaitableEvent::InitialState::NOT_SIGNALED),
|
||||
controller_(controller) {}
|
||||
|
||||
// Runs on the primary thread.
|
||||
void BackgroundIO::OnIOSignalled() {
|
||||
if (controller_)
|
||||
controller_->InvokeCallback(this, false);
|
||||
}
|
||||
|
||||
void BackgroundIO::Cancel() {
|
||||
// controller_ may be in use from the background thread at this time.
|
||||
base::AutoLock lock(controller_lock_);
|
||||
DCHECK(controller_);
|
||||
controller_ = NULL;
|
||||
}
|
||||
|
||||
BackgroundIO::~BackgroundIO() = default;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
InFlightIO::InFlightIO()
|
||||
: callback_task_runner_(base::ThreadTaskRunnerHandle::Get()),
|
||||
running_(false) {}
|
||||
|
||||
InFlightIO::~InFlightIO() = default;
|
||||
|
||||
// Runs on the background thread.
|
||||
void BackgroundIO::NotifyController() {
|
||||
base::AutoLock lock(controller_lock_);
|
||||
if (controller_)
|
||||
controller_->OnIOComplete(this);
|
||||
}
|
||||
|
||||
void InFlightIO::WaitForPendingIO() {
|
||||
while (!io_list_.empty()) {
|
||||
// Block the current thread until all pending IO completes.
|
||||
IOList::iterator it = io_list_.begin();
|
||||
InvokeCallback(it->get(), true);
|
||||
}
|
||||
}
|
||||
|
||||
void InFlightIO::DropPendingIO() {
|
||||
while (!io_list_.empty()) {
|
||||
IOList::iterator it = io_list_.begin();
|
||||
BackgroundIO* operation = it->get();
|
||||
operation->Cancel();
|
||||
DCHECK(io_list_.find(operation) != io_list_.end());
|
||||
io_list_.erase(base::WrapRefCounted(operation));
|
||||
}
|
||||
}
|
||||
|
||||
// Runs in a background sequence.
|
||||
void InFlightIO::OnIOComplete(BackgroundIO* operation) {
|
||||
#if DCHECK_IS_ON()
|
||||
if (callback_task_runner_->RunsTasksInCurrentSequence()) {
|
||||
DCHECK(single_thread_ || !running_);
|
||||
single_thread_ = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
callback_task_runner_->PostTask(
|
||||
FROM_HERE, base::Bind(&BackgroundIO::OnIOSignalled, operation));
|
||||
operation->io_completed()->Signal();
|
||||
}
|
||||
|
||||
// Runs on the primary thread.
|
||||
void InFlightIO::InvokeCallback(BackgroundIO* operation, bool cancel_task) {
|
||||
{
|
||||
// http://crbug.com/74623
|
||||
base::ThreadRestrictions::ScopedAllowWait allow_wait;
|
||||
operation->io_completed()->Wait();
|
||||
}
|
||||
running_ = true;
|
||||
|
||||
if (cancel_task)
|
||||
operation->Cancel();
|
||||
|
||||
// Make sure that we remove the operation from the list before invoking the
|
||||
// callback (so that a subsequent cancel does not invoke the callback again).
|
||||
DCHECK(io_list_.find(operation) != io_list_.end());
|
||||
DCHECK(!operation->HasOneRef());
|
||||
io_list_.erase(base::WrapRefCounted(operation));
|
||||
OnOperationComplete(operation, cancel_task);
|
||||
}
|
||||
|
||||
// Runs on the primary thread.
|
||||
void InFlightIO::OnOperationPosted(BackgroundIO* operation) {
|
||||
DCHECK(callback_task_runner_->RunsTasksInCurrentSequence());
|
||||
io_list_.insert(base::WrapRefCounted(operation));
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
144
net/disk_cache/blockfile/in_flight_io.h
Normal file
144
net/disk_cache/blockfile/in_flight_io.h
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_IN_FLIGHT_IO_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_IN_FLIGHT_IO_H_
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/synchronization/waitable_event.h"
|
||||
|
||||
namespace base {
|
||||
class TaskRunner;
|
||||
} // namespace base
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class InFlightIO;
|
||||
|
||||
// This class represents a single asynchronous IO operation while it is being
|
||||
// bounced between threads.
|
||||
class BackgroundIO : public base::RefCountedThreadSafe<BackgroundIO> {
|
||||
public:
|
||||
// Other than the actual parameters for the IO operation (including the
|
||||
// |callback| that must be notified at the end), we need the controller that
|
||||
// is keeping track of all operations. When done, we notify the controller
|
||||
// (we do NOT invoke the callback), in the worker thead that completed the
|
||||
// operation.
|
||||
explicit BackgroundIO(InFlightIO* controller);
|
||||
|
||||
// This method signals the controller that this operation is finished, in the
|
||||
// original thread. In practice, this is a RunableMethod that allows
|
||||
// cancellation.
|
||||
void OnIOSignalled();
|
||||
|
||||
// Allows the cancellation of the task to notify the controller (step number 8
|
||||
// in the diagram below). In practice, if the controller waits for the
|
||||
// operation to finish it doesn't have to wait for the final task to be
|
||||
// processed by the message loop so calling this method prevents its delivery.
|
||||
// Note that this method is not intended to cancel the actual IO operation or
|
||||
// to prevent the first notification to take place (OnIOComplete).
|
||||
void Cancel();
|
||||
|
||||
int result() { return result_; }
|
||||
|
||||
base::WaitableEvent* io_completed() {
|
||||
return &io_completed_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~BackgroundIO();
|
||||
|
||||
// Notifies the controller about the end of the operation, from the background
|
||||
// thread.
|
||||
void NotifyController();
|
||||
|
||||
int result_; // Final operation result.
|
||||
|
||||
private:
|
||||
friend class base::RefCountedThreadSafe<BackgroundIO>;
|
||||
|
||||
// An event to signal when the operation completes.
|
||||
base::WaitableEvent io_completed_;
|
||||
InFlightIO* controller_; // The controller that tracks all operations.
|
||||
base::Lock controller_lock_; // A lock protecting clearing of controller_.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BackgroundIO);
|
||||
};
|
||||
|
||||
// This class keeps track of asynchronous IO operations. A single instance
|
||||
// of this class is meant to be used to start an asynchronous operation (using
|
||||
// PostXX, exposed by a derived class). This class will post the operation to a
|
||||
// worker thread, hanlde the notification when the operation finishes and
|
||||
// perform the callback on the same thread that was used to start the operation.
|
||||
//
|
||||
// The regular sequence of calls is:
|
||||
// Thread_1 Worker_thread
|
||||
// 1. DerivedInFlightIO::PostXX()
|
||||
// 2. -> PostTask ->
|
||||
// 3. InFlightIO::OnOperationPosted()
|
||||
// 4. DerivedBackgroundIO::XX()
|
||||
// 5. IO operation completes
|
||||
// 6. InFlightIO::OnIOComplete()
|
||||
// 7. <- PostTask <-
|
||||
// 8. BackgroundIO::OnIOSignalled()
|
||||
// 9. InFlightIO::InvokeCallback()
|
||||
// 10. DerivedInFlightIO::OnOperationComplete()
|
||||
// 11. invoke callback
|
||||
//
|
||||
// Shutdown is a special case that is handled though WaitForPendingIO() instead
|
||||
// of just waiting for step 7.
|
||||
class InFlightIO {
|
||||
public:
|
||||
InFlightIO();
|
||||
virtual ~InFlightIO();
|
||||
|
||||
// Blocks the current thread until all IO operations tracked by this object
|
||||
// complete.
|
||||
void WaitForPendingIO();
|
||||
|
||||
// Drops current pending operations without waiting for them to complete.
|
||||
void DropPendingIO();
|
||||
|
||||
// Called on a background thread when |operation| completes.
|
||||
void OnIOComplete(BackgroundIO* operation);
|
||||
|
||||
// Invokes the users' completion callback at the end of the IO operation.
|
||||
// |cancel_task| is true if the actual task posted to the thread is still
|
||||
// queued (because we are inside WaitForPendingIO), and false if said task is
|
||||
// the one performing the call.
|
||||
void InvokeCallback(BackgroundIO* operation, bool cancel_task);
|
||||
|
||||
protected:
|
||||
// This method is called to signal the completion of the |operation|. |cancel|
|
||||
// is true if the operation is being cancelled. This method is called on the
|
||||
// thread that created this object.
|
||||
virtual void OnOperationComplete(BackgroundIO* operation, bool cancel) = 0;
|
||||
|
||||
// Signals this object that the derived class just posted the |operation| to
|
||||
// be executed on a background thread. This method must be called on the same
|
||||
// thread used to create this object.
|
||||
void OnOperationPosted(BackgroundIO* operation);
|
||||
|
||||
private:
|
||||
typedef std::set<scoped_refptr<BackgroundIO> > IOList;
|
||||
|
||||
IOList io_list_; // List of pending, in-flight io operations.
|
||||
scoped_refptr<base::TaskRunner> callback_task_runner_;
|
||||
|
||||
bool running_; // True after the first posted operation completes.
|
||||
#if DCHECK_IS_ON()
|
||||
bool single_thread_ = false; // True if we only have one thread.
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(InFlightIO);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_IN_FLIGHT_IO_H_
|
||||
31
net/disk_cache/blockfile/mapped_file.cc
Normal file
31
net/disk_cache/blockfile/mapped_file.cc
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/mapped_file.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Note: Most of this class is implemented in platform-specific files.
|
||||
|
||||
bool MappedFile::Load(const FileBlock* block) {
|
||||
size_t offset = block->offset() + view_size_;
|
||||
return Read(block->buffer(), block->size(), offset);
|
||||
}
|
||||
|
||||
bool MappedFile::Store(const FileBlock* block) {
|
||||
size_t offset = block->offset() + view_size_;
|
||||
return Write(block->buffer(), block->size(), offset);
|
||||
}
|
||||
|
||||
bool MappedFile::Preload() {
|
||||
size_t file_len = GetLength();
|
||||
std::unique_ptr<char[]> buf(new char[file_len]);
|
||||
if (!Read(buf.get(), file_len, 0))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
} // namespace disk_cache
|
||||
81
net/disk_cache/blockfile/mapped_file.h
Normal file
81
net/disk_cache/blockfile/mapped_file.h
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface of the cache.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_MAPPED_FILE_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_MAPPED_FILE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/file.h"
|
||||
#include "net/disk_cache/blockfile/file_block.h"
|
||||
#include "net/net_features.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This class implements a memory mapped file used to access block-files. The
|
||||
// idea is that the header and bitmap will be memory mapped all the time, and
|
||||
// the actual data for the blocks will be access asynchronously (most of the
|
||||
// time).
|
||||
class NET_EXPORT_PRIVATE MappedFile : public File {
|
||||
public:
|
||||
MappedFile() : File(true), init_(false) {}
|
||||
|
||||
// Performs object initialization. name is the file to use, and size is the
|
||||
// amount of data to memory map from the file. If size is 0, the whole file
|
||||
// will be mapped in memory.
|
||||
void* Init(const base::FilePath& name, size_t size);
|
||||
|
||||
void* buffer() const {
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
// Loads or stores a given block from the backing file (synchronously).
|
||||
bool Load(const FileBlock* block);
|
||||
bool Store(const FileBlock* block);
|
||||
|
||||
// Flush the memory-mapped section to disk (synchronously).
|
||||
void Flush();
|
||||
|
||||
// Heats up the file system cache and make sure the file is fully
|
||||
// readable (synchronously).
|
||||
bool Preload();
|
||||
|
||||
private:
|
||||
~MappedFile() override;
|
||||
|
||||
bool init_;
|
||||
#if defined(OS_WIN)
|
||||
HANDLE section_;
|
||||
#endif
|
||||
void* buffer_; // Address of the memory mapped buffer.
|
||||
size_t view_size_; // Size of the memory pointed by buffer_.
|
||||
#if BUILDFLAG(POSIX_AVOID_MMAP)
|
||||
void* snapshot_; // Copy of the buffer taken when it was last flushed.
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MappedFile);
|
||||
};
|
||||
|
||||
// Helper class for calling Flush() on exit from the current scope.
|
||||
class ScopedFlush {
|
||||
public:
|
||||
explicit ScopedFlush(MappedFile* file) : file_(file) {}
|
||||
~ScopedFlush() {
|
||||
file_->Flush();
|
||||
}
|
||||
private:
|
||||
MappedFile* file_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_MAPPED_FILE_H_
|
||||
63
net/disk_cache/blockfile/mapped_file_avoid_mmap_posix.cc
Normal file
63
net/disk_cache/blockfile/mapped_file_avoid_mmap_posix.cc
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/mapped_file.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
void* MappedFile::Init(const base::FilePath& name, size_t size) {
|
||||
DCHECK(!init_);
|
||||
if (init_ || !File::Init(name))
|
||||
return NULL;
|
||||
|
||||
if (!size)
|
||||
size = GetLength();
|
||||
|
||||
buffer_ = malloc(size);
|
||||
snapshot_ = malloc(size);
|
||||
if (buffer_ && snapshot_ && Read(buffer_, size, 0)) {
|
||||
memcpy(snapshot_, buffer_, size);
|
||||
} else {
|
||||
free(buffer_);
|
||||
free(snapshot_);
|
||||
buffer_ = snapshot_ = 0;
|
||||
}
|
||||
|
||||
init_ = true;
|
||||
view_size_ = size;
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
void MappedFile::Flush() {
|
||||
DCHECK(buffer_);
|
||||
DCHECK(snapshot_);
|
||||
const char* buffer_ptr = static_cast<const char*>(buffer_);
|
||||
char* snapshot_ptr = static_cast<char*>(snapshot_);
|
||||
const size_t block_size = 4096;
|
||||
for (size_t offset = 0; offset < view_size_; offset += block_size) {
|
||||
size_t size = std::min(view_size_ - offset, block_size);
|
||||
if (memcmp(snapshot_ptr + offset, buffer_ptr + offset, size)) {
|
||||
memcpy(snapshot_ptr + offset, buffer_ptr + offset, size);
|
||||
Write(snapshot_ptr + offset, size, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MappedFile::~MappedFile() {
|
||||
if (!init_)
|
||||
return;
|
||||
|
||||
if (buffer_ && snapshot_) {
|
||||
Flush();
|
||||
}
|
||||
free(buffer_);
|
||||
free(snapshot_);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
54
net/disk_cache/blockfile/mapped_file_posix.cc
Normal file
54
net/disk_cache/blockfile/mapped_file_posix.cc
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/mapped_file.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
void* MappedFile::Init(const base::FilePath& name, size_t size) {
|
||||
DCHECK(!init_);
|
||||
if (init_ || !File::Init(name))
|
||||
return NULL;
|
||||
|
||||
size_t temp_len = size ? size : 4096;
|
||||
if (!size)
|
||||
size = GetLength();
|
||||
|
||||
buffer_ = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
platform_file(), 0);
|
||||
init_ = true;
|
||||
view_size_ = size;
|
||||
DPLOG_IF(FATAL, buffer_ == MAP_FAILED) << "Failed to mmap " << name.value();
|
||||
if (buffer_ == MAP_FAILED)
|
||||
buffer_ = 0;
|
||||
|
||||
// Make sure we detect hardware failures reading the headers.
|
||||
std::unique_ptr<char[]> temp(new char[temp_len]);
|
||||
if (!Read(temp.get(), temp_len, 0))
|
||||
return NULL;
|
||||
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
void MappedFile::Flush() {
|
||||
}
|
||||
|
||||
MappedFile::~MappedFile() {
|
||||
if (!init_)
|
||||
return;
|
||||
|
||||
if (buffer_) {
|
||||
int ret = munmap(buffer_, view_size_);
|
||||
DCHECK_EQ(0, ret);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
56
net/disk_cache/blockfile/mapped_file_win.cc
Normal file
56
net/disk_cache/blockfile/mapped_file_win.cc
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/mapped_file.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
void* MappedFile::Init(const base::FilePath& name, size_t size) {
|
||||
DCHECK(!init_);
|
||||
if (init_ || !File::Init(name))
|
||||
return NULL;
|
||||
|
||||
buffer_ = NULL;
|
||||
init_ = true;
|
||||
section_ = CreateFileMapping(platform_file(), NULL, PAGE_READWRITE, 0,
|
||||
static_cast<DWORD>(size), NULL);
|
||||
if (!section_)
|
||||
return NULL;
|
||||
|
||||
buffer_ = MapViewOfFile(section_, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size);
|
||||
DCHECK(buffer_);
|
||||
view_size_ = size;
|
||||
|
||||
// Make sure we detect hardware failures reading the headers.
|
||||
size_t temp_len = size ? size : 4096;
|
||||
std::unique_ptr<char[]> temp(new char[temp_len]);
|
||||
if (!Read(temp.get(), temp_len, 0))
|
||||
return NULL;
|
||||
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
MappedFile::~MappedFile() {
|
||||
if (!init_)
|
||||
return;
|
||||
|
||||
if (buffer_) {
|
||||
BOOL ret = UnmapViewOfFile(buffer_);
|
||||
DCHECK(ret);
|
||||
}
|
||||
|
||||
if (section_)
|
||||
CloseHandle(section_);
|
||||
}
|
||||
|
||||
void MappedFile::Flush() {
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
930
net/disk_cache/blockfile/rankings.cc
Normal file
930
net/disk_cache/blockfile/rankings.cc
Normal file
@@ -0,0 +1,930 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/rankings.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/backend_impl.h"
|
||||
#include "net/disk_cache/blockfile/disk_format.h"
|
||||
#include "net/disk_cache/blockfile/entry_impl.h"
|
||||
#include "net/disk_cache/blockfile/errors.h"
|
||||
#include "net/disk_cache/blockfile/histogram_macros.h"
|
||||
#include "net/disk_cache/blockfile/stress_support.h"
|
||||
|
||||
// Provide a BackendImpl object to macros from histogram_macros.h.
|
||||
#define CACHE_UMA_BACKEND_IMPL_OBJ backend_
|
||||
|
||||
using base::Time;
|
||||
using base::TimeTicks;
|
||||
|
||||
namespace disk_cache {
|
||||
// This is used by crash_cache.exe to generate unit test files.
|
||||
NET_EXPORT_PRIVATE RankCrashes g_rankings_crash = NO_CRASH;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
enum Operation {
|
||||
INSERT = 1,
|
||||
REMOVE
|
||||
};
|
||||
|
||||
// This class provides a simple lock for the LRU list of rankings. Whenever an
|
||||
// entry is to be inserted or removed from the list, a transaction object should
|
||||
// be created to keep track of the operation. If the process crashes before
|
||||
// finishing the operation, the transaction record (stored as part of the user
|
||||
// data on the file header) can be used to finish the operation.
|
||||
class Transaction {
|
||||
public:
|
||||
// addr is the cache addres of the node being inserted or removed. We want to
|
||||
// avoid having the compiler doing optimizations on when to read or write
|
||||
// from user_data because it is the basis of the crash detection. Maybe
|
||||
// volatile is not enough for that, but it should be a good hint.
|
||||
Transaction(volatile disk_cache::LruData* data, disk_cache::Addr addr,
|
||||
Operation op, int list);
|
||||
~Transaction();
|
||||
private:
|
||||
volatile disk_cache::LruData* data_;
|
||||
DISALLOW_COPY_AND_ASSIGN(Transaction);
|
||||
};
|
||||
|
||||
Transaction::Transaction(volatile disk_cache::LruData* data,
|
||||
disk_cache::Addr addr, Operation op, int list)
|
||||
: data_(data) {
|
||||
DCHECK(!data_->transaction);
|
||||
DCHECK(addr.is_initialized());
|
||||
data_->operation = op;
|
||||
data_->operation_list = list;
|
||||
data_->transaction = addr.value();
|
||||
}
|
||||
|
||||
Transaction::~Transaction() {
|
||||
DCHECK(data_->transaction);
|
||||
data_->transaction = 0;
|
||||
data_->operation = 0;
|
||||
data_->operation_list = 0;
|
||||
}
|
||||
|
||||
// Code locations that can generate crashes.
|
||||
enum CrashLocation {
|
||||
ON_INSERT_1, ON_INSERT_2, ON_INSERT_3, ON_INSERT_4, ON_REMOVE_1, ON_REMOVE_2,
|
||||
ON_REMOVE_3, ON_REMOVE_4, ON_REMOVE_5, ON_REMOVE_6, ON_REMOVE_7, ON_REMOVE_8
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
void TerminateSelf() {
|
||||
#if defined(OS_WIN)
|
||||
// Windows does more work on _exit() than we would like, so we force exit.
|
||||
TerminateProcess(GetCurrentProcess(), 0);
|
||||
#elif defined(OS_POSIX)
|
||||
// On POSIX, _exit() will terminate the process with minimal cleanup,
|
||||
// and it is cleaner than killing.
|
||||
_exit(0);
|
||||
#endif
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
// Generates a crash on debug builds, acording to the value of g_rankings_crash.
|
||||
// This used by crash_cache.exe to generate unit-test files.
|
||||
void GenerateCrash(CrashLocation location) {
|
||||
#ifndef NDEBUG
|
||||
if (disk_cache::NO_CRASH == disk_cache::g_rankings_crash)
|
||||
return;
|
||||
switch (location) {
|
||||
case ON_INSERT_1:
|
||||
switch (disk_cache::g_rankings_crash) {
|
||||
case disk_cache::INSERT_ONE_1:
|
||||
case disk_cache::INSERT_LOAD_1:
|
||||
TerminateSelf();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ON_INSERT_2:
|
||||
if (disk_cache::INSERT_EMPTY_1 == disk_cache::g_rankings_crash)
|
||||
TerminateSelf();
|
||||
break;
|
||||
case ON_INSERT_3:
|
||||
switch (disk_cache::g_rankings_crash) {
|
||||
case disk_cache::INSERT_EMPTY_2:
|
||||
case disk_cache::INSERT_ONE_2:
|
||||
case disk_cache::INSERT_LOAD_2:
|
||||
TerminateSelf();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ON_INSERT_4:
|
||||
switch (disk_cache::g_rankings_crash) {
|
||||
case disk_cache::INSERT_EMPTY_3:
|
||||
case disk_cache::INSERT_ONE_3:
|
||||
TerminateSelf();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ON_REMOVE_1:
|
||||
switch (disk_cache::g_rankings_crash) {
|
||||
case disk_cache::REMOVE_ONE_1:
|
||||
case disk_cache::REMOVE_HEAD_1:
|
||||
case disk_cache::REMOVE_TAIL_1:
|
||||
case disk_cache::REMOVE_LOAD_1:
|
||||
TerminateSelf();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ON_REMOVE_2:
|
||||
if (disk_cache::REMOVE_ONE_2 == disk_cache::g_rankings_crash)
|
||||
TerminateSelf();
|
||||
break;
|
||||
case ON_REMOVE_3:
|
||||
if (disk_cache::REMOVE_ONE_3 == disk_cache::g_rankings_crash)
|
||||
TerminateSelf();
|
||||
break;
|
||||
case ON_REMOVE_4:
|
||||
if (disk_cache::REMOVE_HEAD_2 == disk_cache::g_rankings_crash)
|
||||
TerminateSelf();
|
||||
break;
|
||||
case ON_REMOVE_5:
|
||||
if (disk_cache::REMOVE_TAIL_2 == disk_cache::g_rankings_crash)
|
||||
TerminateSelf();
|
||||
break;
|
||||
case ON_REMOVE_6:
|
||||
if (disk_cache::REMOVE_TAIL_3 == disk_cache::g_rankings_crash)
|
||||
TerminateSelf();
|
||||
break;
|
||||
case ON_REMOVE_7:
|
||||
switch (disk_cache::g_rankings_crash) {
|
||||
case disk_cache::REMOVE_ONE_4:
|
||||
case disk_cache::REMOVE_LOAD_2:
|
||||
case disk_cache::REMOVE_HEAD_3:
|
||||
TerminateSelf();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ON_REMOVE_8:
|
||||
switch (disk_cache::g_rankings_crash) {
|
||||
case disk_cache::REMOVE_HEAD_4:
|
||||
case disk_cache::REMOVE_LOAD_3:
|
||||
TerminateSelf();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
// Update the timestamp fields of |node|.
|
||||
void UpdateTimes(disk_cache::CacheRankingsBlock* node, bool modified) {
|
||||
base::Time now = base::Time::Now();
|
||||
node->Data()->last_used = now.ToInternalValue();
|
||||
if (modified)
|
||||
node->Data()->last_modified = now.ToInternalValue();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
Rankings::ScopedRankingsBlock::ScopedRankingsBlock() : rankings_(NULL) {}
|
||||
|
||||
Rankings::ScopedRankingsBlock::ScopedRankingsBlock(Rankings* rankings)
|
||||
: rankings_(rankings) {}
|
||||
|
||||
Rankings::ScopedRankingsBlock::ScopedRankingsBlock(Rankings* rankings,
|
||||
CacheRankingsBlock* node)
|
||||
: std::unique_ptr<CacheRankingsBlock>(node), rankings_(rankings) {}
|
||||
|
||||
Rankings::Iterator::Iterator() {
|
||||
memset(this, 0, sizeof(Iterator));
|
||||
}
|
||||
|
||||
void Rankings::Iterator::Reset() {
|
||||
if (my_rankings) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
ScopedRankingsBlock(my_rankings, nodes[i]);
|
||||
}
|
||||
memset(this, 0, sizeof(Iterator));
|
||||
}
|
||||
|
||||
Rankings::Rankings() : init_(false) {}
|
||||
|
||||
Rankings::~Rankings() = default;
|
||||
|
||||
bool Rankings::Init(BackendImpl* backend, bool count_lists) {
|
||||
DCHECK(!init_);
|
||||
if (init_)
|
||||
return false;
|
||||
|
||||
backend_ = backend;
|
||||
control_data_ = backend_->GetLruData();
|
||||
count_lists_ = count_lists;
|
||||
|
||||
ReadHeads();
|
||||
ReadTails();
|
||||
|
||||
if (control_data_->transaction)
|
||||
CompleteTransaction();
|
||||
|
||||
init_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Rankings::Reset() {
|
||||
init_ = false;
|
||||
for (int i = 0; i < LAST_ELEMENT; i++) {
|
||||
heads_[i].set_value(0);
|
||||
tails_[i].set_value(0);
|
||||
}
|
||||
control_data_ = NULL;
|
||||
}
|
||||
|
||||
void Rankings::Insert(CacheRankingsBlock* node, bool modified, List list) {
|
||||
Trace("Insert 0x%x l %d", node->address().value(), list);
|
||||
DCHECK(node->HasData());
|
||||
Addr& my_head = heads_[list];
|
||||
Addr& my_tail = tails_[list];
|
||||
Transaction lock(control_data_, node->address(), INSERT, list);
|
||||
CacheRankingsBlock head(backend_->File(my_head), my_head);
|
||||
if (my_head.is_initialized()) {
|
||||
if (!GetRanking(&head))
|
||||
return;
|
||||
|
||||
if (head.Data()->prev != my_head.value() && // Normal path.
|
||||
head.Data()->prev != node->address().value()) { // FinishInsert().
|
||||
backend_->CriticalError(ERR_INVALID_LINKS);
|
||||
return;
|
||||
}
|
||||
|
||||
head.Data()->prev = node->address().value();
|
||||
head.Store();
|
||||
GenerateCrash(ON_INSERT_1);
|
||||
UpdateIterators(&head);
|
||||
}
|
||||
|
||||
node->Data()->next = my_head.value();
|
||||
node->Data()->prev = node->address().value();
|
||||
my_head.set_value(node->address().value());
|
||||
|
||||
if (!my_tail.is_initialized() || my_tail.value() == node->address().value()) {
|
||||
my_tail.set_value(node->address().value());
|
||||
node->Data()->next = my_tail.value();
|
||||
WriteTail(list);
|
||||
GenerateCrash(ON_INSERT_2);
|
||||
}
|
||||
|
||||
UpdateTimes(node, modified);
|
||||
node->Store();
|
||||
GenerateCrash(ON_INSERT_3);
|
||||
|
||||
// The last thing to do is move our head to point to a node already stored.
|
||||
WriteHead(list);
|
||||
IncrementCounter(list);
|
||||
GenerateCrash(ON_INSERT_4);
|
||||
backend_->FlushIndex();
|
||||
}
|
||||
|
||||
// If a, b and r are elements on the list, and we want to remove r, the possible
|
||||
// states for the objects if a crash happens are (where y(x, z) means for object
|
||||
// y, prev is x and next is z):
|
||||
// A. One element:
|
||||
// 1. r(r, r), head(r), tail(r) initial state
|
||||
// 2. r(r, r), head(0), tail(r) WriteHead()
|
||||
// 3. r(r, r), head(0), tail(0) WriteTail()
|
||||
// 4. r(0, 0), head(0), tail(0) next.Store()
|
||||
//
|
||||
// B. Remove a random element:
|
||||
// 1. a(x, r), r(a, b), b(r, y), head(x), tail(y) initial state
|
||||
// 2. a(x, r), r(a, b), b(a, y), head(x), tail(y) next.Store()
|
||||
// 3. a(x, b), r(a, b), b(a, y), head(x), tail(y) prev.Store()
|
||||
// 4. a(x, b), r(0, 0), b(a, y), head(x), tail(y) node.Store()
|
||||
//
|
||||
// C. Remove head:
|
||||
// 1. r(r, b), b(r, y), head(r), tail(y) initial state
|
||||
// 2. r(r, b), b(r, y), head(b), tail(y) WriteHead()
|
||||
// 3. r(r, b), b(b, y), head(b), tail(y) next.Store()
|
||||
// 4. r(0, 0), b(b, y), head(b), tail(y) prev.Store()
|
||||
//
|
||||
// D. Remove tail:
|
||||
// 1. a(x, r), r(a, r), head(x), tail(r) initial state
|
||||
// 2. a(x, r), r(a, r), head(x), tail(a) WriteTail()
|
||||
// 3. a(x, a), r(a, r), head(x), tail(a) prev.Store()
|
||||
// 4. a(x, a), r(0, 0), head(x), tail(a) next.Store()
|
||||
void Rankings::Remove(CacheRankingsBlock* node, List list, bool strict) {
|
||||
Trace("Remove 0x%x (0x%x 0x%x) l %d", node->address().value(),
|
||||
node->Data()->next, node->Data()->prev, list);
|
||||
DCHECK(node->HasData());
|
||||
if (strict)
|
||||
InvalidateIterators(node);
|
||||
|
||||
Addr next_addr(node->Data()->next);
|
||||
Addr prev_addr(node->Data()->prev);
|
||||
if (!next_addr.is_initialized() || next_addr.is_separate_file() ||
|
||||
!prev_addr.is_initialized() || prev_addr.is_separate_file()) {
|
||||
if (next_addr.is_initialized() || prev_addr.is_initialized()) {
|
||||
LOG(ERROR) << "Invalid rankings info.";
|
||||
STRESS_NOTREACHED();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CacheRankingsBlock next(backend_->File(next_addr), next_addr);
|
||||
CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr);
|
||||
if (!GetRanking(&next) || !GetRanking(&prev)) {
|
||||
STRESS_NOTREACHED();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckLinks(node, &prev, &next, &list))
|
||||
return;
|
||||
|
||||
Transaction lock(control_data_, node->address(), REMOVE, list);
|
||||
prev.Data()->next = next.address().value();
|
||||
next.Data()->prev = prev.address().value();
|
||||
GenerateCrash(ON_REMOVE_1);
|
||||
|
||||
CacheAddr node_value = node->address().value();
|
||||
Addr& my_head = heads_[list];
|
||||
Addr& my_tail = tails_[list];
|
||||
if (node_value == my_head.value() || node_value == my_tail.value()) {
|
||||
if (my_head.value() == my_tail.value()) {
|
||||
my_head.set_value(0);
|
||||
my_tail.set_value(0);
|
||||
|
||||
WriteHead(list);
|
||||
GenerateCrash(ON_REMOVE_2);
|
||||
WriteTail(list);
|
||||
GenerateCrash(ON_REMOVE_3);
|
||||
} else if (node_value == my_head.value()) {
|
||||
my_head.set_value(next.address().value());
|
||||
next.Data()->prev = next.address().value();
|
||||
|
||||
WriteHead(list);
|
||||
GenerateCrash(ON_REMOVE_4);
|
||||
} else if (node_value == my_tail.value()) {
|
||||
my_tail.set_value(prev.address().value());
|
||||
prev.Data()->next = prev.address().value();
|
||||
|
||||
WriteTail(list);
|
||||
GenerateCrash(ON_REMOVE_5);
|
||||
|
||||
// Store the new tail to make sure we can undo the operation if we crash.
|
||||
prev.Store();
|
||||
GenerateCrash(ON_REMOVE_6);
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes out of the list can be identified by invalid pointers.
|
||||
node->Data()->next = 0;
|
||||
node->Data()->prev = 0;
|
||||
|
||||
// The last thing to get to disk is the node itself, so before that there is
|
||||
// enough info to recover.
|
||||
next.Store();
|
||||
GenerateCrash(ON_REMOVE_7);
|
||||
prev.Store();
|
||||
GenerateCrash(ON_REMOVE_8);
|
||||
node->Store();
|
||||
DecrementCounter(list);
|
||||
UpdateIterators(&next);
|
||||
UpdateIterators(&prev);
|
||||
backend_->FlushIndex();
|
||||
}
|
||||
|
||||
// A crash in between Remove and Insert will lead to a dirty entry not on the
|
||||
// list. We want to avoid that case as much as we can (as while waiting for IO),
|
||||
// but the net effect is just an assert on debug when attempting to remove the
|
||||
// entry. Otherwise we'll need reentrant transactions, which is an overkill.
|
||||
void Rankings::UpdateRank(CacheRankingsBlock* node, bool modified, List list) {
|
||||
Addr& my_head = heads_[list];
|
||||
if (my_head.value() == node->address().value()) {
|
||||
UpdateTimes(node, modified);
|
||||
node->set_modified();
|
||||
return;
|
||||
}
|
||||
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
Remove(node, list, true);
|
||||
Insert(node, modified, list);
|
||||
CACHE_UMA(AGE_MS, "UpdateRank", 0, start);
|
||||
}
|
||||
|
||||
CacheRankingsBlock* Rankings::GetNext(CacheRankingsBlock* node, List list) {
|
||||
ScopedRankingsBlock next(this);
|
||||
if (!node) {
|
||||
Addr& my_head = heads_[list];
|
||||
if (!my_head.is_initialized())
|
||||
return NULL;
|
||||
next.reset(new CacheRankingsBlock(backend_->File(my_head), my_head));
|
||||
} else {
|
||||
if (!node->HasData())
|
||||
node->Load();
|
||||
Addr& my_tail = tails_[list];
|
||||
if (!my_tail.is_initialized())
|
||||
return NULL;
|
||||
if (my_tail.value() == node->address().value())
|
||||
return NULL;
|
||||
Addr address(node->Data()->next);
|
||||
if (address.value() == node->address().value())
|
||||
return NULL; // Another tail? fail it.
|
||||
next.reset(new CacheRankingsBlock(backend_->File(address), address));
|
||||
}
|
||||
|
||||
TrackRankingsBlock(next.get(), true);
|
||||
|
||||
if (!GetRanking(next.get()))
|
||||
return NULL;
|
||||
|
||||
ConvertToLongLived(next.get());
|
||||
if (node && !CheckSingleLink(node, next.get()))
|
||||
return NULL;
|
||||
|
||||
return next.release();
|
||||
}
|
||||
|
||||
CacheRankingsBlock* Rankings::GetPrev(CacheRankingsBlock* node, List list) {
|
||||
ScopedRankingsBlock prev(this);
|
||||
if (!node) {
|
||||
Addr& my_tail = tails_[list];
|
||||
if (!my_tail.is_initialized())
|
||||
return NULL;
|
||||
prev.reset(new CacheRankingsBlock(backend_->File(my_tail), my_tail));
|
||||
} else {
|
||||
if (!node->HasData())
|
||||
node->Load();
|
||||
Addr& my_head = heads_[list];
|
||||
if (!my_head.is_initialized())
|
||||
return NULL;
|
||||
if (my_head.value() == node->address().value())
|
||||
return NULL;
|
||||
Addr address(node->Data()->prev);
|
||||
if (address.value() == node->address().value())
|
||||
return NULL; // Another head? fail it.
|
||||
prev.reset(new CacheRankingsBlock(backend_->File(address), address));
|
||||
}
|
||||
|
||||
TrackRankingsBlock(prev.get(), true);
|
||||
|
||||
if (!GetRanking(prev.get()))
|
||||
return NULL;
|
||||
|
||||
ConvertToLongLived(prev.get());
|
||||
if (node && !CheckSingleLink(prev.get(), node))
|
||||
return NULL;
|
||||
|
||||
return prev.release();
|
||||
}
|
||||
|
||||
void Rankings::FreeRankingsBlock(CacheRankingsBlock* node) {
|
||||
TrackRankingsBlock(node, false);
|
||||
}
|
||||
|
||||
void Rankings::TrackRankingsBlock(CacheRankingsBlock* node,
|
||||
bool start_tracking) {
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
IteratorPair current(node->address().value(), node);
|
||||
|
||||
if (start_tracking)
|
||||
iterators_.push_back(current);
|
||||
else
|
||||
iterators_.remove(current);
|
||||
}
|
||||
|
||||
int Rankings::SelfCheck() {
|
||||
int total = 0;
|
||||
int error = 0;
|
||||
for (int i = 0; i < LAST_ELEMENT; i++) {
|
||||
int partial = CheckList(static_cast<List>(i));
|
||||
if (partial < 0 && !error)
|
||||
error = partial;
|
||||
else if (partial > 0)
|
||||
total += partial;
|
||||
}
|
||||
|
||||
return error ? error : total;
|
||||
}
|
||||
|
||||
bool Rankings::SanityCheck(CacheRankingsBlock* node, bool from_list) const {
|
||||
if (!node->VerifyHash())
|
||||
return false;
|
||||
|
||||
const RankingsNode* data = node->Data();
|
||||
|
||||
if ((!data->next && data->prev) || (data->next && !data->prev))
|
||||
return false;
|
||||
|
||||
// Both pointers on zero is a node out of the list.
|
||||
if (!data->next && !data->prev && from_list)
|
||||
return false;
|
||||
|
||||
List list = NO_USE; // Initialize it to something.
|
||||
if ((node->address().value() == data->prev) && !IsHead(data->prev, &list))
|
||||
return false;
|
||||
|
||||
if ((node->address().value() == data->next) && !IsTail(data->next, &list))
|
||||
return false;
|
||||
|
||||
if (!data->next && !data->prev)
|
||||
return true;
|
||||
|
||||
Addr next_addr(data->next);
|
||||
Addr prev_addr(data->prev);
|
||||
if (!next_addr.SanityCheck() || next_addr.file_type() != RANKINGS ||
|
||||
!prev_addr.SanityCheck() || prev_addr.file_type() != RANKINGS)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Rankings::DataSanityCheck(CacheRankingsBlock* node, bool from_list) const {
|
||||
const RankingsNode* data = node->Data();
|
||||
if (!data->contents)
|
||||
return false;
|
||||
|
||||
// It may have never been inserted.
|
||||
if (from_list && (!data->last_used || !data->last_modified))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Rankings::SetContents(CacheRankingsBlock* node, CacheAddr address) {
|
||||
node->Data()->contents = address;
|
||||
node->Store();
|
||||
}
|
||||
|
||||
void Rankings::ReadHeads() {
|
||||
for (int i = 0; i < LAST_ELEMENT; i++)
|
||||
heads_[i] = Addr(control_data_->heads[i]);
|
||||
}
|
||||
|
||||
void Rankings::ReadTails() {
|
||||
for (int i = 0; i < LAST_ELEMENT; i++)
|
||||
tails_[i] = Addr(control_data_->tails[i]);
|
||||
}
|
||||
|
||||
void Rankings::WriteHead(List list) {
|
||||
control_data_->heads[list] = heads_[list].value();
|
||||
}
|
||||
|
||||
void Rankings::WriteTail(List list) {
|
||||
control_data_->tails[list] = tails_[list].value();
|
||||
}
|
||||
|
||||
bool Rankings::GetRanking(CacheRankingsBlock* rankings) {
|
||||
if (!rankings->address().is_initialized())
|
||||
return false;
|
||||
|
||||
TimeTicks start = TimeTicks::Now();
|
||||
if (!rankings->Load())
|
||||
return false;
|
||||
|
||||
if (!SanityCheck(rankings, true)) {
|
||||
backend_->CriticalError(ERR_INVALID_LINKS);
|
||||
return false;
|
||||
}
|
||||
|
||||
backend_->OnEvent(Stats::OPEN_RANKINGS);
|
||||
|
||||
// Note that if the cache is in read_only mode, open entries are not marked
|
||||
// as dirty, except when an entry is doomed. We have to look for open entries.
|
||||
if (!backend_->read_only() && !rankings->Data()->dirty)
|
||||
return true;
|
||||
|
||||
EntryImpl* entry = backend_->GetOpenEntry(rankings);
|
||||
if (!entry) {
|
||||
if (backend_->read_only())
|
||||
return true;
|
||||
|
||||
// We cannot trust this entry, but we cannot initiate a cleanup from this
|
||||
// point (we may be in the middle of a cleanup already). The entry will be
|
||||
// deleted when detected from a regular open/create path.
|
||||
rankings->Data()->dirty = backend_->GetCurrentEntryId() - 1;
|
||||
if (!rankings->Data()->dirty)
|
||||
rankings->Data()->dirty--;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note that we should not leave this module without deleting rankings first.
|
||||
rankings->SetData(entry->rankings()->Data());
|
||||
|
||||
CACHE_UMA(AGE_MS, "GetRankings", 0, start);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Rankings::ConvertToLongLived(CacheRankingsBlock* rankings) {
|
||||
if (rankings->own_data())
|
||||
return;
|
||||
|
||||
// We cannot return a shared node because we are not keeping a reference
|
||||
// to the entry that owns the buffer. Make this node a copy of the one that
|
||||
// we have, and let the iterator logic update it when the entry changes.
|
||||
CacheRankingsBlock temp(NULL, Addr(0));
|
||||
*temp.Data() = *rankings->Data();
|
||||
rankings->StopSharingData();
|
||||
*rankings->Data() = *temp.Data();
|
||||
}
|
||||
|
||||
void Rankings::CompleteTransaction() {
|
||||
Addr node_addr(static_cast<CacheAddr>(control_data_->transaction));
|
||||
if (!node_addr.is_initialized() || node_addr.is_separate_file()) {
|
||||
NOTREACHED();
|
||||
LOG(ERROR) << "Invalid rankings info.";
|
||||
return;
|
||||
}
|
||||
|
||||
Trace("CompleteTransaction 0x%x", node_addr.value());
|
||||
|
||||
CacheRankingsBlock node(backend_->File(node_addr), node_addr);
|
||||
if (!node.Load())
|
||||
return;
|
||||
|
||||
node.Store();
|
||||
|
||||
Addr& my_head = heads_[control_data_->operation_list];
|
||||
Addr& my_tail = tails_[control_data_->operation_list];
|
||||
|
||||
// We want to leave the node inside the list. The entry must me marked as
|
||||
// dirty, and will be removed later. Otherwise, we'll get assertions when
|
||||
// attempting to remove the dirty entry.
|
||||
if (INSERT == control_data_->operation) {
|
||||
Trace("FinishInsert h:0x%x t:0x%x", my_head.value(), my_tail.value());
|
||||
FinishInsert(&node);
|
||||
} else if (REMOVE == control_data_->operation) {
|
||||
Trace("RevertRemove h:0x%x t:0x%x", my_head.value(), my_tail.value());
|
||||
RevertRemove(&node);
|
||||
} else {
|
||||
NOTREACHED();
|
||||
LOG(ERROR) << "Invalid operation to recover.";
|
||||
}
|
||||
}
|
||||
|
||||
void Rankings::FinishInsert(CacheRankingsBlock* node) {
|
||||
control_data_->transaction = 0;
|
||||
control_data_->operation = 0;
|
||||
Addr& my_head = heads_[control_data_->operation_list];
|
||||
Addr& my_tail = tails_[control_data_->operation_list];
|
||||
if (my_head.value() != node->address().value()) {
|
||||
if (my_tail.value() == node->address().value()) {
|
||||
// This part will be skipped by the logic of Insert.
|
||||
node->Data()->next = my_tail.value();
|
||||
}
|
||||
|
||||
Insert(node, true, static_cast<List>(control_data_->operation_list));
|
||||
}
|
||||
|
||||
// Tell the backend about this entry.
|
||||
backend_->RecoveredEntry(node);
|
||||
}
|
||||
|
||||
void Rankings::RevertRemove(CacheRankingsBlock* node) {
|
||||
Addr next_addr(node->Data()->next);
|
||||
Addr prev_addr(node->Data()->prev);
|
||||
if (!next_addr.is_initialized() || !prev_addr.is_initialized()) {
|
||||
// The operation actually finished. Nothing to do.
|
||||
control_data_->transaction = 0;
|
||||
return;
|
||||
}
|
||||
if (next_addr.is_separate_file() || prev_addr.is_separate_file()) {
|
||||
NOTREACHED();
|
||||
LOG(WARNING) << "Invalid rankings info.";
|
||||
control_data_->transaction = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
CacheRankingsBlock next(backend_->File(next_addr), next_addr);
|
||||
CacheRankingsBlock prev(backend_->File(prev_addr), prev_addr);
|
||||
if (!next.Load() || !prev.Load())
|
||||
return;
|
||||
|
||||
CacheAddr node_value = node->address().value();
|
||||
DCHECK(prev.Data()->next == node_value ||
|
||||
prev.Data()->next == prev_addr.value() ||
|
||||
prev.Data()->next == next.address().value());
|
||||
DCHECK(next.Data()->prev == node_value ||
|
||||
next.Data()->prev == next_addr.value() ||
|
||||
next.Data()->prev == prev.address().value());
|
||||
|
||||
if (node_value != prev_addr.value())
|
||||
prev.Data()->next = node_value;
|
||||
if (node_value != next_addr.value())
|
||||
next.Data()->prev = node_value;
|
||||
|
||||
List my_list = static_cast<List>(control_data_->operation_list);
|
||||
Addr& my_head = heads_[my_list];
|
||||
Addr& my_tail = tails_[my_list];
|
||||
if (!my_head.is_initialized() || !my_tail.is_initialized()) {
|
||||
my_head.set_value(node_value);
|
||||
my_tail.set_value(node_value);
|
||||
WriteHead(my_list);
|
||||
WriteTail(my_list);
|
||||
} else if (my_head.value() == next.address().value()) {
|
||||
my_head.set_value(node_value);
|
||||
prev.Data()->next = next.address().value();
|
||||
WriteHead(my_list);
|
||||
} else if (my_tail.value() == prev.address().value()) {
|
||||
my_tail.set_value(node_value);
|
||||
next.Data()->prev = prev.address().value();
|
||||
WriteTail(my_list);
|
||||
}
|
||||
|
||||
next.Store();
|
||||
prev.Store();
|
||||
control_data_->transaction = 0;
|
||||
control_data_->operation = 0;
|
||||
backend_->FlushIndex();
|
||||
}
|
||||
|
||||
bool Rankings::CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev,
|
||||
CacheRankingsBlock* next, List* list) {
|
||||
CacheAddr node_addr = node->address().value();
|
||||
if (prev->Data()->next == node_addr &&
|
||||
next->Data()->prev == node_addr) {
|
||||
// A regular linked node.
|
||||
return true;
|
||||
}
|
||||
|
||||
Trace("CheckLinks 0x%x (0x%x 0x%x)", node_addr,
|
||||
prev->Data()->next, next->Data()->prev);
|
||||
|
||||
if (node_addr != prev->address().value() &&
|
||||
node_addr != next->address().value() &&
|
||||
prev->Data()->next == next->address().value() &&
|
||||
next->Data()->prev == prev->address().value()) {
|
||||
// The list is actually ok, node is wrong.
|
||||
Trace("node 0x%x out of list %d", node_addr, list);
|
||||
node->Data()->next = 0;
|
||||
node->Data()->prev = 0;
|
||||
node->Store();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prev->Data()->next == node_addr ||
|
||||
next->Data()->prev == node_addr) {
|
||||
// Only one link is weird, lets double check.
|
||||
if (prev->Data()->next != node_addr && IsHead(node_addr, list))
|
||||
return true;
|
||||
|
||||
if (next->Data()->prev != node_addr && IsTail(node_addr, list))
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG(ERROR) << "Inconsistent LRU.";
|
||||
STRESS_NOTREACHED();
|
||||
|
||||
backend_->CriticalError(ERR_INVALID_LINKS);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Rankings::CheckSingleLink(CacheRankingsBlock* prev,
|
||||
CacheRankingsBlock* next) {
|
||||
if (prev->Data()->next != next->address().value() ||
|
||||
next->Data()->prev != prev->address().value()) {
|
||||
LOG(ERROR) << "Inconsistent LRU.";
|
||||
|
||||
backend_->CriticalError(ERR_INVALID_LINKS);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int Rankings::CheckList(List list) {
|
||||
Addr last1, last2;
|
||||
int head_items;
|
||||
int rv = CheckListSection(list, last1, last2, true, // Head to tail.
|
||||
&last1, &last2, &head_items);
|
||||
if (rv == ERR_NO_ERROR)
|
||||
return head_items;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Note that the returned error codes assume a forward walk (from head to tail)
|
||||
// so they have to be adjusted accordingly by the caller. We use two stop values
|
||||
// to be able to detect a corrupt node at the end that is not linked going back.
|
||||
int Rankings::CheckListSection(List list, Addr end1, Addr end2, bool forward,
|
||||
Addr* last, Addr* second_last, int* num_items) {
|
||||
Addr current = forward ? heads_[list] : tails_[list];
|
||||
*last = *second_last = current;
|
||||
*num_items = 0;
|
||||
if (!current.is_initialized())
|
||||
return ERR_NO_ERROR;
|
||||
|
||||
if (!current.SanityCheckForRankings())
|
||||
return ERR_INVALID_HEAD;
|
||||
|
||||
std::unique_ptr<CacheRankingsBlock> node;
|
||||
Addr prev_addr(current);
|
||||
do {
|
||||
node.reset(new CacheRankingsBlock(backend_->File(current), current));
|
||||
node->Load();
|
||||
if (!SanityCheck(node.get(), true))
|
||||
return ERR_INVALID_ENTRY;
|
||||
|
||||
CacheAddr next = forward ? node->Data()->next : node->Data()->prev;
|
||||
CacheAddr prev = forward ? node->Data()->prev : node->Data()->next;
|
||||
|
||||
if (prev != prev_addr.value())
|
||||
return ERR_INVALID_PREV;
|
||||
|
||||
Addr next_addr(next);
|
||||
if (!next_addr.SanityCheckForRankings())
|
||||
return ERR_INVALID_NEXT;
|
||||
|
||||
prev_addr = current;
|
||||
current = next_addr;
|
||||
*second_last = *last;
|
||||
*last = current;
|
||||
(*num_items)++;
|
||||
|
||||
if (next_addr == prev_addr) {
|
||||
Addr last = forward ? tails_[list] : heads_[list];
|
||||
if (next_addr == last)
|
||||
return ERR_NO_ERROR;
|
||||
return ERR_INVALID_TAIL;
|
||||
}
|
||||
} while (current != end1 && current != end2);
|
||||
return ERR_NO_ERROR;
|
||||
}
|
||||
|
||||
bool Rankings::IsHead(CacheAddr addr, List* list) const {
|
||||
for (int i = 0; i < LAST_ELEMENT; i++) {
|
||||
if (addr == heads_[i].value()) {
|
||||
if (*list != i)
|
||||
Trace("Changing list %d to %d", *list, i);
|
||||
*list = static_cast<List>(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Rankings::IsTail(CacheAddr addr, List* list) const {
|
||||
for (int i = 0; i < LAST_ELEMENT; i++) {
|
||||
if (addr == tails_[i].value()) {
|
||||
if (*list != i)
|
||||
Trace("Changing list %d to %d", *list, i);
|
||||
*list = static_cast<List>(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// We expect to have just a few iterators at any given time, maybe two or three,
|
||||
// But we could have more than one pointing at the same mode. We walk the list
|
||||
// of cache iterators and update all that are pointing to the given node.
|
||||
void Rankings::UpdateIterators(CacheRankingsBlock* node) {
|
||||
CacheAddr address = node->address().value();
|
||||
for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
|
||||
++it) {
|
||||
if (it->first == address && it->second->HasData()) {
|
||||
CacheRankingsBlock* other = it->second;
|
||||
*other->Data() = *node->Data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Rankings::InvalidateIterators(CacheRankingsBlock* node) {
|
||||
CacheAddr address = node->address().value();
|
||||
for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
|
||||
++it) {
|
||||
if (it->first == address)
|
||||
it->second->Discard();
|
||||
}
|
||||
}
|
||||
|
||||
void Rankings::IncrementCounter(List list) {
|
||||
if (!count_lists_)
|
||||
return;
|
||||
|
||||
DCHECK(control_data_->sizes[list] < std::numeric_limits<int32_t>::max());
|
||||
if (control_data_->sizes[list] < std::numeric_limits<int32_t>::max())
|
||||
control_data_->sizes[list]++;
|
||||
}
|
||||
|
||||
void Rankings::DecrementCounter(List list) {
|
||||
if (!count_lists_)
|
||||
return;
|
||||
|
||||
DCHECK(control_data_->sizes[list] > 0);
|
||||
if (control_data_->sizes[list] > 0)
|
||||
control_data_->sizes[list]--;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
215
net/disk_cache/blockfile/rankings.h
Normal file
215
net/disk_cache/blockfile/rankings.h
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_RANKINGS_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_RANKINGS_H_
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "net/disk_cache/blockfile/addr.h"
|
||||
#include "net/disk_cache/blockfile/mapped_file.h"
|
||||
#include "net/disk_cache/blockfile/storage_block.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class BackendImpl;
|
||||
struct LruData;
|
||||
struct RankingsNode;
|
||||
typedef StorageBlock<RankingsNode> CacheRankingsBlock;
|
||||
|
||||
// Type of crashes generated for the unit tests.
|
||||
enum RankCrashes {
|
||||
NO_CRASH = 0,
|
||||
INSERT_EMPTY_1,
|
||||
INSERT_EMPTY_2,
|
||||
INSERT_EMPTY_3,
|
||||
INSERT_ONE_1,
|
||||
INSERT_ONE_2,
|
||||
INSERT_ONE_3,
|
||||
INSERT_LOAD_1,
|
||||
INSERT_LOAD_2,
|
||||
REMOVE_ONE_1,
|
||||
REMOVE_ONE_2,
|
||||
REMOVE_ONE_3,
|
||||
REMOVE_ONE_4,
|
||||
REMOVE_HEAD_1,
|
||||
REMOVE_HEAD_2,
|
||||
REMOVE_HEAD_3,
|
||||
REMOVE_HEAD_4,
|
||||
REMOVE_TAIL_1,
|
||||
REMOVE_TAIL_2,
|
||||
REMOVE_TAIL_3,
|
||||
REMOVE_LOAD_1,
|
||||
REMOVE_LOAD_2,
|
||||
REMOVE_LOAD_3,
|
||||
MAX_CRASH
|
||||
};
|
||||
|
||||
// This class handles the ranking information for the cache.
|
||||
class Rankings {
|
||||
public:
|
||||
// Possible lists of entries.
|
||||
enum List {
|
||||
NO_USE = 0, // List of entries that have not been reused.
|
||||
LOW_USE, // List of entries with low reuse.
|
||||
HIGH_USE, // List of entries with high reuse.
|
||||
RESERVED, // Reserved for future use.
|
||||
DELETED, // List of recently deleted or doomed entries.
|
||||
LAST_ELEMENT
|
||||
};
|
||||
|
||||
// This class provides a specialized version of scoped_ptr, that calls
|
||||
// Rankings whenever a CacheRankingsBlock is deleted, to keep track of cache
|
||||
// iterators that may go stale.
|
||||
class ScopedRankingsBlock : public std::unique_ptr<CacheRankingsBlock> {
|
||||
public:
|
||||
ScopedRankingsBlock();
|
||||
explicit ScopedRankingsBlock(Rankings* rankings);
|
||||
ScopedRankingsBlock(Rankings* rankings, CacheRankingsBlock* node);
|
||||
|
||||
~ScopedRankingsBlock() {
|
||||
rankings_->FreeRankingsBlock(get());
|
||||
}
|
||||
|
||||
void set_rankings(Rankings* rankings) {
|
||||
rankings_ = rankings;
|
||||
}
|
||||
|
||||
// scoped_ptr::reset will delete the object.
|
||||
void reset(CacheRankingsBlock* p = NULL) {
|
||||
if (p != get())
|
||||
rankings_->FreeRankingsBlock(get());
|
||||
std::unique_ptr<CacheRankingsBlock>::reset(p);
|
||||
}
|
||||
|
||||
private:
|
||||
Rankings* rankings_;
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedRankingsBlock);
|
||||
};
|
||||
|
||||
// If we have multiple lists, we have to iterate through all at the same time.
|
||||
// This structure keeps track of where we are on the iteration.
|
||||
struct Iterator {
|
||||
Iterator();
|
||||
void Reset();
|
||||
|
||||
List list; // Which entry was returned to the user.
|
||||
CacheRankingsBlock* nodes[3]; // Nodes on the first three lists.
|
||||
Rankings* my_rankings;
|
||||
};
|
||||
|
||||
Rankings();
|
||||
~Rankings();
|
||||
|
||||
bool Init(BackendImpl* backend, bool count_lists);
|
||||
|
||||
// Restores original state, leaving the object ready for initialization.
|
||||
void Reset();
|
||||
|
||||
// Inserts a given entry at the head of the queue.
|
||||
void Insert(CacheRankingsBlock* node, bool modified, List list);
|
||||
|
||||
// Removes a given entry from the LRU list. If |strict| is true, this method
|
||||
// assumes that |node| is not pointed to by an active iterator. On the other
|
||||
// hand, removing that restriction allows the current "head" of an iterator
|
||||
// to be removed from the list (basically without control of the code that is
|
||||
// performing the iteration), so it should be used with extra care.
|
||||
void Remove(CacheRankingsBlock* node, List list, bool strict);
|
||||
|
||||
// Moves a given entry to the head.
|
||||
void UpdateRank(CacheRankingsBlock* node, bool modified, List list);
|
||||
|
||||
// Iterates through the list.
|
||||
CacheRankingsBlock* GetNext(CacheRankingsBlock* node, List list);
|
||||
CacheRankingsBlock* GetPrev(CacheRankingsBlock* node, List list);
|
||||
void FreeRankingsBlock(CacheRankingsBlock* node);
|
||||
|
||||
// Controls tracking of nodes used for enumerations.
|
||||
void TrackRankingsBlock(CacheRankingsBlock* node, bool start_tracking);
|
||||
|
||||
// Peforms a simple self-check of the lists, and returns the number of items
|
||||
// or an error code (negative value).
|
||||
int SelfCheck();
|
||||
|
||||
// Returns false if the entry is clearly invalid. from_list is true if the
|
||||
// node comes from the LRU list.
|
||||
bool SanityCheck(CacheRankingsBlock* node, bool from_list) const;
|
||||
bool DataSanityCheck(CacheRankingsBlock* node, bool from_list) const;
|
||||
|
||||
// Sets the |contents| field of |node| to |address|.
|
||||
void SetContents(CacheRankingsBlock* node, CacheAddr address);
|
||||
|
||||
private:
|
||||
typedef std::pair<CacheAddr, CacheRankingsBlock*> IteratorPair;
|
||||
typedef std::list<IteratorPair> IteratorList;
|
||||
|
||||
void ReadHeads();
|
||||
void ReadTails();
|
||||
void WriteHead(List list);
|
||||
void WriteTail(List list);
|
||||
|
||||
// Gets the rankings information for a given rankings node. We may end up
|
||||
// sharing the actual memory with a loaded entry, but we are not taking a
|
||||
// reference to that entry, so |rankings| must be short lived.
|
||||
bool GetRanking(CacheRankingsBlock* rankings);
|
||||
|
||||
// Makes |rankings| suitable to live a long life.
|
||||
void ConvertToLongLived(CacheRankingsBlock* rankings);
|
||||
|
||||
// Finishes a list modification after a crash.
|
||||
void CompleteTransaction();
|
||||
void FinishInsert(CacheRankingsBlock* rankings);
|
||||
void RevertRemove(CacheRankingsBlock* rankings);
|
||||
|
||||
// Returns false if node is not properly linked. This method may change the
|
||||
// provided |list| to reflect the list where this node is actually stored.
|
||||
bool CheckLinks(CacheRankingsBlock* node, CacheRankingsBlock* prev,
|
||||
CacheRankingsBlock* next, List* list);
|
||||
|
||||
// Checks the links between two consecutive nodes.
|
||||
bool CheckSingleLink(CacheRankingsBlock* prev, CacheRankingsBlock* next);
|
||||
|
||||
// Peforms a simple check of the list, and returns the number of items or an
|
||||
// error code (negative value).
|
||||
int CheckList(List list);
|
||||
|
||||
// Walks a list in the desired direction until the nodes |end1| or |end2| are
|
||||
// reached. Returns an error code (0 on success), the number of items verified
|
||||
// and the addresses of the last nodes visited.
|
||||
int CheckListSection(List list, Addr end1, Addr end2, bool forward,
|
||||
Addr* last, Addr* second_last, int* num_items);
|
||||
|
||||
// Returns true if addr is the head or tail of any list. When there is a
|
||||
// match |list| will contain the list number for |addr|.
|
||||
bool IsHead(CacheAddr addr, List* list) const;
|
||||
bool IsTail(CacheAddr addr, List* list) const;
|
||||
|
||||
// Updates the iterators whenever node is being changed.
|
||||
void UpdateIterators(CacheRankingsBlock* node);
|
||||
|
||||
// Invalidates the iterators pointing to this node.
|
||||
void InvalidateIterators(CacheRankingsBlock* node);
|
||||
|
||||
// Keeps track of the number of entries on a list.
|
||||
void IncrementCounter(List list);
|
||||
void DecrementCounter(List list);
|
||||
|
||||
bool init_;
|
||||
bool count_lists_;
|
||||
Addr heads_[LAST_ELEMENT];
|
||||
Addr tails_[LAST_ELEMENT];
|
||||
BackendImpl* backend_;
|
||||
LruData* control_data_; // Data related to the LRU lists.
|
||||
IteratorList iterators_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Rankings);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_RANKINGS_H_
|
||||
906
net/disk_cache/blockfile/sparse_control.cc
Normal file
906
net/disk_cache/blockfile/sparse_control.cc
Normal file
@@ -0,0 +1,906 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/sparse_control.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/format_macros.h"
|
||||
#include "base/location.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/blockfile/backend_impl.h"
|
||||
#include "net/disk_cache/blockfile/entry_impl.h"
|
||||
#include "net/disk_cache/blockfile/file.h"
|
||||
#include "net/disk_cache/net_log_parameters.h"
|
||||
#include "net/log/net_log.h"
|
||||
#include "net/log/net_log_event_type.h"
|
||||
#include "net/log/net_log_with_source.h"
|
||||
|
||||
using base::Time;
|
||||
|
||||
namespace {
|
||||
|
||||
// Stream of the sparse data index.
|
||||
const int kSparseIndex = 2;
|
||||
|
||||
// Stream of the sparse data.
|
||||
const int kSparseData = 1;
|
||||
|
||||
// We can have up to 64k children.
|
||||
const int kMaxMapSize = 8 * 1024;
|
||||
|
||||
// The maximum number of bytes that a child can store.
|
||||
const int kMaxEntrySize = 0x100000;
|
||||
|
||||
// The size of each data block (tracked by the child allocation bitmap).
|
||||
const int kBlockSize = 1024;
|
||||
|
||||
// Returns the name of a child entry given the base_name and signature of the
|
||||
// parent and the child_id.
|
||||
// If the entry is called entry_name, child entries will be named something
|
||||
// like Range_entry_name:XXX:YYY where XXX is the entry signature and YYY is the
|
||||
// number of the particular child.
|
||||
std::string GenerateChildName(const std::string& base_name,
|
||||
int64_t signature,
|
||||
int64_t child_id) {
|
||||
return base::StringPrintf("Range_%s:%" PRIx64 ":%" PRIx64, base_name.c_str(),
|
||||
signature, child_id);
|
||||
}
|
||||
|
||||
// This class deletes the children of a sparse entry.
|
||||
class ChildrenDeleter
|
||||
: public base::RefCounted<ChildrenDeleter>,
|
||||
public disk_cache::FileIOCallback {
|
||||
public:
|
||||
ChildrenDeleter(disk_cache::BackendImpl* backend, const std::string& name)
|
||||
: backend_(backend->GetWeakPtr()), name_(name), signature_(0) {}
|
||||
|
||||
void OnFileIOComplete(int bytes_copied) override;
|
||||
|
||||
// Two ways of deleting the children: if we have the children map, use Start()
|
||||
// directly, otherwise pass the data address to ReadData().
|
||||
void Start(char* buffer, int len);
|
||||
void ReadData(disk_cache::Addr address, int len);
|
||||
|
||||
private:
|
||||
friend class base::RefCounted<ChildrenDeleter>;
|
||||
~ChildrenDeleter() override = default;
|
||||
|
||||
void DeleteChildren();
|
||||
|
||||
base::WeakPtr<disk_cache::BackendImpl> backend_;
|
||||
std::string name_;
|
||||
disk_cache::Bitmap children_map_;
|
||||
int64_t signature_;
|
||||
std::unique_ptr<char[]> buffer_;
|
||||
DISALLOW_COPY_AND_ASSIGN(ChildrenDeleter);
|
||||
};
|
||||
|
||||
// This is the callback of the file operation.
|
||||
void ChildrenDeleter::OnFileIOComplete(int bytes_copied) {
|
||||
char* buffer = buffer_.release();
|
||||
Start(buffer, bytes_copied);
|
||||
}
|
||||
|
||||
void ChildrenDeleter::Start(char* buffer, int len) {
|
||||
buffer_.reset(buffer);
|
||||
if (len < static_cast<int>(sizeof(disk_cache::SparseData)))
|
||||
return Release();
|
||||
|
||||
// Just copy the information from |buffer|, delete |buffer| and start deleting
|
||||
// the child entries.
|
||||
disk_cache::SparseData* data =
|
||||
reinterpret_cast<disk_cache::SparseData*>(buffer);
|
||||
signature_ = data->header.signature;
|
||||
|
||||
int num_bits = (len - sizeof(disk_cache::SparseHeader)) * 8;
|
||||
children_map_.Resize(num_bits, false);
|
||||
children_map_.SetMap(data->bitmap, num_bits / 32);
|
||||
buffer_.reset();
|
||||
|
||||
DeleteChildren();
|
||||
}
|
||||
|
||||
void ChildrenDeleter::ReadData(disk_cache::Addr address, int len) {
|
||||
DCHECK(address.is_block_file());
|
||||
if (!backend_.get())
|
||||
return Release();
|
||||
|
||||
disk_cache::File* file(backend_->File(address));
|
||||
if (!file)
|
||||
return Release();
|
||||
|
||||
size_t file_offset = address.start_block() * address.BlockSize() +
|
||||
disk_cache::kBlockHeaderSize;
|
||||
|
||||
buffer_.reset(new char[len]);
|
||||
bool completed;
|
||||
if (!file->Read(buffer_.get(), len, file_offset, this, &completed))
|
||||
return Release();
|
||||
|
||||
if (completed)
|
||||
OnFileIOComplete(len);
|
||||
|
||||
// And wait until OnFileIOComplete gets called.
|
||||
}
|
||||
|
||||
void ChildrenDeleter::DeleteChildren() {
|
||||
int child_id = 0;
|
||||
if (!children_map_.FindNextSetBit(&child_id) || !backend_.get()) {
|
||||
// We are done. Just delete this object.
|
||||
return Release();
|
||||
}
|
||||
std::string child_name = GenerateChildName(name_, signature_, child_id);
|
||||
backend_->SyncDoomEntry(child_name);
|
||||
children_map_.Set(child_id, false);
|
||||
|
||||
// Post a task to delete the next child.
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE, base::Bind(&ChildrenDeleter::DeleteChildren, this));
|
||||
}
|
||||
|
||||
// Returns the NetLog event type corresponding to a SparseOperation.
|
||||
net::NetLogEventType GetSparseEventType(
|
||||
disk_cache::SparseControl::SparseOperation operation) {
|
||||
switch (operation) {
|
||||
case disk_cache::SparseControl::kReadOperation:
|
||||
return net::NetLogEventType::SPARSE_READ;
|
||||
case disk_cache::SparseControl::kWriteOperation:
|
||||
return net::NetLogEventType::SPARSE_WRITE;
|
||||
case disk_cache::SparseControl::kGetRangeOperation:
|
||||
return net::NetLogEventType::SPARSE_GET_RANGE;
|
||||
default:
|
||||
NOTREACHED();
|
||||
return net::NetLogEventType::CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
// Logs the end event for |operation| on a child entry. Range operations log
|
||||
// no events for each child they search through.
|
||||
void LogChildOperationEnd(const net::NetLogWithSource& net_log,
|
||||
disk_cache::SparseControl::SparseOperation operation,
|
||||
int result) {
|
||||
if (net_log.IsCapturing()) {
|
||||
net::NetLogEventType event_type;
|
||||
switch (operation) {
|
||||
case disk_cache::SparseControl::kReadOperation:
|
||||
event_type = net::NetLogEventType::SPARSE_READ_CHILD_DATA;
|
||||
break;
|
||||
case disk_cache::SparseControl::kWriteOperation:
|
||||
event_type = net::NetLogEventType::SPARSE_WRITE_CHILD_DATA;
|
||||
break;
|
||||
case disk_cache::SparseControl::kGetRangeOperation:
|
||||
return;
|
||||
default:
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
net_log.EndEventWithNetErrorCode(event_type, result);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace.
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
SparseControl::SparseControl(EntryImpl* entry)
|
||||
: entry_(entry),
|
||||
child_(NULL),
|
||||
operation_(kNoOperation),
|
||||
pending_(false),
|
||||
finished_(false),
|
||||
init_(false),
|
||||
range_found_(false),
|
||||
abort_(false),
|
||||
child_map_(child_data_.bitmap, kNumSparseBits, kNumSparseBits / 32),
|
||||
offset_(0),
|
||||
buf_len_(0),
|
||||
child_offset_(0),
|
||||
child_len_(0),
|
||||
result_(0) {
|
||||
memset(&sparse_header_, 0, sizeof(sparse_header_));
|
||||
memset(&child_data_, 0, sizeof(child_data_));
|
||||
}
|
||||
|
||||
SparseControl::~SparseControl() {
|
||||
if (child_)
|
||||
CloseChild();
|
||||
if (init_)
|
||||
WriteSparseData();
|
||||
}
|
||||
|
||||
int SparseControl::Init() {
|
||||
DCHECK(!init_);
|
||||
|
||||
// We should not have sparse data for the exposed entry.
|
||||
if (entry_->GetDataSize(kSparseData))
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
// Now see if there is something where we store our data.
|
||||
int rv = net::OK;
|
||||
int data_len = entry_->GetDataSize(kSparseIndex);
|
||||
if (!data_len) {
|
||||
rv = CreateSparseEntry();
|
||||
} else {
|
||||
rv = OpenSparseEntry(data_len);
|
||||
}
|
||||
|
||||
if (rv == net::OK)
|
||||
init_ = true;
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool SparseControl::CouldBeSparse() const {
|
||||
DCHECK(!init_);
|
||||
|
||||
if (entry_->GetDataSize(kSparseData))
|
||||
return false;
|
||||
|
||||
// We don't verify the data, just see if it could be there.
|
||||
return (entry_->GetDataSize(kSparseIndex) != 0);
|
||||
}
|
||||
|
||||
int SparseControl::StartIO(SparseOperation op,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) {
|
||||
DCHECK(init_);
|
||||
// We don't support simultaneous IO for sparse data.
|
||||
if (operation_ != kNoOperation)
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
if (offset < 0 || buf_len < 0)
|
||||
return net::ERR_INVALID_ARGUMENT;
|
||||
|
||||
// We only support up to 64 GB.
|
||||
if (static_cast<uint64_t>(offset) + static_cast<unsigned int>(buf_len) >=
|
||||
UINT64_C(0x1000000000)) {
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
DCHECK(!user_buf_.get());
|
||||
DCHECK(user_callback_.is_null());
|
||||
|
||||
if (!buf && (op == kReadOperation || op == kWriteOperation))
|
||||
return 0;
|
||||
|
||||
// Copy the operation parameters.
|
||||
operation_ = op;
|
||||
offset_ = offset;
|
||||
user_buf_ = buf ? new net::DrainableIOBuffer(buf, buf_len) : NULL;
|
||||
buf_len_ = buf_len;
|
||||
user_callback_ = callback;
|
||||
|
||||
result_ = 0;
|
||||
pending_ = false;
|
||||
finished_ = false;
|
||||
abort_ = false;
|
||||
|
||||
if (entry_->net_log().IsCapturing()) {
|
||||
entry_->net_log().BeginEvent(
|
||||
GetSparseEventType(operation_),
|
||||
CreateNetLogSparseOperationCallback(offset_, buf_len_));
|
||||
}
|
||||
DoChildrenIO();
|
||||
|
||||
if (!pending_) {
|
||||
// Everything was done synchronously.
|
||||
operation_ = kNoOperation;
|
||||
user_buf_ = NULL;
|
||||
user_callback_.Reset();
|
||||
return result_;
|
||||
}
|
||||
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
int SparseControl::GetAvailableRange(int64_t offset, int len, int64_t* start) {
|
||||
DCHECK(init_);
|
||||
// We don't support simultaneous IO for sparse data.
|
||||
if (operation_ != kNoOperation)
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
DCHECK(start);
|
||||
|
||||
range_found_ = false;
|
||||
int result = StartIO(
|
||||
kGetRangeOperation, offset, NULL, len, CompletionCallback());
|
||||
if (range_found_) {
|
||||
*start = offset_;
|
||||
return result;
|
||||
}
|
||||
|
||||
// This is a failure. We want to return a valid start value in any case.
|
||||
*start = offset;
|
||||
return result < 0 ? result : 0; // Don't mask error codes to the caller.
|
||||
}
|
||||
|
||||
void SparseControl::CancelIO() {
|
||||
if (operation_ == kNoOperation)
|
||||
return;
|
||||
abort_ = true;
|
||||
}
|
||||
|
||||
int SparseControl::ReadyToUse(const CompletionCallback& callback) {
|
||||
if (!abort_)
|
||||
return net::OK;
|
||||
|
||||
// We'll grab another reference to keep this object alive because we just have
|
||||
// one extra reference due to the pending IO operation itself, but we'll
|
||||
// release that one before invoking user_callback_.
|
||||
entry_->AddRef(); // Balanced in DoAbortCallbacks.
|
||||
abort_callbacks_.push_back(callback);
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
// Static
|
||||
void SparseControl::DeleteChildren(EntryImpl* entry) {
|
||||
DCHECK(entry->GetEntryFlags() & PARENT_ENTRY);
|
||||
int data_len = entry->GetDataSize(kSparseIndex);
|
||||
if (data_len < static_cast<int>(sizeof(SparseData)) ||
|
||||
entry->GetDataSize(kSparseData))
|
||||
return;
|
||||
|
||||
int map_len = data_len - sizeof(SparseHeader);
|
||||
if (map_len > kMaxMapSize || map_len % 4)
|
||||
return;
|
||||
|
||||
char* buffer;
|
||||
Addr address;
|
||||
entry->GetData(kSparseIndex, &buffer, &address);
|
||||
if (!buffer && !address.is_initialized())
|
||||
return;
|
||||
|
||||
entry->net_log().AddEvent(net::NetLogEventType::SPARSE_DELETE_CHILDREN);
|
||||
|
||||
DCHECK(entry->backend_.get());
|
||||
ChildrenDeleter* deleter = new ChildrenDeleter(entry->backend_.get(),
|
||||
entry->GetKey());
|
||||
// The object will self destruct when finished.
|
||||
deleter->AddRef();
|
||||
|
||||
if (buffer) {
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&ChildrenDeleter::Start, deleter, buffer, data_len));
|
||||
} else {
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(&ChildrenDeleter::ReadData, deleter, address, data_len));
|
||||
}
|
||||
}
|
||||
|
||||
// We are going to start using this entry to store sparse data, so we have to
|
||||
// initialize our control info.
|
||||
int SparseControl::CreateSparseEntry() {
|
||||
if (CHILD_ENTRY & entry_->GetEntryFlags())
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
memset(&sparse_header_, 0, sizeof(sparse_header_));
|
||||
sparse_header_.signature = Time::Now().ToInternalValue();
|
||||
sparse_header_.magic = kIndexMagic;
|
||||
sparse_header_.parent_key_len = entry_->GetKey().size();
|
||||
children_map_.Resize(kNumSparseBits, true);
|
||||
|
||||
// Save the header. The bitmap is saved in the destructor.
|
||||
scoped_refptr<net::IOBuffer> buf(
|
||||
new net::WrappedIOBuffer(reinterpret_cast<char*>(&sparse_header_)));
|
||||
|
||||
int rv = entry_->WriteData(kSparseIndex, 0, buf.get(), sizeof(sparse_header_),
|
||||
CompletionCallback(), false);
|
||||
if (rv != sizeof(sparse_header_)) {
|
||||
DLOG(ERROR) << "Unable to save sparse_header_";
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
entry_->SetEntryFlags(PARENT_ENTRY);
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
// We are opening an entry from disk. Make sure that our control data is there.
|
||||
int SparseControl::OpenSparseEntry(int data_len) {
|
||||
if (data_len < static_cast<int>(sizeof(SparseData)))
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
if (entry_->GetDataSize(kSparseData))
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
if (!(PARENT_ENTRY & entry_->GetEntryFlags()))
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
// Dont't go over board with the bitmap. 8 KB gives us offsets up to 64 GB.
|
||||
int map_len = data_len - sizeof(sparse_header_);
|
||||
if (map_len > kMaxMapSize || map_len % 4)
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
scoped_refptr<net::IOBuffer> buf(
|
||||
new net::WrappedIOBuffer(reinterpret_cast<char*>(&sparse_header_)));
|
||||
|
||||
// Read header.
|
||||
int rv = entry_->ReadData(kSparseIndex, 0, buf.get(), sizeof(sparse_header_),
|
||||
CompletionCallback());
|
||||
if (rv != static_cast<int>(sizeof(sparse_header_)))
|
||||
return net::ERR_CACHE_READ_FAILURE;
|
||||
|
||||
// The real validation should be performed by the caller. This is just to
|
||||
// double check.
|
||||
if (sparse_header_.magic != kIndexMagic ||
|
||||
sparse_header_.parent_key_len !=
|
||||
static_cast<int>(entry_->GetKey().size()))
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
// Read the actual bitmap.
|
||||
buf = new net::IOBuffer(map_len);
|
||||
rv = entry_->ReadData(kSparseIndex, sizeof(sparse_header_), buf.get(),
|
||||
map_len, CompletionCallback());
|
||||
if (rv != map_len)
|
||||
return net::ERR_CACHE_READ_FAILURE;
|
||||
|
||||
// Grow the bitmap to the current size and copy the bits.
|
||||
children_map_.Resize(map_len * 8, false);
|
||||
children_map_.SetMap(reinterpret_cast<uint32_t*>(buf->data()), map_len);
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
bool SparseControl::OpenChild() {
|
||||
DCHECK_GE(result_, 0);
|
||||
|
||||
std::string key = GenerateChildKey();
|
||||
if (child_) {
|
||||
// Keep using the same child or open another one?.
|
||||
if (key == child_->GetKey())
|
||||
return true;
|
||||
CloseChild();
|
||||
}
|
||||
|
||||
// See if we are tracking this child.
|
||||
if (!ChildPresent())
|
||||
return ContinueWithoutChild(key);
|
||||
|
||||
if (!entry_->backend_.get())
|
||||
return false;
|
||||
|
||||
child_ = entry_->backend_->OpenEntryImpl(key);
|
||||
if (!child_)
|
||||
return ContinueWithoutChild(key);
|
||||
|
||||
if (!(CHILD_ENTRY & child_->GetEntryFlags()) ||
|
||||
child_->GetDataSize(kSparseIndex) < static_cast<int>(sizeof(child_data_)))
|
||||
return KillChildAndContinue(key, false);
|
||||
|
||||
scoped_refptr<net::WrappedIOBuffer> buf(
|
||||
new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
|
||||
|
||||
// Read signature.
|
||||
int rv = child_->ReadData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
|
||||
CompletionCallback());
|
||||
if (rv != sizeof(child_data_))
|
||||
return KillChildAndContinue(key, true); // This is a fatal failure.
|
||||
|
||||
if (child_data_.header.signature != sparse_header_.signature ||
|
||||
child_data_.header.magic != kIndexMagic)
|
||||
return KillChildAndContinue(key, false);
|
||||
|
||||
if (child_data_.header.last_block_len < 0 ||
|
||||
child_data_.header.last_block_len >= kBlockSize) {
|
||||
// Make sure these values are always within range.
|
||||
child_data_.header.last_block_len = 0;
|
||||
child_data_.header.last_block = -1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SparseControl::CloseChild() {
|
||||
scoped_refptr<net::WrappedIOBuffer> buf(
|
||||
new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
|
||||
|
||||
// Save the allocation bitmap before closing the child entry.
|
||||
int rv = child_->WriteData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
|
||||
CompletionCallback(), false);
|
||||
if (rv != sizeof(child_data_))
|
||||
DLOG(ERROR) << "Failed to save child data";
|
||||
child_ = NULL;
|
||||
}
|
||||
|
||||
std::string SparseControl::GenerateChildKey() {
|
||||
return GenerateChildName(entry_->GetKey(), sparse_header_.signature,
|
||||
offset_ >> 20);
|
||||
}
|
||||
|
||||
// We are deleting the child because something went wrong.
|
||||
bool SparseControl::KillChildAndContinue(const std::string& key, bool fatal) {
|
||||
SetChildBit(false);
|
||||
child_->DoomImpl();
|
||||
child_ = NULL;
|
||||
if (fatal) {
|
||||
result_ = net::ERR_CACHE_READ_FAILURE;
|
||||
return false;
|
||||
}
|
||||
return ContinueWithoutChild(key);
|
||||
}
|
||||
|
||||
// We were not able to open this child; see what we can do.
|
||||
bool SparseControl::ContinueWithoutChild(const std::string& key) {
|
||||
if (kReadOperation == operation_)
|
||||
return false;
|
||||
if (kGetRangeOperation == operation_)
|
||||
return true;
|
||||
|
||||
if (!entry_->backend_.get())
|
||||
return false;
|
||||
|
||||
child_ = entry_->backend_->CreateEntryImpl(key);
|
||||
if (!child_) {
|
||||
child_ = NULL;
|
||||
result_ = net::ERR_CACHE_READ_FAILURE;
|
||||
return false;
|
||||
}
|
||||
// Write signature.
|
||||
InitChildData();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SparseControl::ChildPresent() {
|
||||
int child_bit = static_cast<int>(offset_ >> 20);
|
||||
if (children_map_.Size() <= child_bit)
|
||||
return false;
|
||||
|
||||
return children_map_.Get(child_bit);
|
||||
}
|
||||
|
||||
void SparseControl::SetChildBit(bool value) {
|
||||
int child_bit = static_cast<int>(offset_ >> 20);
|
||||
|
||||
// We may have to increase the bitmap of child entries.
|
||||
if (children_map_.Size() <= child_bit)
|
||||
children_map_.Resize(Bitmap::RequiredArraySize(child_bit + 1) * 32, true);
|
||||
|
||||
children_map_.Set(child_bit, value);
|
||||
}
|
||||
|
||||
void SparseControl::WriteSparseData() {
|
||||
scoped_refptr<net::IOBuffer> buf(new net::WrappedIOBuffer(
|
||||
reinterpret_cast<const char*>(children_map_.GetMap())));
|
||||
|
||||
int len = children_map_.ArraySize() * 4;
|
||||
int rv = entry_->WriteData(kSparseIndex, sizeof(sparse_header_), buf.get(),
|
||||
len, CompletionCallback(), false);
|
||||
if (rv != len) {
|
||||
DLOG(ERROR) << "Unable to save sparse map";
|
||||
}
|
||||
}
|
||||
|
||||
bool SparseControl::VerifyRange() {
|
||||
DCHECK_GE(result_, 0);
|
||||
|
||||
child_offset_ = static_cast<int>(offset_) & (kMaxEntrySize - 1);
|
||||
child_len_ = std::min(buf_len_, kMaxEntrySize - child_offset_);
|
||||
|
||||
// We can write to (or get info from) anywhere in this child.
|
||||
if (operation_ != kReadOperation)
|
||||
return true;
|
||||
|
||||
// Check that there are no holes in this range.
|
||||
int last_bit = (child_offset_ + child_len_ + 1023) >> 10;
|
||||
int start = child_offset_ >> 10;
|
||||
if (child_map_.FindNextBit(&start, last_bit, false)) {
|
||||
// Something is not here.
|
||||
DCHECK_GE(child_data_.header.last_block_len, 0);
|
||||
DCHECK_LT(child_data_.header.last_block_len, kBlockSize);
|
||||
int partial_block_len = PartialBlockLength(start);
|
||||
if (start == child_offset_ >> 10) {
|
||||
// It looks like we don't have anything.
|
||||
if (partial_block_len <= (child_offset_ & (kBlockSize - 1)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// We have the first part.
|
||||
child_len_ = (start << 10) - child_offset_;
|
||||
if (partial_block_len) {
|
||||
// We may have a few extra bytes.
|
||||
child_len_ = std::min(child_len_ + partial_block_len, buf_len_);
|
||||
}
|
||||
// There is no need to read more after this one.
|
||||
buf_len_ = child_len_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SparseControl::UpdateRange(int result) {
|
||||
if (result <= 0 || operation_ != kWriteOperation)
|
||||
return;
|
||||
|
||||
DCHECK_GE(child_data_.header.last_block_len, 0);
|
||||
DCHECK_LT(child_data_.header.last_block_len, kBlockSize);
|
||||
|
||||
// Write the bitmap.
|
||||
int first_bit = child_offset_ >> 10;
|
||||
int block_offset = child_offset_ & (kBlockSize - 1);
|
||||
if (block_offset && (child_data_.header.last_block != first_bit ||
|
||||
child_data_.header.last_block_len < block_offset)) {
|
||||
// The first block is not completely filled; ignore it.
|
||||
first_bit++;
|
||||
}
|
||||
|
||||
int last_bit = (child_offset_ + result) >> 10;
|
||||
block_offset = (child_offset_ + result) & (kBlockSize - 1);
|
||||
|
||||
// This condition will hit with the following criteria:
|
||||
// 1. The first byte doesn't follow the last write.
|
||||
// 2. The first byte is in the middle of a block.
|
||||
// 3. The first byte and the last byte are in the same block.
|
||||
if (first_bit > last_bit)
|
||||
return;
|
||||
|
||||
if (block_offset && !child_map_.Get(last_bit)) {
|
||||
// The last block is not completely filled; save it for later.
|
||||
child_data_.header.last_block = last_bit;
|
||||
child_data_.header.last_block_len = block_offset;
|
||||
} else {
|
||||
child_data_.header.last_block = -1;
|
||||
}
|
||||
|
||||
child_map_.SetRange(first_bit, last_bit, true);
|
||||
}
|
||||
|
||||
int SparseControl::PartialBlockLength(int block_index) const {
|
||||
if (block_index == child_data_.header.last_block)
|
||||
return child_data_.header.last_block_len;
|
||||
|
||||
// This is really empty.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SparseControl::InitChildData() {
|
||||
child_->SetEntryFlags(CHILD_ENTRY);
|
||||
|
||||
memset(&child_data_, 0, sizeof(child_data_));
|
||||
child_data_.header = sparse_header_;
|
||||
|
||||
scoped_refptr<net::WrappedIOBuffer> buf(
|
||||
new net::WrappedIOBuffer(reinterpret_cast<char*>(&child_data_)));
|
||||
|
||||
int rv = child_->WriteData(kSparseIndex, 0, buf.get(), sizeof(child_data_),
|
||||
CompletionCallback(), false);
|
||||
if (rv != sizeof(child_data_))
|
||||
DLOG(ERROR) << "Failed to save child data";
|
||||
SetChildBit(true);
|
||||
}
|
||||
|
||||
void SparseControl::DoChildrenIO() {
|
||||
while (DoChildIO()) continue;
|
||||
|
||||
// Range operations are finished synchronously, often without setting
|
||||
// |finished_| to true.
|
||||
if (kGetRangeOperation == operation_ && entry_->net_log().IsCapturing()) {
|
||||
entry_->net_log().EndEvent(
|
||||
net::NetLogEventType::SPARSE_GET_RANGE,
|
||||
CreateNetLogGetAvailableRangeResultCallback(offset_, result_));
|
||||
}
|
||||
if (finished_) {
|
||||
if (kGetRangeOperation != operation_ && entry_->net_log().IsCapturing()) {
|
||||
entry_->net_log().EndEvent(GetSparseEventType(operation_));
|
||||
}
|
||||
if (pending_)
|
||||
DoUserCallback(); // Don't touch this object after this point.
|
||||
}
|
||||
}
|
||||
|
||||
bool SparseControl::DoChildIO() {
|
||||
finished_ = true;
|
||||
if (!buf_len_ || result_ < 0)
|
||||
return false;
|
||||
|
||||
if (!OpenChild())
|
||||
return false;
|
||||
|
||||
if (!VerifyRange())
|
||||
return false;
|
||||
|
||||
// We have more work to do. Let's not trigger a callback to the caller.
|
||||
finished_ = false;
|
||||
CompletionCallback callback;
|
||||
if (!user_callback_.is_null()) {
|
||||
callback =
|
||||
base::Bind(&SparseControl::OnChildIOCompleted, base::Unretained(this));
|
||||
}
|
||||
|
||||
int rv = 0;
|
||||
switch (operation_) {
|
||||
case kReadOperation:
|
||||
if (entry_->net_log().IsCapturing()) {
|
||||
entry_->net_log().BeginEvent(
|
||||
net::NetLogEventType::SPARSE_READ_CHILD_DATA,
|
||||
CreateNetLogSparseReadWriteCallback(child_->net_log().source(),
|
||||
child_len_));
|
||||
}
|
||||
rv = child_->ReadDataImpl(kSparseData, child_offset_, user_buf_.get(),
|
||||
child_len_, callback);
|
||||
break;
|
||||
case kWriteOperation:
|
||||
if (entry_->net_log().IsCapturing()) {
|
||||
entry_->net_log().BeginEvent(
|
||||
net::NetLogEventType::SPARSE_WRITE_CHILD_DATA,
|
||||
CreateNetLogSparseReadWriteCallback(child_->net_log().source(),
|
||||
child_len_));
|
||||
}
|
||||
rv = child_->WriteDataImpl(kSparseData, child_offset_, user_buf_.get(),
|
||||
child_len_, callback, false);
|
||||
break;
|
||||
case kGetRangeOperation:
|
||||
rv = DoGetAvailableRange();
|
||||
break;
|
||||
default:
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
if (rv == net::ERR_IO_PENDING) {
|
||||
if (!pending_) {
|
||||
pending_ = true;
|
||||
// The child will protect himself against closing the entry while IO is in
|
||||
// progress. However, this entry can still be closed, and that would not
|
||||
// be a good thing for us, so we increase the refcount until we're
|
||||
// finished doing sparse stuff.
|
||||
entry_->AddRef(); // Balanced in DoUserCallback.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!rv)
|
||||
return false;
|
||||
|
||||
DoChildIOCompleted(rv);
|
||||
return true;
|
||||
}
|
||||
|
||||
int SparseControl::DoGetAvailableRange() {
|
||||
if (!child_)
|
||||
return child_len_; // Move on to the next child.
|
||||
|
||||
// Bits on the bitmap should only be set when the corresponding block was
|
||||
// fully written (it's really being used). If a block is partially used, it
|
||||
// has to start with valid data, the length of the valid data is saved in
|
||||
// |header.last_block_len| and the block itself should match
|
||||
// |header.last_block|.
|
||||
//
|
||||
// In other words, (|header.last_block| + |header.last_block_len|) is the
|
||||
// offset where the last write ended, and data in that block (which is not
|
||||
// marked as used because it is not full) will only be reused if the next
|
||||
// write continues at that point.
|
||||
//
|
||||
// This code has to find if there is any data between child_offset_ and
|
||||
// child_offset_ + child_len_.
|
||||
int last_bit = (child_offset_ + child_len_ + kBlockSize - 1) >> 10;
|
||||
int start = child_offset_ >> 10;
|
||||
int partial_start_bytes = PartialBlockLength(start);
|
||||
int found = start;
|
||||
int bits_found = child_map_.FindBits(&found, last_bit, true);
|
||||
bool is_last_block_in_range = start < child_data_.header.last_block &&
|
||||
child_data_.header.last_block < last_bit;
|
||||
|
||||
int block_offset = child_offset_ & (kBlockSize - 1);
|
||||
if (!bits_found && partial_start_bytes <= block_offset) {
|
||||
if (!is_last_block_in_range)
|
||||
return child_len_;
|
||||
found = last_bit - 1; // There are some bytes here.
|
||||
}
|
||||
|
||||
// We are done. Just break the loop and reset result_ to our real result.
|
||||
range_found_ = true;
|
||||
|
||||
int bytes_found = bits_found << 10;
|
||||
bytes_found += PartialBlockLength(found + bits_found);
|
||||
|
||||
// found now points to the first bytes. Lets see if we have data before it.
|
||||
int empty_start = std::max((found << 10) - child_offset_, 0);
|
||||
if (empty_start >= child_len_)
|
||||
return child_len_;
|
||||
|
||||
// At this point we have bytes_found stored after (found << 10), and we want
|
||||
// child_len_ bytes after child_offset_. The first empty_start bytes after
|
||||
// child_offset_ are invalid.
|
||||
|
||||
if (start == found)
|
||||
bytes_found -= block_offset;
|
||||
|
||||
// If the user is searching past the end of this child, bits_found is the
|
||||
// right result; otherwise, we have some empty space at the start of this
|
||||
// query that we have to subtract from the range that we searched.
|
||||
result_ = std::min(bytes_found, child_len_ - empty_start);
|
||||
|
||||
if (partial_start_bytes) {
|
||||
result_ = std::min(partial_start_bytes - block_offset, child_len_);
|
||||
empty_start = 0;
|
||||
}
|
||||
|
||||
// Only update offset_ when this query found zeros at the start.
|
||||
if (empty_start)
|
||||
offset_ += empty_start;
|
||||
|
||||
// This will actually break the loop.
|
||||
buf_len_ = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SparseControl::DoChildIOCompleted(int result) {
|
||||
LogChildOperationEnd(entry_->net_log(), operation_, result);
|
||||
if (result < 0) {
|
||||
// We fail the whole operation if we encounter an error.
|
||||
result_ = result;
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateRange(result);
|
||||
|
||||
result_ += result;
|
||||
offset_ += result;
|
||||
buf_len_ -= result;
|
||||
|
||||
// We'll be reusing the user provided buffer for the next chunk.
|
||||
if (buf_len_ && user_buf_.get())
|
||||
user_buf_->DidConsume(result);
|
||||
}
|
||||
|
||||
void SparseControl::OnChildIOCompleted(int result) {
|
||||
DCHECK_NE(net::ERR_IO_PENDING, result);
|
||||
DoChildIOCompleted(result);
|
||||
|
||||
if (abort_) {
|
||||
// We'll return the current result of the operation, which may be less than
|
||||
// the bytes to read or write, but the user cancelled the operation.
|
||||
abort_ = false;
|
||||
if (entry_->net_log().IsCapturing()) {
|
||||
entry_->net_log().AddEvent(net::NetLogEventType::CANCELLED);
|
||||
entry_->net_log().EndEvent(GetSparseEventType(operation_));
|
||||
}
|
||||
// We have an indirect reference to this object for every callback so if
|
||||
// there is only one callback, we may delete this object before reaching
|
||||
// DoAbortCallbacks.
|
||||
bool has_abort_callbacks = !abort_callbacks_.empty();
|
||||
DoUserCallback();
|
||||
if (has_abort_callbacks)
|
||||
DoAbortCallbacks();
|
||||
return;
|
||||
}
|
||||
|
||||
// We are running a callback from the message loop. It's time to restart what
|
||||
// we were doing before.
|
||||
DoChildrenIO();
|
||||
}
|
||||
|
||||
void SparseControl::DoUserCallback() {
|
||||
DCHECK(!user_callback_.is_null());
|
||||
CompletionCallback cb = user_callback_;
|
||||
user_callback_.Reset();
|
||||
user_buf_ = NULL;
|
||||
pending_ = false;
|
||||
operation_ = kNoOperation;
|
||||
int rv = result_;
|
||||
entry_->Release(); // Don't touch object after this line.
|
||||
cb.Run(rv);
|
||||
}
|
||||
|
||||
void SparseControl::DoAbortCallbacks() {
|
||||
std::vector<CompletionCallback> abort_callbacks;
|
||||
abort_callbacks.swap(abort_callbacks_);
|
||||
|
||||
for (CompletionCallback& callback : abort_callbacks) {
|
||||
// Releasing all references to entry_ may result in the destruction of this
|
||||
// object so we should not be touching it after the last Release().
|
||||
entry_->Release();
|
||||
callback.Run(net::OK);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
182
net/disk_cache/blockfile/sparse_control.h
Normal file
182
net/disk_cache/blockfile/sparse_control.h
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_SPARSE_CONTROL_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_SPARSE_CONTROL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/macros.h"
|
||||
#include "net/base/completion_callback.h"
|
||||
#include "net/disk_cache/blockfile/bitmap.h"
|
||||
#include "net/disk_cache/blockfile/disk_format.h"
|
||||
|
||||
namespace net {
|
||||
class IOBuffer;
|
||||
class DrainableIOBuffer;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class Entry;
|
||||
class EntryImpl;
|
||||
|
||||
// This class provides support for the sparse capabilities of the disk cache.
|
||||
// Basically, sparse IO is directed from EntryImpl to this class, and we split
|
||||
// the operation into multiple small pieces, sending each one to the
|
||||
// appropriate entry. An instance of this class is associated with each entry
|
||||
// used directly for sparse operations (the entry passed in to the constructor).
|
||||
class SparseControl {
|
||||
public:
|
||||
typedef net::CompletionCallback CompletionCallback;
|
||||
|
||||
// The operation to perform.
|
||||
enum SparseOperation {
|
||||
kNoOperation,
|
||||
kReadOperation,
|
||||
kWriteOperation,
|
||||
kGetRangeOperation
|
||||
};
|
||||
|
||||
explicit SparseControl(EntryImpl* entry);
|
||||
~SparseControl();
|
||||
|
||||
// Initializes the object for the current entry. If this entry already stores
|
||||
// sparse data, or can be used to do it, it updates the relevant information
|
||||
// on disk and returns net::OK. Otherwise it returns a net error code.
|
||||
int Init();
|
||||
|
||||
// Performs a quick test to see if the entry is sparse or not, without
|
||||
// generating disk IO (so the answer provided is only a best effort).
|
||||
bool CouldBeSparse() const;
|
||||
|
||||
// Performs an actual sparse read or write operation for this entry. |op| is
|
||||
// the operation to perform, |offset| is the desired sparse offset, |buf| and
|
||||
// |buf_len| specify the actual data to use and |callback| is the callback
|
||||
// to use for asynchronous operations. See the description of the Read /
|
||||
// WriteSparseData for details about the arguments. The return value is the
|
||||
// number of bytes read or written, or a net error code.
|
||||
int StartIO(SparseOperation op,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
// Implements Entry::GetAvailableRange().
|
||||
int GetAvailableRange(int64_t offset, int len, int64_t* start);
|
||||
|
||||
// Cancels the current sparse operation (if any).
|
||||
void CancelIO();
|
||||
|
||||
// Returns OK if the entry can be used for new IO or ERR_IO_PENDING if we are
|
||||
// busy. If the entry is busy, we'll invoke the callback when we are ready
|
||||
// again. See disk_cache::Entry::ReadyToUse() for more info.
|
||||
int ReadyToUse(const CompletionCallback& completion_callback);
|
||||
|
||||
// Deletes the children entries of |entry|.
|
||||
static void DeleteChildren(EntryImpl* entry);
|
||||
|
||||
private:
|
||||
// Creates a new sparse entry or opens an aready created entry from disk.
|
||||
// These methods just read / write the required info from disk for the current
|
||||
// entry, and verify that everything is correct. The return value is a net
|
||||
// error code.
|
||||
int CreateSparseEntry();
|
||||
int OpenSparseEntry(int data_len);
|
||||
|
||||
// Opens and closes a child entry. A child entry is a regular EntryImpl object
|
||||
// with a key derived from the key of the resource to store and the range
|
||||
// stored by that child.
|
||||
bool OpenChild();
|
||||
void CloseChild();
|
||||
std::string GenerateChildKey();
|
||||
|
||||
// Deletes the current child and continues the current operation (open).
|
||||
bool KillChildAndContinue(const std::string& key, bool fatal);
|
||||
|
||||
// Continues the current operation (open) without a current child.
|
||||
bool ContinueWithoutChild(const std::string& key);
|
||||
|
||||
// Returns true if the required child is tracked by the parent entry, i.e. it
|
||||
// was already created.
|
||||
bool ChildPresent();
|
||||
|
||||
// Sets the bit for the current child to the provided |value|. In other words,
|
||||
// starts or stops tracking this child.
|
||||
void SetChildBit(bool value);
|
||||
|
||||
// Writes to disk the tracking information for this entry.
|
||||
void WriteSparseData();
|
||||
|
||||
// Verify that the range to be accessed for the current child is appropriate.
|
||||
// Returns false if an error is detected or there is no need to perform the
|
||||
// current IO operation (for instance if the required range is not stored by
|
||||
// the child).
|
||||
bool VerifyRange();
|
||||
|
||||
// Updates the contents bitmap for the current range, based on the result of
|
||||
// the current operation.
|
||||
void UpdateRange(int result);
|
||||
|
||||
// Returns the number of bytes stored at |block_index|, if its allocation-bit
|
||||
// is off (because it is not completely filled).
|
||||
int PartialBlockLength(int block_index) const;
|
||||
|
||||
// Initializes the sparse info for the current child.
|
||||
void InitChildData();
|
||||
|
||||
// Iterates through all the children needed to complete the current operation.
|
||||
void DoChildrenIO();
|
||||
|
||||
// Performs a single operation with the current child. Returns true when we
|
||||
// should move on to the next child and false when we should interrupt our
|
||||
// work.
|
||||
bool DoChildIO();
|
||||
|
||||
// Performs the required work for GetAvailableRange for one child.
|
||||
int DoGetAvailableRange();
|
||||
|
||||
// Performs the required work after a single IO operations finishes.
|
||||
void DoChildIOCompleted(int result);
|
||||
|
||||
// Invoked by the callback of asynchronous operations.
|
||||
void OnChildIOCompleted(int result);
|
||||
|
||||
// Reports to the user that we are done.
|
||||
void DoUserCallback();
|
||||
void DoAbortCallbacks();
|
||||
|
||||
EntryImpl* entry_; // The sparse entry.
|
||||
scoped_refptr<EntryImpl> child_; // The current child entry.
|
||||
SparseOperation operation_;
|
||||
bool pending_; // True if any child IO operation returned pending.
|
||||
bool finished_;
|
||||
bool init_;
|
||||
bool range_found_; // True if GetAvailableRange found something.
|
||||
bool abort_; // True if we should abort the current operation ASAP.
|
||||
|
||||
SparseHeader sparse_header_; // Data about the children of entry_.
|
||||
Bitmap children_map_; // The actual bitmap of children.
|
||||
SparseData child_data_; // Parent and allocation map of child_.
|
||||
Bitmap child_map_; // The allocation map as a bitmap.
|
||||
|
||||
CompletionCallback user_callback_;
|
||||
std::vector<CompletionCallback> abort_callbacks_;
|
||||
int64_t offset_; // Current sparse offset.
|
||||
scoped_refptr<net::DrainableIOBuffer> user_buf_;
|
||||
int buf_len_; // Bytes to read or write.
|
||||
int child_offset_; // Offset to use for the current child.
|
||||
int child_len_; // Bytes to read or write for this child.
|
||||
int result_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SparseControl);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_SPARSE_CONTROL_H_
|
||||
327
net/disk_cache/blockfile/stats.cc
Normal file
327
net/disk_cache/blockfile/stats.cc
Normal file
@@ -0,0 +1,327 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/stats.h"
|
||||
|
||||
#include "base/format_macros.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/metrics/bucket_ranges.h"
|
||||
#include "base/metrics/histogram.h"
|
||||
#include "base/metrics/histogram_samples.h"
|
||||
#include "base/metrics/sample_vector.h"
|
||||
#include "base/metrics/statistics_recorder.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const int32_t kDiskSignature = 0xF01427E0;
|
||||
|
||||
struct OnDiskStats {
|
||||
int32_t signature;
|
||||
int size;
|
||||
int data_sizes[disk_cache::Stats::kDataSizesLength];
|
||||
int64_t counters[disk_cache::Stats::MAX_COUNTER];
|
||||
};
|
||||
static_assert(sizeof(OnDiskStats) < 512, "needs more than 2 blocks");
|
||||
|
||||
// Returns the "floor" (as opposed to "ceiling") of log base 2 of number.
|
||||
int LogBase2(int32_t number) {
|
||||
unsigned int value = static_cast<unsigned int>(number);
|
||||
const unsigned int mask[] = {0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000};
|
||||
const unsigned int s[] = {1, 2, 4, 8, 16};
|
||||
|
||||
unsigned int result = 0;
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
if (value & mask[i]) {
|
||||
value >>= s[i];
|
||||
result |= s[i];
|
||||
}
|
||||
}
|
||||
return static_cast<int>(result);
|
||||
}
|
||||
|
||||
// WARNING: Add new stats only at the end, or change LoadStats().
|
||||
const char* const kCounterNames[] = {
|
||||
"Open miss",
|
||||
"Open hit",
|
||||
"Create miss",
|
||||
"Create hit",
|
||||
"Resurrect hit",
|
||||
"Create error",
|
||||
"Trim entry",
|
||||
"Doom entry",
|
||||
"Doom cache",
|
||||
"Invalid entry",
|
||||
"Open entries",
|
||||
"Max entries",
|
||||
"Timer",
|
||||
"Read data",
|
||||
"Write data",
|
||||
"Open rankings",
|
||||
"Get rankings",
|
||||
"Fatal error",
|
||||
"Last report",
|
||||
"Last report timer",
|
||||
"Doom recent entries",
|
||||
"unused"
|
||||
};
|
||||
static_assert(arraysize(kCounterNames) == disk_cache::Stats::MAX_COUNTER,
|
||||
"update the names");
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
bool VerifyStats(OnDiskStats* stats) {
|
||||
if (stats->signature != kDiskSignature)
|
||||
return false;
|
||||
|
||||
// We don't want to discard the whole cache every time we have one extra
|
||||
// counter; we keep old data if we can.
|
||||
if (static_cast<unsigned int>(stats->size) > sizeof(*stats)) {
|
||||
memset(stats, 0, sizeof(*stats));
|
||||
stats->signature = kDiskSignature;
|
||||
} else if (static_cast<unsigned int>(stats->size) != sizeof(*stats)) {
|
||||
size_t delta = sizeof(*stats) - static_cast<unsigned int>(stats->size);
|
||||
memset(reinterpret_cast<char*>(stats) + stats->size, 0, delta);
|
||||
stats->size = sizeof(*stats);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Stats::Stats() = default;
|
||||
|
||||
Stats::~Stats() = default;
|
||||
|
||||
bool Stats::Init(void* data, int num_bytes, Addr address) {
|
||||
OnDiskStats local_stats;
|
||||
OnDiskStats* stats = &local_stats;
|
||||
if (!num_bytes) {
|
||||
memset(stats, 0, sizeof(local_stats));
|
||||
local_stats.signature = kDiskSignature;
|
||||
local_stats.size = sizeof(local_stats);
|
||||
} else if (num_bytes >= static_cast<int>(sizeof(*stats))) {
|
||||
stats = reinterpret_cast<OnDiskStats*>(data);
|
||||
if (!VerifyStats(stats)) {
|
||||
memset(&local_stats, 0, sizeof(local_stats));
|
||||
if (memcmp(stats, &local_stats, sizeof(local_stats))) {
|
||||
return false;
|
||||
} else {
|
||||
// The storage is empty which means that SerializeStats() was never
|
||||
// called on the last run. Just re-initialize everything.
|
||||
local_stats.signature = kDiskSignature;
|
||||
local_stats.size = sizeof(local_stats);
|
||||
stats = &local_stats;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
storage_addr_ = address;
|
||||
|
||||
memcpy(data_sizes_, stats->data_sizes, sizeof(data_sizes_));
|
||||
memcpy(counters_, stats->counters, sizeof(counters_));
|
||||
|
||||
// Clean up old value.
|
||||
SetCounter(UNUSED, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stats::InitSizeHistogram() {
|
||||
// Only generate this histogram for the main cache.
|
||||
static bool first_time = true;
|
||||
if (!first_time)
|
||||
return;
|
||||
|
||||
first_time = false;
|
||||
int min = 1;
|
||||
int max = 64 * 1024;
|
||||
int num_buckets = 75;
|
||||
base::BucketRanges ranges(num_buckets + 1);
|
||||
base::Histogram::InitializeBucketRanges(min, max, &ranges);
|
||||
|
||||
base::HistogramBase* stats_histogram = base::Histogram::FactoryGet(
|
||||
"DiskCache.SizeStats2", min, max, num_buckets,
|
||||
base::HistogramBase::kUmaTargetedHistogramFlag);
|
||||
|
||||
base::SampleVector samples(&ranges);
|
||||
for (int i = 0; i < kDataSizesLength; i++) {
|
||||
// This is a good time to fix any inconsistent data. The count should be
|
||||
// always positive, but if it's not, reset the value now.
|
||||
if (data_sizes_[i] < 0)
|
||||
data_sizes_[i] = 0;
|
||||
|
||||
samples.Accumulate(GetBucketRange(i) / 1024, data_sizes_[i]);
|
||||
}
|
||||
stats_histogram->AddSamples(samples);
|
||||
}
|
||||
|
||||
int Stats::StorageSize() {
|
||||
// If we have more than 512 bytes of counters, change kDiskSignature so we
|
||||
// don't overwrite something else (LoadStats must fail).
|
||||
static_assert(sizeof(OnDiskStats) <= 256 * 2, "use more blocks");
|
||||
return 256 * 2;
|
||||
}
|
||||
|
||||
void Stats::ModifyStorageStats(int32_t old_size, int32_t new_size) {
|
||||
// We keep a counter of the data block size on an array where each entry is
|
||||
// the adjusted log base 2 of the size. The first entry counts blocks of 256
|
||||
// bytes, the second blocks up to 512 bytes, etc. With 20 entries, the last
|
||||
// one stores entries of more than 64 MB
|
||||
int new_index = GetStatsBucket(new_size);
|
||||
int old_index = GetStatsBucket(old_size);
|
||||
|
||||
if (new_size)
|
||||
data_sizes_[new_index]++;
|
||||
|
||||
if (old_size)
|
||||
data_sizes_[old_index]--;
|
||||
}
|
||||
|
||||
void Stats::OnEvent(Counters an_event) {
|
||||
DCHECK(an_event >= MIN_COUNTER && an_event < MAX_COUNTER);
|
||||
counters_[an_event]++;
|
||||
}
|
||||
|
||||
void Stats::SetCounter(Counters counter, int64_t value) {
|
||||
DCHECK(counter >= MIN_COUNTER && counter < MAX_COUNTER);
|
||||
counters_[counter] = value;
|
||||
}
|
||||
|
||||
int64_t Stats::GetCounter(Counters counter) const {
|
||||
DCHECK(counter >= MIN_COUNTER && counter < MAX_COUNTER);
|
||||
return counters_[counter];
|
||||
}
|
||||
|
||||
void Stats::GetItems(StatsItems* items) {
|
||||
std::pair<std::string, std::string> item;
|
||||
for (int i = 0; i < kDataSizesLength; i++) {
|
||||
item.first = base::StringPrintf("Size%02d", i);
|
||||
item.second = base::StringPrintf("0x%08x", data_sizes_[i]);
|
||||
items->push_back(item);
|
||||
}
|
||||
|
||||
for (int i = MIN_COUNTER; i < MAX_COUNTER; i++) {
|
||||
item.first = kCounterNames[i];
|
||||
item.second = base::StringPrintf("0x%" PRIx64, counters_[i]);
|
||||
items->push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
int Stats::GetHitRatio() const {
|
||||
return GetRatio(OPEN_HIT, OPEN_MISS);
|
||||
}
|
||||
|
||||
int Stats::GetResurrectRatio() const {
|
||||
return GetRatio(RESURRECT_HIT, CREATE_HIT);
|
||||
}
|
||||
|
||||
void Stats::ResetRatios() {
|
||||
SetCounter(OPEN_HIT, 0);
|
||||
SetCounter(OPEN_MISS, 0);
|
||||
SetCounter(RESURRECT_HIT, 0);
|
||||
SetCounter(CREATE_HIT, 0);
|
||||
}
|
||||
|
||||
int Stats::GetLargeEntriesSize() {
|
||||
int total = 0;
|
||||
// data_sizes_[20] stores values between 512 KB and 1 MB (see comment before
|
||||
// GetStatsBucket()).
|
||||
for (int bucket = 20; bucket < kDataSizesLength; bucket++)
|
||||
total += data_sizes_[bucket] * GetBucketRange(bucket);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
int Stats::SerializeStats(void* data, int num_bytes, Addr* address) {
|
||||
OnDiskStats* stats = reinterpret_cast<OnDiskStats*>(data);
|
||||
if (num_bytes < static_cast<int>(sizeof(*stats)))
|
||||
return 0;
|
||||
|
||||
stats->signature = kDiskSignature;
|
||||
stats->size = sizeof(*stats);
|
||||
memcpy(stats->data_sizes, data_sizes_, sizeof(data_sizes_));
|
||||
memcpy(stats->counters, counters_, sizeof(counters_));
|
||||
|
||||
*address = storage_addr_;
|
||||
return sizeof(*stats);
|
||||
}
|
||||
|
||||
int Stats::GetBucketRange(size_t i) const {
|
||||
if (i < 2)
|
||||
return static_cast<int>(1024 * i);
|
||||
|
||||
if (i < 12)
|
||||
return static_cast<int>(2048 * (i - 1));
|
||||
|
||||
if (i < 17)
|
||||
return static_cast<int>(4096 * (i - 11)) + 20 * 1024;
|
||||
|
||||
int n = 64 * 1024;
|
||||
if (i > static_cast<size_t>(kDataSizesLength)) {
|
||||
NOTREACHED();
|
||||
i = kDataSizesLength;
|
||||
}
|
||||
|
||||
i -= 17;
|
||||
n <<= i;
|
||||
return n;
|
||||
}
|
||||
|
||||
// The array will be filled this way:
|
||||
// index size
|
||||
// 0 [0, 1024)
|
||||
// 1 [1024, 2048)
|
||||
// 2 [2048, 4096)
|
||||
// 3 [4K, 6K)
|
||||
// ...
|
||||
// 10 [18K, 20K)
|
||||
// 11 [20K, 24K)
|
||||
// 12 [24k, 28K)
|
||||
// ...
|
||||
// 15 [36k, 40K)
|
||||
// 16 [40k, 64K)
|
||||
// 17 [64K, 128K)
|
||||
// 18 [128K, 256K)
|
||||
// ...
|
||||
// 23 [4M, 8M)
|
||||
// 24 [8M, 16M)
|
||||
// 25 [16M, 32M)
|
||||
// 26 [32M, 64M)
|
||||
// 27 [64M, ...)
|
||||
int Stats::GetStatsBucket(int32_t size) {
|
||||
if (size < 1024)
|
||||
return 0;
|
||||
|
||||
// 10 slots more, until 20K.
|
||||
if (size < 20 * 1024)
|
||||
return size / 2048 + 1;
|
||||
|
||||
// 5 slots more, from 20K to 40K.
|
||||
if (size < 40 * 1024)
|
||||
return (size - 20 * 1024) / 4096 + 11;
|
||||
|
||||
// From this point on, use a logarithmic scale.
|
||||
int result = LogBase2(size) + 1;
|
||||
|
||||
static_assert(kDataSizesLength > 16, "update the scale");
|
||||
if (result >= kDataSizesLength)
|
||||
result = kDataSizesLength - 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int Stats::GetRatio(Counters hit, Counters miss) const {
|
||||
int64_t ratio = GetCounter(hit) * 100;
|
||||
if (!ratio)
|
||||
return 0;
|
||||
|
||||
ratio /= (GetCounter(hit) + GetCounter(miss));
|
||||
return static_cast<int>(ratio);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
100
net/disk_cache/blockfile/stats.h
Normal file
100
net/disk_cache/blockfile/stats.h
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_STATS_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_STATS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/blockfile/addr.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
using StatsItems = base::StringPairs;
|
||||
|
||||
// This class stores cache-specific usage information, for tunning purposes.
|
||||
class NET_EXPORT_PRIVATE Stats {
|
||||
public:
|
||||
static const int kDataSizesLength = 28;
|
||||
enum Counters {
|
||||
MIN_COUNTER = 0,
|
||||
OPEN_MISS = MIN_COUNTER,
|
||||
OPEN_HIT,
|
||||
CREATE_MISS,
|
||||
CREATE_HIT,
|
||||
RESURRECT_HIT,
|
||||
CREATE_ERROR,
|
||||
TRIM_ENTRY,
|
||||
DOOM_ENTRY,
|
||||
DOOM_CACHE,
|
||||
INVALID_ENTRY,
|
||||
OPEN_ENTRIES, // Average number of open entries.
|
||||
MAX_ENTRIES, // Maximum number of open entries.
|
||||
TIMER,
|
||||
READ_DATA,
|
||||
WRITE_DATA,
|
||||
OPEN_RANKINGS, // An entry has to be read just to modify rankings.
|
||||
GET_RANKINGS, // We got the ranking info without reading the whole entry.
|
||||
FATAL_ERROR,
|
||||
LAST_REPORT, // Time of the last time we sent a report.
|
||||
LAST_REPORT_TIMER, // Timer count of the last time we sent a report.
|
||||
DOOM_RECENT, // The cache was partially cleared.
|
||||
UNUSED, // Was: ga.js was evicted from the cache.
|
||||
MAX_COUNTER
|
||||
};
|
||||
|
||||
Stats();
|
||||
~Stats();
|
||||
|
||||
// Initializes this object with |data| from disk.
|
||||
bool Init(void* data, int num_bytes, Addr address);
|
||||
|
||||
// Generates a size distribution histogram.
|
||||
void InitSizeHistogram();
|
||||
|
||||
// Returns the number of bytes needed to store the stats on disk.
|
||||
int StorageSize();
|
||||
|
||||
// Tracks changes to the stoage space used by an entry.
|
||||
void ModifyStorageStats(int32_t old_size, int32_t new_size);
|
||||
|
||||
// Tracks general events.
|
||||
void OnEvent(Counters an_event);
|
||||
void SetCounter(Counters counter, int64_t value);
|
||||
int64_t GetCounter(Counters counter) const;
|
||||
|
||||
void GetItems(StatsItems* items);
|
||||
int GetHitRatio() const;
|
||||
int GetResurrectRatio() const;
|
||||
void ResetRatios();
|
||||
|
||||
// Returns the lower bound of the space used by entries bigger than 512 KB.
|
||||
int GetLargeEntriesSize();
|
||||
|
||||
// Writes the stats into |data|, to be stored at the given cache address.
|
||||
// Returns the number of bytes copied.
|
||||
int SerializeStats(void* data, int num_bytes, Addr* address);
|
||||
|
||||
private:
|
||||
// Supports generation of SizeStats histogram data.
|
||||
int GetBucketRange(size_t i) const;
|
||||
int GetStatsBucket(int32_t size);
|
||||
int GetRatio(Counters hit, Counters miss) const;
|
||||
|
||||
Addr storage_addr_;
|
||||
int data_sizes_[kDataSizesLength];
|
||||
int64_t counters_[MAX_COUNTER];
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Stats);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_STATS_H_
|
||||
209
net/disk_cache/blockfile/storage_block-inl.h
Normal file
209
net/disk_cache/blockfile/storage_block-inl.h
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_INL_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_INL_H_
|
||||
|
||||
#include "net/disk_cache/blockfile/storage_block.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/hash.h"
|
||||
#include "base/logging.h"
|
||||
#include "net/disk_cache/blockfile/trace.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
template<typename T> StorageBlock<T>::StorageBlock(MappedFile* file,
|
||||
Addr address)
|
||||
: data_(NULL), file_(file), address_(address), modified_(false),
|
||||
own_data_(false), extended_(false) {
|
||||
if (address.num_blocks() > 1)
|
||||
extended_ = true;
|
||||
DCHECK(!address.is_initialized() || sizeof(*data_) == address.BlockSize());
|
||||
}
|
||||
|
||||
template<typename T> StorageBlock<T>::~StorageBlock() {
|
||||
if (modified_)
|
||||
Store();
|
||||
DeleteData();
|
||||
}
|
||||
|
||||
template<typename T> void* StorageBlock<T>::buffer() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
template<typename T> size_t StorageBlock<T>::size() const {
|
||||
if (!extended_)
|
||||
return sizeof(*data_);
|
||||
return address_.num_blocks() * sizeof(*data_);
|
||||
}
|
||||
|
||||
template<typename T> int StorageBlock<T>::offset() const {
|
||||
return address_.start_block() * address_.BlockSize();
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::LazyInit(MappedFile* file,
|
||||
Addr address) {
|
||||
if (file_ || address_.is_initialized()) {
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
file_ = file;
|
||||
address_.set_value(address.value());
|
||||
if (address.num_blocks() > 1)
|
||||
extended_ = true;
|
||||
|
||||
DCHECK(sizeof(*data_) == address.BlockSize());
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T> void StorageBlock<T>::SetData(T* other) {
|
||||
DCHECK(!modified_);
|
||||
DeleteData();
|
||||
data_ = other;
|
||||
}
|
||||
|
||||
template<typename T> void StorageBlock<T>::Discard() {
|
||||
if (!data_)
|
||||
return;
|
||||
if (!own_data_) {
|
||||
NOTREACHED();
|
||||
return;
|
||||
}
|
||||
DeleteData();
|
||||
data_ = NULL;
|
||||
modified_ = false;
|
||||
extended_ = false;
|
||||
}
|
||||
|
||||
template<typename T> void StorageBlock<T>::StopSharingData() {
|
||||
if (!data_ || own_data_)
|
||||
return;
|
||||
DCHECK(!modified_);
|
||||
data_ = NULL;
|
||||
}
|
||||
|
||||
template<typename T> void StorageBlock<T>::set_modified() {
|
||||
DCHECK(data_);
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
template<typename T> void StorageBlock<T>::clear_modified() {
|
||||
modified_ = false;
|
||||
}
|
||||
|
||||
template<typename T> T* StorageBlock<T>::Data() {
|
||||
if (!data_)
|
||||
AllocateData();
|
||||
return data_;
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::HasData() const {
|
||||
return (NULL != data_);
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::VerifyHash() const {
|
||||
uint32_t hash = CalculateHash();
|
||||
return (!data_->self_hash || data_->self_hash == hash);
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::own_data() const {
|
||||
return own_data_;
|
||||
}
|
||||
|
||||
template<typename T> const Addr StorageBlock<T>::address() const {
|
||||
return address_;
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::Load() {
|
||||
if (file_) {
|
||||
if (!data_)
|
||||
AllocateData();
|
||||
|
||||
if (file_->Load(this)) {
|
||||
modified_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(WARNING) << "Failed data load.";
|
||||
Trace("Failed data load.");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::Store() {
|
||||
if (file_ && data_) {
|
||||
data_->self_hash = CalculateHash();
|
||||
if (file_->Store(this)) {
|
||||
modified_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(ERROR) << "Failed data store.";
|
||||
Trace("Failed data store.");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::Load(FileIOCallback* callback,
|
||||
bool* completed) {
|
||||
if (file_) {
|
||||
if (!data_)
|
||||
AllocateData();
|
||||
|
||||
if (file_->Load(this, callback, completed)) {
|
||||
modified_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(WARNING) << "Failed data load.";
|
||||
Trace("Failed data load.");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T> bool StorageBlock<T>::Store(FileIOCallback* callback,
|
||||
bool* completed) {
|
||||
if (file_ && data_) {
|
||||
data_->self_hash = CalculateHash();
|
||||
if (file_->Store(this, callback, completed)) {
|
||||
modified_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(ERROR) << "Failed data store.";
|
||||
Trace("Failed data store.");
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T> void StorageBlock<T>::AllocateData() {
|
||||
DCHECK(!data_);
|
||||
if (!extended_) {
|
||||
data_ = new T;
|
||||
} else {
|
||||
void* buffer = new char[address_.num_blocks() * sizeof(*data_)];
|
||||
data_ = new(buffer) T;
|
||||
}
|
||||
own_data_ = true;
|
||||
}
|
||||
|
||||
template<typename T> void StorageBlock<T>::DeleteData() {
|
||||
if (own_data_) {
|
||||
if (!extended_) {
|
||||
delete data_;
|
||||
} else {
|
||||
data_->~T();
|
||||
delete[] reinterpret_cast<char*>(data_);
|
||||
}
|
||||
own_data_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
uint32_t StorageBlock<T>::CalculateHash() const {
|
||||
return base::Hash(data_, offsetof(T, self_hash));
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_INL_H_
|
||||
101
net/disk_cache/blockfile/storage_block.h
Normal file
101
net/disk_cache/blockfile/storage_block.h
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "net/disk_cache/blockfile/addr.h"
|
||||
#include "net/disk_cache/blockfile/mapped_file.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This class encapsulates common behavior of a single "block" of data that is
|
||||
// stored on a block-file. It implements the FileBlock interface, so it can be
|
||||
// serialized directly to the backing file.
|
||||
// This object provides a memory buffer for the related data, and it can be used
|
||||
// to actually share that memory with another instance of the class.
|
||||
//
|
||||
// The following example shows how to share storage with another object:
|
||||
// StorageBlock<TypeA> a(file, address);
|
||||
// StorageBlock<TypeB> b(file, address);
|
||||
// a.Load();
|
||||
// DoSomething(a.Data());
|
||||
// b.SetData(a.Data());
|
||||
// ModifySomething(b.Data());
|
||||
// // Data modified on the previous call will be saved by b's destructor.
|
||||
// b.set_modified();
|
||||
template<typename T>
|
||||
class StorageBlock : public FileBlock {
|
||||
public:
|
||||
StorageBlock(MappedFile* file, Addr address);
|
||||
virtual ~StorageBlock();
|
||||
|
||||
// FileBlock interface.
|
||||
void* buffer() const override;
|
||||
size_t size() const override;
|
||||
int offset() const override;
|
||||
|
||||
// Allows the overide of dummy values passed on the constructor.
|
||||
bool LazyInit(MappedFile* file, Addr address);
|
||||
|
||||
// Sets the internal storage to share the memory provided by other instance.
|
||||
void SetData(T* other);
|
||||
|
||||
// Deletes the data, even if it was modified and not saved. This object must
|
||||
// own the memory buffer (it cannot be shared).
|
||||
void Discard();
|
||||
|
||||
// Stops sharing the data with another object.
|
||||
void StopSharingData();
|
||||
|
||||
// Sets the object to lazily save the in-memory data on destruction.
|
||||
void set_modified();
|
||||
|
||||
// Forgets that the data was modified, so it's not lazily saved.
|
||||
void clear_modified();
|
||||
|
||||
// Gets a pointer to the internal storage (allocates storage if needed).
|
||||
T* Data();
|
||||
|
||||
// Returns true if there is data associated with this object.
|
||||
bool HasData() const;
|
||||
|
||||
// Returns true if the internal hash is correct.
|
||||
bool VerifyHash() const;
|
||||
|
||||
// Returns true if this object owns the data buffer, false if it is shared.
|
||||
bool own_data() const;
|
||||
|
||||
const Addr address() const;
|
||||
|
||||
// Loads and store the data.
|
||||
bool Load();
|
||||
bool Store();
|
||||
bool Load(FileIOCallback* callback, bool* completed);
|
||||
bool Store(FileIOCallback* callback, bool* completed);
|
||||
|
||||
private:
|
||||
void AllocateData();
|
||||
void DeleteData();
|
||||
uint32_t CalculateHash() const;
|
||||
|
||||
T* data_;
|
||||
MappedFile* file_;
|
||||
Addr address_;
|
||||
bool modified_;
|
||||
bool own_data_; // Is data_ owned by this object or shared with someone else.
|
||||
bool extended_; // Used to store an entry of more than one block.
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StorageBlock);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_STORAGE_BLOCK_H_
|
||||
39
net/disk_cache/blockfile/stress_support.h
Normal file
39
net/disk_cache/blockfile/stress_support.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_STRESS_SUPPORT_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_STRESS_SUPPORT_H_
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Uncomment this line to generate a debug build of stress_cache with checks
|
||||
// to ensure that we are not producing corrupt entries.
|
||||
// #define NET_BUILD_STRESS_CACHE 1
|
||||
|
||||
// Uncomment this line to direct the in-memory disk cache tracing to the base
|
||||
// logging system. On Windows this option will enable ETW (Event Tracing for
|
||||
// Windows) so logs across multiple runs can be collected.
|
||||
// #define DISK_CACHE_TRACE_TO_LOG 1
|
||||
|
||||
// Uncomment this line to perform extended integrity checks during init. It is
|
||||
// not recommended to enable this option unless some corruption is being tracked
|
||||
// down.
|
||||
// #define STRESS_CACHE_EXTENDED_VALIDATION 1
|
||||
|
||||
#if defined(NET_BUILD_STRESS_CACHE)
|
||||
#define STRESS_NOTREACHED() NOTREACHED()
|
||||
#define STRESS_DCHECK(a) DCHECK(a)
|
||||
#else
|
||||
// We don't support streams with these macros, but that's a small price to pay
|
||||
// to have a straightforward logic here. Please don't add something like
|
||||
// LogMessageVoidify.
|
||||
#define STRESS_NOTREACHED() {}
|
||||
#define STRESS_DCHECK(a) {}
|
||||
#endif
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_STRESS_SUPPORT_H_
|
||||
193
net/disk_cache/blockfile/trace.cc
Normal file
193
net/disk_cache/blockfile/trace.cc
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/trace.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "net/disk_cache/blockfile/stress_support.h"
|
||||
|
||||
// Change this value to 1 to enable tracing on a release build. By default,
|
||||
// tracing is enabled only on debug builds.
|
||||
#define ENABLE_TRACING 0
|
||||
|
||||
#ifndef NDEBUG
|
||||
#undef ENABLE_TRACING
|
||||
#define ENABLE_TRACING 1
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
const int kEntrySize = 12 * sizeof(size_t);
|
||||
#if defined(NET_BUILD_STRESS_CACHE)
|
||||
const int kNumberOfEntries = 500000;
|
||||
#else
|
||||
const int kNumberOfEntries = 5000; // 240 KB on 32bit, 480 KB on 64bit
|
||||
#endif
|
||||
|
||||
bool s_trace_enabled = false;
|
||||
base::LazyInstance<base::Lock>::Leaky s_lock = LAZY_INSTANCE_INITIALIZER;
|
||||
|
||||
struct TraceBuffer {
|
||||
int num_traces;
|
||||
int current;
|
||||
char buffer[kNumberOfEntries][kEntrySize];
|
||||
};
|
||||
|
||||
#if ENABLE_TRACING
|
||||
void DebugOutput(const char* msg) {
|
||||
#if defined(OS_WIN)
|
||||
OutputDebugStringA(msg);
|
||||
#else
|
||||
NOTIMPLEMENTED();
|
||||
#endif
|
||||
}
|
||||
#endif // ENABLE_TRACING
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// s_trace_buffer and s_trace_object are not singletons because I want the
|
||||
// buffer to be destroyed and re-created when the last user goes away, and it
|
||||
// must be straightforward to access the buffer from the debugger.
|
||||
static TraceObject* s_trace_object = NULL;
|
||||
|
||||
// Static.
|
||||
TraceObject* TraceObject::GetTraceObject() {
|
||||
base::AutoLock lock(s_lock.Get());
|
||||
|
||||
if (s_trace_object)
|
||||
return s_trace_object;
|
||||
|
||||
s_trace_object = new TraceObject();
|
||||
return s_trace_object;
|
||||
}
|
||||
|
||||
TraceObject::TraceObject() {
|
||||
InitTrace();
|
||||
}
|
||||
|
||||
TraceObject::~TraceObject() {
|
||||
DestroyTrace();
|
||||
}
|
||||
|
||||
void TraceObject::EnableTracing(bool enable) {
|
||||
base::AutoLock lock(s_lock.Get());
|
||||
s_trace_enabled = enable;
|
||||
}
|
||||
|
||||
#if ENABLE_TRACING
|
||||
|
||||
static TraceBuffer* s_trace_buffer = NULL;
|
||||
|
||||
void InitTrace(void) {
|
||||
s_trace_enabled = true;
|
||||
if (s_trace_buffer)
|
||||
return;
|
||||
|
||||
s_trace_buffer = new TraceBuffer;
|
||||
memset(s_trace_buffer, 0, sizeof(*s_trace_buffer));
|
||||
}
|
||||
|
||||
void DestroyTrace(void) {
|
||||
base::AutoLock lock(s_lock.Get());
|
||||
|
||||
delete s_trace_buffer;
|
||||
s_trace_buffer = NULL;
|
||||
s_trace_object = NULL;
|
||||
}
|
||||
|
||||
void Trace(const char* format, ...) {
|
||||
if (!s_trace_buffer || !s_trace_enabled)
|
||||
return;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
char line[kEntrySize + 2];
|
||||
|
||||
#if defined(OS_WIN)
|
||||
vsprintf_s(line, format, ap);
|
||||
#else
|
||||
vsnprintf(line, kEntrySize, format, ap);
|
||||
#endif
|
||||
|
||||
#if defined(DISK_CACHE_TRACE_TO_LOG)
|
||||
line[kEntrySize] = '\0';
|
||||
LOG(INFO) << line;
|
||||
#endif
|
||||
|
||||
va_end(ap);
|
||||
|
||||
{
|
||||
base::AutoLock lock(s_lock.Get());
|
||||
if (!s_trace_buffer || !s_trace_enabled)
|
||||
return;
|
||||
|
||||
memcpy(s_trace_buffer->buffer[s_trace_buffer->current], line, kEntrySize);
|
||||
|
||||
s_trace_buffer->num_traces++;
|
||||
s_trace_buffer->current++;
|
||||
if (s_trace_buffer->current == kNumberOfEntries)
|
||||
s_trace_buffer->current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the last num_traces to the debugger output.
|
||||
void DumpTrace(int num_traces) {
|
||||
DCHECK(s_trace_buffer);
|
||||
DebugOutput("Last traces:\n");
|
||||
|
||||
if (num_traces > kNumberOfEntries || num_traces < 0)
|
||||
num_traces = kNumberOfEntries;
|
||||
|
||||
if (s_trace_buffer->num_traces) {
|
||||
char line[kEntrySize + 2];
|
||||
|
||||
int current = s_trace_buffer->current - num_traces;
|
||||
if (current < 0)
|
||||
current += kNumberOfEntries;
|
||||
|
||||
for (int i = 0; i < num_traces; i++) {
|
||||
memcpy(line, s_trace_buffer->buffer[current], kEntrySize);
|
||||
line[kEntrySize] = '\0';
|
||||
size_t length = strlen(line);
|
||||
if (length) {
|
||||
line[length] = '\n';
|
||||
line[length + 1] = '\0';
|
||||
DebugOutput(line);
|
||||
}
|
||||
|
||||
current++;
|
||||
if (current == kNumberOfEntries)
|
||||
current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DebugOutput("End of Traces\n");
|
||||
}
|
||||
|
||||
#else // ENABLE_TRACING
|
||||
|
||||
void InitTrace(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
void DestroyTrace(void) {
|
||||
s_trace_object = NULL;
|
||||
}
|
||||
|
||||
void Trace(const char* format, ...) {
|
||||
}
|
||||
|
||||
#endif // ENABLE_TRACING
|
||||
|
||||
} // namespace disk_cache
|
||||
42
net/disk_cache/blockfile/trace.h
Normal file
42
net/disk_cache/blockfile/trace.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This file provides support for basic in-memory tracing of short events. We
|
||||
// keep a static circular buffer where we store the last traced events, so we
|
||||
// can review the cache recent behavior should we need it.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_TRACE_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_TRACE_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Create and destroy the tracing buffer.
|
||||
void InitTrace(void);
|
||||
void DestroyTrace(void);
|
||||
|
||||
// Simple class to handle the trace buffer lifetime. Any object interested in
|
||||
// tracing should keep a reference to the object returned by GetTraceObject().
|
||||
class TraceObject : public base::RefCountedThreadSafe<TraceObject> {
|
||||
friend class base::RefCountedThreadSafe<TraceObject>;
|
||||
|
||||
public:
|
||||
static TraceObject* GetTraceObject();
|
||||
void EnableTracing(bool enable);
|
||||
|
||||
private:
|
||||
TraceObject();
|
||||
~TraceObject();
|
||||
DISALLOW_COPY_AND_ASSIGN(TraceObject);
|
||||
};
|
||||
|
||||
// Traces to the internal buffer.
|
||||
NET_EXPORT_PRIVATE void Trace(const char* format, ...);
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_TRACE_H_
|
||||
105
net/disk_cache/blockfile/webfonts_histogram.cc
Normal file
105
net/disk_cache/blockfile/webfonts_histogram.cc
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/blockfile/webfonts_histogram.h"
|
||||
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "net/disk_cache/blockfile/entry_impl.h"
|
||||
#include "net/disk_cache/blockfile/histogram_macros.h"
|
||||
|
||||
namespace {
|
||||
|
||||
enum WebFontDiskCacheEventType {
|
||||
CACHE_EVENT_MISS,
|
||||
CACHE_EVENT_HIT,
|
||||
CACHE_EVENT_EVICTED_ENTRY,
|
||||
CACHE_EVENT_MAX
|
||||
};
|
||||
|
||||
// Tests if the substring of str that begins at pos starts with substr. If so,
|
||||
// returns true and advances pos by the length of substr.
|
||||
bool Consume(const std::string& str, const base::StringPiece& substr,
|
||||
std::string::size_type* pos) {
|
||||
if (!str.compare(*pos, substr.length(), substr.data())) {
|
||||
*pos += substr.length();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char kRoboto[] = "roboto";
|
||||
const char kOpenSans[] = "opensans";
|
||||
const char kOthers[] = "others";
|
||||
|
||||
// Check if the given string is a URL for a font resource of Google Fonts.
|
||||
// If so, returns a label for UMA histogram ("roboto", "opensans" or "others").
|
||||
const char* HistogramLabel(const std::string& str) {
|
||||
std::string::size_type pos = 0;
|
||||
if (Consume(str, "http://", &pos) || Consume(str, "https://", &pos)) {
|
||||
if (Consume(str, "themes.googleusercontent.com/static/fonts/", &pos) ||
|
||||
Consume(str, "ssl.gstatic.com/fonts/", &pos) ||
|
||||
Consume(str, "fonts.gstatic.com/s/", &pos)) {
|
||||
if (Consume(str, kRoboto, &pos))
|
||||
return kRoboto;
|
||||
if (Consume(str, kOpenSans, &pos))
|
||||
return kOpenSans;
|
||||
return kOthers;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::string HistogramName(const char* prefix, const char* label) {
|
||||
return base::StringPrintf("WebFont.%s_%s", prefix, label);
|
||||
}
|
||||
|
||||
void RecordCacheEvent(WebFontDiskCacheEventType type, const char* label) {
|
||||
CACHE_HISTOGRAM_ENUMERATION(HistogramName("DiskCacheHit", label),
|
||||
type, CACHE_EVENT_MAX);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
namespace web_fonts_histogram {
|
||||
|
||||
void RecordCacheMiss(const std::string& key) {
|
||||
const char* label = HistogramLabel(key);
|
||||
if (label)
|
||||
RecordCacheEvent(CACHE_EVENT_MISS, label);
|
||||
}
|
||||
|
||||
void RecordEvictedEntry(const std::string& key) {
|
||||
const char* label = HistogramLabel(key);
|
||||
if (label)
|
||||
RecordCacheEvent(CACHE_EVENT_EVICTED_ENTRY, label);
|
||||
}
|
||||
|
||||
void RecordCacheHit(EntryImpl* entry) {
|
||||
const char* label = HistogramLabel(entry->GetKey());
|
||||
if (!label)
|
||||
return;
|
||||
EntryStore* info = entry->entry()->Data();
|
||||
CACHE_HISTOGRAM_COUNTS_10000(HistogramName("DiskCache.ReuseCount.Hit", label),
|
||||
info->reuse_count);
|
||||
CACHE_HISTOGRAM_AGE(HistogramName("DiskCache.EntryAge.Hit", label),
|
||||
base::Time::FromInternalValue(info->creation_time));
|
||||
RecordCacheEvent(CACHE_EVENT_HIT, label);
|
||||
}
|
||||
|
||||
void RecordEviction(EntryImpl* entry) {
|
||||
const char* label = HistogramLabel(entry->GetKey());
|
||||
if (!label)
|
||||
return;
|
||||
EntryStore* info = entry->entry()->Data();
|
||||
CACHE_HISTOGRAM_COUNTS_10000(
|
||||
HistogramName("DiskCache.ReuseCount.Evict", label),
|
||||
info->reuse_count);
|
||||
CACHE_HISTOGRAM_AGE(HistogramName("DiskCache.EntryAge.Evict", label),
|
||||
base::Time::FromInternalValue(info->creation_time));
|
||||
}
|
||||
|
||||
} // namespace web_fonts_histogram
|
||||
} // namespace disk_cache
|
||||
26
net/disk_cache/blockfile/webfonts_histogram.h
Normal file
26
net/disk_cache/blockfile/webfonts_histogram.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_BLOCKFILE_WEBFONTS_HISTOGRAM_H_
|
||||
#define NET_DISK_CACHE_BLOCKFILE_WEBFONTS_HISTOGRAM_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class EntryImpl;
|
||||
|
||||
// A collection of functions for histogram reporting about web fonts.
|
||||
namespace web_fonts_histogram {
|
||||
|
||||
void RecordCacheMiss(const std::string& key);
|
||||
void RecordEvictedEntry(const std::string& key);
|
||||
void RecordCacheHit(EntryImpl* entry);
|
||||
void RecordEviction(EntryImpl* entry);
|
||||
|
||||
} // namespace web_fonts_histogram
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_BLOCKFILE_WEBFONTS_HISTOGRAM_H_
|
||||
160
net/disk_cache/cache_util.cc
Normal file
160
net/disk_cache/cache_util.cc
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/location.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task_scheduler/post_task.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const int kMaxOldFolders = 100;
|
||||
|
||||
// Returns a fully qualified name from path and name, using a given name prefix
|
||||
// and index number. For instance, if the arguments are "/foo", "bar" and 5, it
|
||||
// will return "/foo/old_bar_005".
|
||||
base::FilePath GetPrefixedName(const base::FilePath& path,
|
||||
const std::string& name,
|
||||
int index) {
|
||||
std::string tmp = base::StringPrintf("%s%s_%03d", "old_",
|
||||
name.c_str(), index);
|
||||
return path.AppendASCII(tmp);
|
||||
}
|
||||
|
||||
// This is a simple callback to cleanup old caches.
|
||||
void CleanupCallback(const base::FilePath& path, const std::string& name) {
|
||||
for (int i = 0; i < kMaxOldFolders; i++) {
|
||||
base::FilePath to_delete = GetPrefixedName(path, name, i);
|
||||
disk_cache::DeleteCache(to_delete, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a full path to rename the current cache, in order to delete it. path
|
||||
// is the current folder location, and name is the current folder name.
|
||||
base::FilePath GetTempCacheName(const base::FilePath& path,
|
||||
const std::string& name) {
|
||||
// We'll attempt to have up to kMaxOldFolders folders for deletion.
|
||||
for (int i = 0; i < kMaxOldFolders; i++) {
|
||||
base::FilePath to_delete = GetPrefixedName(path, name, i);
|
||||
if (!base::PathExists(to_delete))
|
||||
return to_delete;
|
||||
}
|
||||
return base::FilePath();
|
||||
}
|
||||
|
||||
int64_t PreferredCacheSizeInternal(int64_t available) {
|
||||
using disk_cache::kDefaultCacheSize;
|
||||
// Return 80% of the available space if there is not enough space to use
|
||||
// kDefaultCacheSize.
|
||||
if (available < kDefaultCacheSize * 10 / 8)
|
||||
return available * 8 / 10;
|
||||
|
||||
// Return kDefaultCacheSize if it uses 10% to 80% of the available space.
|
||||
if (available < kDefaultCacheSize * 10)
|
||||
return kDefaultCacheSize;
|
||||
|
||||
// Return 10% of the available space if the target size
|
||||
// (2.5 * kDefaultCacheSize) is more than 10%.
|
||||
if (available < static_cast<int64_t>(kDefaultCacheSize) * 25)
|
||||
return available / 10;
|
||||
|
||||
// Return the target size (2.5 * kDefaultCacheSize) if it uses 10% to 1%
|
||||
// of the available space.
|
||||
if (available < static_cast<int64_t>(kDefaultCacheSize) * 250)
|
||||
return kDefaultCacheSize * 5 / 2;
|
||||
|
||||
// Return 1% of the available space.
|
||||
return available / 100;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
const int kDefaultCacheSize = 80 * 1024 * 1024;
|
||||
|
||||
void DeleteCache(const base::FilePath& path, bool remove_folder) {
|
||||
if (remove_folder) {
|
||||
if (!base::DeleteFile(path, /* recursive */ true))
|
||||
LOG(WARNING) << "Unable to delete cache folder.";
|
||||
return;
|
||||
}
|
||||
|
||||
base::FileEnumerator iter(
|
||||
path,
|
||||
/* recursive */ false,
|
||||
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
|
||||
for (base::FilePath file = iter.Next(); !file.value().empty();
|
||||
file = iter.Next()) {
|
||||
if (!base::DeleteFile(file, /* recursive */ true)) {
|
||||
LOG(WARNING) << "Unable to delete cache.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In order to process a potentially large number of files, we'll rename the
|
||||
// cache directory to old_ + original_name + number, (located on the same parent
|
||||
// directory), and use a worker thread to delete all the files on all the stale
|
||||
// cache directories. The whole process can still fail if we are not able to
|
||||
// rename the cache directory (for instance due to a sharing violation), and in
|
||||
// that case a cache for this profile (on the desired path) cannot be created.
|
||||
bool DelayedCacheCleanup(const base::FilePath& full_path) {
|
||||
// GetTempCacheName() and MoveCache() use synchronous file
|
||||
// operations.
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
|
||||
base::FilePath current_path = full_path.StripTrailingSeparators();
|
||||
|
||||
base::FilePath path = current_path.DirName();
|
||||
base::FilePath name = current_path.BaseName();
|
||||
#if defined(OS_POSIX)
|
||||
std::string name_str = name.value();
|
||||
#elif defined(OS_WIN)
|
||||
// We created this file so it should only contain ASCII.
|
||||
std::string name_str = base::UTF16ToASCII(name.value());
|
||||
#endif
|
||||
|
||||
base::FilePath to_delete = GetTempCacheName(path, name_str);
|
||||
if (to_delete.empty()) {
|
||||
LOG(ERROR) << "Unable to get another cache folder";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!disk_cache::MoveCache(full_path, to_delete)) {
|
||||
LOG(ERROR) << "Unable to move cache folder " << full_path.value() << " to "
|
||||
<< to_delete.value();
|
||||
return false;
|
||||
}
|
||||
|
||||
base::PostTaskWithTraits(FROM_HERE,
|
||||
{base::MayBlock(), base::TaskPriority::BACKGROUND,
|
||||
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
|
||||
base::Bind(&CleanupCallback, path, name_str));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the preferred maximum number of bytes for the cache given the
|
||||
// number of available bytes.
|
||||
int PreferredCacheSize(int64_t available) {
|
||||
if (available < 0)
|
||||
return kDefaultCacheSize;
|
||||
|
||||
// Limit cache size to somewhat less than kint32max to avoid potential
|
||||
// integer overflows in cache backend implementations.
|
||||
DCHECK_LT(kDefaultCacheSize * 4, std::numeric_limits<int32_t>::max());
|
||||
return static_cast<int32_t>(
|
||||
std::min(PreferredCacheSizeInternal(available),
|
||||
static_cast<int64_t>(kDefaultCacheSize * 4)));
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
49
net/disk_cache/cache_util.h
Normal file
49
net/disk_cache/cache_util.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_CACHE_UTIL_H_
|
||||
#define NET_DISK_CACHE_CACHE_UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Moves the cache files from the given path to another location.
|
||||
// Fails if the destination exists already, or if it doesn't have
|
||||
// permission for the operation. This is basically a rename operation
|
||||
// for the cache directory. Returns true if successful. On ChromeOS,
|
||||
// this moves the cache contents, and leaves the empty cache
|
||||
// directory.
|
||||
NET_EXPORT_PRIVATE bool MoveCache(const base::FilePath& from_path,
|
||||
const base::FilePath& to_path);
|
||||
|
||||
// Deletes the cache files stored on |path|, and optionally also attempts to
|
||||
// delete the folder itself.
|
||||
NET_EXPORT_PRIVATE void DeleteCache(const base::FilePath& path,
|
||||
bool remove_folder);
|
||||
|
||||
// Deletes a cache file.
|
||||
NET_EXPORT_PRIVATE bool DeleteCacheFile(const base::FilePath& name);
|
||||
|
||||
// Renames cache directory synchronously and fires off a background cleanup
|
||||
// task. Used by cache creator itself or by backends for self-restart on error.
|
||||
bool DelayedCacheCleanup(const base::FilePath& full_path);
|
||||
|
||||
// Returns the preferred max cache size given the available disk space.
|
||||
NET_EXPORT_PRIVATE int PreferredCacheSize(int64_t available);
|
||||
|
||||
// The default cache size should not ideally be exposed, but the blockfile
|
||||
// backend uses it for reasons that include testing.
|
||||
NET_EXPORT_PRIVATE extern const int kDefaultCacheSize;
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_CACHE_UTIL_H_
|
||||
46
net/disk_cache/cache_util_posix.cc
Normal file
46
net/disk_cache/cache_util_posix.cc
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_util.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
bool MoveCache(const base::FilePath& from_path, const base::FilePath& to_path) {
|
||||
#if defined(OS_CHROMEOS)
|
||||
// For ChromeOS, we don't actually want to rename the cache
|
||||
// directory, because if we do, then it'll get recreated through the
|
||||
// encrypted filesystem (with encrypted names), and we won't be able
|
||||
// to see these directories anymore in an unmounted encrypted
|
||||
// filesystem, so we just move each item in the cache to a new
|
||||
// directory.
|
||||
if (!base::CreateDirectory(to_path)) {
|
||||
LOG(ERROR) << "Unable to create destination cache directory.";
|
||||
return false;
|
||||
}
|
||||
base::FileEnumerator iter(from_path, false /* not recursive */,
|
||||
base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
|
||||
for (base::FilePath name = iter.Next(); !name.value().empty();
|
||||
name = iter.Next()) {
|
||||
base::FilePath destination = to_path.Append(name.BaseName());
|
||||
if (!base::Move(name, destination)) {
|
||||
LOG(ERROR) << "Unable to move cache item.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return base::Move(from_path, to_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DeleteCacheFile(const base::FilePath& name) {
|
||||
return base::DeleteFile(name, false);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
46
net/disk_cache/cache_util_win.cc
Normal file
46
net/disk_cache/cache_util_win.cc
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
bool MoveCache(const base::FilePath& from_path, const base::FilePath& to_path) {
|
||||
// I don't want to use the shell version of move because if something goes
|
||||
// wrong, that version will attempt to move file by file and fail at the end.
|
||||
if (!MoveFileEx(from_path.value().c_str(), to_path.value().c_str(), 0)) {
|
||||
LOG(ERROR) << "Unable to move the cache: " << GetLastError();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteCacheFile(const base::FilePath& name) {
|
||||
// We do a simple delete, without ever falling back to SHFileOperation, as the
|
||||
// version from base does.
|
||||
if (!DeleteFile(name.value().c_str())) {
|
||||
// There is an error, but we share delete access so let's see if there is a
|
||||
// file to open. Note that this code assumes that we have a handle to the
|
||||
// file at all times (even now), so nobody can have a handle that prevents
|
||||
// us from opening the file again (unless it was deleted).
|
||||
DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
|
||||
DWORD access = SYNCHRONIZE;
|
||||
base::win::ScopedHandle file(CreateFile(
|
||||
name.value().c_str(), access, sharing, NULL, OPEN_EXISTING, 0, NULL));
|
||||
if (file.IsValid())
|
||||
return false;
|
||||
|
||||
// Most likely there is no file to open... and that's what we wanted.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
287
net/disk_cache/disk_cache.cc
Normal file
287
net/disk_cache/disk_cache.cc
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/metrics/field_trial.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/task_scheduler/task_scheduler.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/backend_cleanup_tracker.h"
|
||||
#include "net/disk_cache/blockfile/backend_impl.h"
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/disk_cache/memory/mem_backend_impl.h"
|
||||
#include "net/disk_cache/simple/simple_backend_impl.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Builds an instance of the backend depending on platform, type, experiments
|
||||
// etc. Takes care of the retry state. This object will self-destroy when
|
||||
// finished.
|
||||
class CacheCreator {
|
||||
public:
|
||||
CacheCreator(const base::FilePath& path,
|
||||
bool force,
|
||||
int max_bytes,
|
||||
net::CacheType type,
|
||||
net::BackendType backend_type,
|
||||
uint32_t flags,
|
||||
net::NetLog* net_log,
|
||||
std::unique_ptr<disk_cache::Backend>* backend,
|
||||
base::OnceClosure post_cleanup_callback,
|
||||
const net::CompletionCallback& callback);
|
||||
|
||||
int TryCreateCleanupTrackerAndRun();
|
||||
|
||||
// Creates the backend, the cleanup context for it having been already
|
||||
// established... or purposefully left as null.
|
||||
int Run();
|
||||
|
||||
private:
|
||||
~CacheCreator();
|
||||
|
||||
void DoCallback(int result);
|
||||
|
||||
void OnIOComplete(int result);
|
||||
|
||||
const base::FilePath path_;
|
||||
bool force_;
|
||||
bool retry_;
|
||||
int max_bytes_;
|
||||
net::CacheType type_;
|
||||
net::BackendType backend_type_;
|
||||
#if !defined(OS_ANDROID)
|
||||
uint32_t flags_;
|
||||
#endif
|
||||
std::unique_ptr<disk_cache::Backend>* backend_;
|
||||
base::OnceClosure post_cleanup_callback_;
|
||||
net::CompletionCallback callback_;
|
||||
std::unique_ptr<disk_cache::Backend> created_cache_;
|
||||
net::NetLog* net_log_;
|
||||
scoped_refptr<disk_cache::BackendCleanupTracker> cleanup_tracker_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CacheCreator);
|
||||
};
|
||||
|
||||
CacheCreator::CacheCreator(
|
||||
const base::FilePath& path,
|
||||
bool force,
|
||||
int max_bytes,
|
||||
net::CacheType type,
|
||||
net::BackendType backend_type,
|
||||
uint32_t flags,
|
||||
net::NetLog* net_log,
|
||||
std::unique_ptr<disk_cache::Backend>* backend,
|
||||
base::OnceClosure post_cleanup_callback,
|
||||
const net::CompletionCallback& callback)
|
||||
: path_(path),
|
||||
force_(force),
|
||||
retry_(false),
|
||||
max_bytes_(max_bytes),
|
||||
type_(type),
|
||||
backend_type_(backend_type),
|
||||
#if !defined(OS_ANDROID)
|
||||
flags_(flags),
|
||||
#endif
|
||||
backend_(backend),
|
||||
post_cleanup_callback_(std::move(post_cleanup_callback)),
|
||||
callback_(callback),
|
||||
net_log_(net_log) {
|
||||
}
|
||||
|
||||
CacheCreator::~CacheCreator() = default;
|
||||
|
||||
int CacheCreator::Run() {
|
||||
#if defined(OS_ANDROID)
|
||||
static const bool kSimpleBackendIsDefault = true;
|
||||
#else
|
||||
static const bool kSimpleBackendIsDefault = false;
|
||||
#endif
|
||||
if (backend_type_ == net::CACHE_BACKEND_SIMPLE ||
|
||||
(backend_type_ == net::CACHE_BACKEND_DEFAULT &&
|
||||
kSimpleBackendIsDefault)) {
|
||||
disk_cache::SimpleBackendImpl* simple_cache =
|
||||
new disk_cache::SimpleBackendImpl(
|
||||
path_, cleanup_tracker_.get(), /* file_tracker = */ nullptr,
|
||||
max_bytes_, type_, /* cache_thread = */ nullptr, net_log_);
|
||||
created_cache_.reset(simple_cache);
|
||||
return simple_cache->Init(
|
||||
base::Bind(&CacheCreator::OnIOComplete, base::Unretained(this)));
|
||||
}
|
||||
|
||||
// Avoid references to blockfile functions on Android to reduce binary size.
|
||||
#if defined(OS_ANDROID)
|
||||
return net::ERR_FAILED;
|
||||
#else
|
||||
disk_cache::BackendImpl* new_cache = new disk_cache::BackendImpl(
|
||||
path_, cleanup_tracker_.get(), /*cache_thread = */ nullptr, net_log_);
|
||||
created_cache_.reset(new_cache);
|
||||
new_cache->SetMaxSize(max_bytes_);
|
||||
new_cache->SetType(type_);
|
||||
new_cache->SetFlags(flags_);
|
||||
int rv = new_cache->Init(
|
||||
base::Bind(&CacheCreator::OnIOComplete, base::Unretained(this)));
|
||||
DCHECK_EQ(net::ERR_IO_PENDING, rv);
|
||||
return rv;
|
||||
#endif
|
||||
}
|
||||
|
||||
int CacheCreator::TryCreateCleanupTrackerAndRun() {
|
||||
// Before creating a cache Backend, a BackendCleanupTracker object is needed
|
||||
// so there is a place to keep track of outstanding I/O even after the backend
|
||||
// object itself is destroyed, so that further use of the directory
|
||||
// doesn't race with those outstanding disk I/O ops.
|
||||
|
||||
// This method's purpose it to grab exlusive ownership of a fresh
|
||||
// BackendCleanupTracker for the cache path, and then move on to Run(),
|
||||
// which will take care of creating the actual cache backend. It's possible
|
||||
// that something else is currently making use of the directory, in which
|
||||
// case BackendCleanupTracker::TryCreate will fail, but will just have
|
||||
// TryCreateCleanupTrackerAndRun run again at an opportune time to make
|
||||
// another attempt.
|
||||
|
||||
// The resulting BackendCleanupTracker is stored into a scoped_refptr member
|
||||
// so that it's kept alive while |this| CacheCreator exists , so that in the
|
||||
// case Run() needs to retry Backend creation the same BackendCleanupTracker
|
||||
// is used for both attempts, and |post_cleanup_callback_| gets called after
|
||||
// the second try, not the first one.
|
||||
cleanup_tracker_ = disk_cache::BackendCleanupTracker::TryCreate(
|
||||
path_, base::BindOnce(base::IgnoreResult(
|
||||
&CacheCreator::TryCreateCleanupTrackerAndRun),
|
||||
base::Unretained(this)));
|
||||
if (!cleanup_tracker_)
|
||||
return net::ERR_IO_PENDING;
|
||||
if (!post_cleanup_callback_.is_null())
|
||||
cleanup_tracker_->AddPostCleanupCallback(std::move(post_cleanup_callback_));
|
||||
return Run();
|
||||
}
|
||||
|
||||
void CacheCreator::DoCallback(int result) {
|
||||
DCHECK_NE(net::ERR_IO_PENDING, result);
|
||||
if (result == net::OK) {
|
||||
*backend_ = std::move(created_cache_);
|
||||
} else {
|
||||
LOG(ERROR) << "Unable to create cache";
|
||||
created_cache_.reset();
|
||||
}
|
||||
callback_.Run(result);
|
||||
delete this;
|
||||
}
|
||||
|
||||
// If the initialization of the cache fails, and |force| is true, we will
|
||||
// discard the whole cache and create a new one.
|
||||
void CacheCreator::OnIOComplete(int result) {
|
||||
if (result == net::OK || !force_ || retry_)
|
||||
return DoCallback(result);
|
||||
|
||||
// This is a failure and we are supposed to try again, so delete the object,
|
||||
// delete all the files, and try again.
|
||||
retry_ = true;
|
||||
created_cache_.reset();
|
||||
if (!disk_cache::DelayedCacheCleanup(path_))
|
||||
return DoCallback(result);
|
||||
|
||||
// The worker thread will start deleting files soon, but the original folder
|
||||
// is not there anymore... let's create a new set of files.
|
||||
int rv = Run();
|
||||
DCHECK_EQ(net::ERR_IO_PENDING, rv);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
int CreateCacheBackendImpl(
|
||||
net::CacheType type,
|
||||
net::BackendType backend_type,
|
||||
const base::FilePath& path,
|
||||
int max_bytes,
|
||||
bool force,
|
||||
net::NetLog* net_log,
|
||||
std::unique_ptr<Backend>* backend,
|
||||
base::OnceClosure post_cleanup_callback,
|
||||
const net::CompletionCallback& callback) {
|
||||
DCHECK(!callback.is_null());
|
||||
|
||||
if (type == net::MEMORY_CACHE) {
|
||||
std::unique_ptr<MemBackendImpl> mem_backend_impl =
|
||||
disk_cache::MemBackendImpl::CreateBackend(max_bytes, net_log);
|
||||
if (mem_backend_impl) {
|
||||
mem_backend_impl->SetPostCleanupCallback(
|
||||
std::move(post_cleanup_callback));
|
||||
*backend = std::move(mem_backend_impl);
|
||||
return net::OK;
|
||||
} else {
|
||||
if (!post_cleanup_callback.is_null())
|
||||
base::SequencedTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE, std::move(post_cleanup_callback));
|
||||
return net::ERR_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
bool had_post_cleanup_callback = !post_cleanup_callback.is_null();
|
||||
CacheCreator* creator = new CacheCreator(
|
||||
path, force, max_bytes, type, backend_type, kNone, net_log, backend,
|
||||
std::move(post_cleanup_callback), callback);
|
||||
if (type == net::DISK_CACHE || type == net::MEDIA_CACHE) {
|
||||
DCHECK(!had_post_cleanup_callback);
|
||||
return creator->Run();
|
||||
}
|
||||
|
||||
return creator->TryCreateCleanupTrackerAndRun();
|
||||
}
|
||||
|
||||
int CreateCacheBackend(net::CacheType type,
|
||||
net::BackendType backend_type,
|
||||
const base::FilePath& path,
|
||||
int max_bytes,
|
||||
bool force,
|
||||
net::NetLog* net_log,
|
||||
std::unique_ptr<Backend>* backend,
|
||||
const net::CompletionCallback& callback) {
|
||||
return CreateCacheBackendImpl(type, backend_type, path, max_bytes, force,
|
||||
net_log, backend, base::OnceClosure(),
|
||||
callback);
|
||||
}
|
||||
|
||||
int CreateCacheBackend(net::CacheType type,
|
||||
net::BackendType backend_type,
|
||||
const base::FilePath& path,
|
||||
int max_bytes,
|
||||
bool force,
|
||||
net::NetLog* net_log,
|
||||
std::unique_ptr<Backend>* backend,
|
||||
base::OnceClosure post_cleanup_callback,
|
||||
const net::CompletionCallback& callback) {
|
||||
return CreateCacheBackendImpl(type, backend_type, path, max_bytes, force,
|
||||
net_log, backend,
|
||||
std::move(post_cleanup_callback), callback);
|
||||
}
|
||||
|
||||
void FlushCacheThreadForTesting() {
|
||||
// For simple backend.
|
||||
SimpleBackendImpl::FlushWorkerPoolForTesting();
|
||||
base::TaskScheduler::GetInstance()->FlushForTesting();
|
||||
|
||||
// Block backend.
|
||||
BackendImpl::FlushForTesting();
|
||||
}
|
||||
|
||||
int Backend::CalculateSizeOfEntriesBetween(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) {
|
||||
return net::ERR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
uint8_t Backend::GetEntryInMemoryData(const std::string& key) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Backend::SetEntryInMemoryData(const std::string& key, uint8_t data) {}
|
||||
|
||||
} // namespace disk_cache
|
||||
401
net/disk_cache/disk_cache.h
Normal file
401
net/disk_cache/disk_cache.h
Normal file
@@ -0,0 +1,401 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Defines the public interface of the disk cache. For more details see
|
||||
// http://dev.chromium.org/developers/design-documents/network-stack/disk-cache
|
||||
|
||||
#ifndef NET_DISK_CACHE_DISK_CACHE_H_
|
||||
#define NET_DISK_CACHE_DISK_CACHE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/completion_callback.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
|
||||
namespace trace_event {
|
||||
class ProcessMemoryDump;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
|
||||
namespace net {
|
||||
class IOBuffer;
|
||||
class NetLog;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class Entry;
|
||||
class Backend;
|
||||
|
||||
// Returns an instance of a Backend of the given |type|. |path| points to a
|
||||
// folder where the cached data will be stored (if appropriate). This cache
|
||||
// instance must be the only object that will be reading or writing files to
|
||||
// that folder (if another one exists, and |type| is not net::DISK_CACHE or
|
||||
// net::MEDIA_CACHE, this operation will not complete until the previous
|
||||
// duplicate gets destroyed and finishes all I/O).
|
||||
//
|
||||
// The returned object should be deleted when not needed anymore.
|
||||
// If |force| is true, and there is a problem with the cache initialization, the
|
||||
// files will be deleted and a new set will be created. |max_bytes| is the
|
||||
// maximum size the cache can grow to. If zero is passed in as |max_bytes|, the
|
||||
// cache will determine the value to use. The returned pointer can be
|
||||
// NULL if a fatal error is found. The actual return value of the function is a
|
||||
// net error code. If this function returns ERR_IO_PENDING, the |callback| will
|
||||
// be invoked when a backend is available or a fatal error condition is reached.
|
||||
// The pointer to receive the |backend| must remain valid until the operation
|
||||
// completes (the callback is notified).
|
||||
NET_EXPORT int CreateCacheBackend(net::CacheType type,
|
||||
net::BackendType backend_type,
|
||||
const base::FilePath& path,
|
||||
int max_bytes,
|
||||
bool force,
|
||||
net::NetLog* net_log,
|
||||
std::unique_ptr<Backend>* backend,
|
||||
const net::CompletionCallback& callback);
|
||||
|
||||
// Variant of the above that calls |post_cleanup_callback| once all the I/O
|
||||
// that was in flight has completed post-destruction. |post_cleanup_callback|
|
||||
// will get invoked even if the creation fails. The invocation will always be
|
||||
// via the event loop, and never direct.
|
||||
//
|
||||
// This is currently unsupported for |type| == net::DISK_CACHE or
|
||||
// net::MEDIA_CACHE.
|
||||
//
|
||||
// Note that this will not wait for |post_cleanup_callback| of a previous
|
||||
// instance for |path| to run.
|
||||
NET_EXPORT int CreateCacheBackend(net::CacheType type,
|
||||
net::BackendType backend_type,
|
||||
const base::FilePath& path,
|
||||
int max_bytes,
|
||||
bool force,
|
||||
net::NetLog* net_log,
|
||||
std::unique_ptr<Backend>* backend,
|
||||
base::OnceClosure post_cleanup_callback,
|
||||
const net::CompletionCallback& callback);
|
||||
|
||||
// This will flush any internal threads used by backends created w/o an
|
||||
// externally injected thread specified, so tests can be sure that all I/O
|
||||
// has finished before inspecting the world.
|
||||
NET_EXPORT void FlushCacheThreadForTesting();
|
||||
|
||||
// The root interface for a disk cache instance.
|
||||
class NET_EXPORT Backend {
|
||||
public:
|
||||
typedef net::CompletionCallback CompletionCallback;
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
virtual ~Iterator() {}
|
||||
|
||||
// OpenNextEntry returns |net::OK| and provides |next_entry| if there is an
|
||||
// entry to enumerate. It returns |net::ERR_FAILED| at the end of
|
||||
// enumeration. If the function returns |net::ERR_IO_PENDING|, then the
|
||||
// final result will be passed to the provided |callback|, otherwise
|
||||
// |callback| will not be called. If any entry in the cache is modified
|
||||
// during iteration, the result of this function is thereafter undefined.
|
||||
//
|
||||
// Calling OpenNextEntry after the backend which created it is destroyed
|
||||
// may fail with |net::ERR_FAILED|; however it should not crash.
|
||||
//
|
||||
// Some cache backends make stronger guarantees about mutation during
|
||||
// iteration, see top comment in simple_backend_impl.h for details.
|
||||
virtual int OpenNextEntry(Entry** next_entry,
|
||||
const CompletionCallback& callback) = 0;
|
||||
};
|
||||
|
||||
// If the backend is destroyed when there are operations in progress (any
|
||||
// callback that has not been invoked yet), this method cancels said
|
||||
// operations so the callbacks are not invoked, possibly leaving the work
|
||||
// half way (for instance, dooming just a few entries). Note that pending IO
|
||||
// for a given Entry (as opposed to the Backend) will still generate a
|
||||
// callback from within this method.
|
||||
virtual ~Backend() {}
|
||||
|
||||
// Returns the type of this cache.
|
||||
virtual net::CacheType GetCacheType() const = 0;
|
||||
|
||||
// Returns the number of entries in the cache.
|
||||
virtual int32_t GetEntryCount() const = 0;
|
||||
|
||||
// Opens an existing entry. Upon success, |entry| holds a pointer to an Entry
|
||||
// object representing the specified disk cache entry. When the entry pointer
|
||||
// is no longer needed, its Close method should be called. The return value is
|
||||
// a net error code. If this method returns ERR_IO_PENDING, the |callback|
|
||||
// will be invoked when the entry is available. The pointer to receive the
|
||||
// |entry| must remain valid until the operation completes.
|
||||
virtual int OpenEntry(const std::string& key, Entry** entry,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Creates a new entry. Upon success, the out param holds a pointer to an
|
||||
// Entry object representing the newly created disk cache entry. When the
|
||||
// entry pointer is no longer needed, its Close method should be called. The
|
||||
// return value is a net error code. If this method returns ERR_IO_PENDING,
|
||||
// the |callback| will be invoked when the entry is available. The pointer to
|
||||
// receive the |entry| must remain valid until the operation completes.
|
||||
virtual int CreateEntry(const std::string& key, Entry** entry,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Marks the entry, specified by the given key, for deletion. The return value
|
||||
// is a net error code. If this method returns ERR_IO_PENDING, the |callback|
|
||||
// will be invoked after the entry is doomed.
|
||||
virtual int DoomEntry(const std::string& key,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Marks all entries for deletion. The return value is a net error code. If
|
||||
// this method returns ERR_IO_PENDING, the |callback| will be invoked when the
|
||||
// operation completes.
|
||||
virtual int DoomAllEntries(const CompletionCallback& callback) = 0;
|
||||
|
||||
// Marks a range of entries for deletion. This supports unbounded deletes in
|
||||
// either direction by using null Time values for either argument. The return
|
||||
// value is a net error code. If this method returns ERR_IO_PENDING, the
|
||||
// |callback| will be invoked when the operation completes.
|
||||
// Entries with |initial_time| <= access time < |end_time| are deleted.
|
||||
virtual int DoomEntriesBetween(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Marks all entries accessed since |initial_time| for deletion. The return
|
||||
// value is a net error code. If this method returns ERR_IO_PENDING, the
|
||||
// |callback| will be invoked when the operation completes.
|
||||
// Entries with |initial_time| <= access time are deleted.
|
||||
virtual int DoomEntriesSince(base::Time initial_time,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Calculate the total size of the cache. The return value is the size in
|
||||
// bytes or a net error code. If this method returns ERR_IO_PENDING,
|
||||
// the |callback| will be invoked when the operation completes.
|
||||
virtual int CalculateSizeOfAllEntries(const CompletionCallback& callback) = 0;
|
||||
|
||||
// Calculate the size of all cache entries accessed between |initial_time| and
|
||||
// |end_time|.
|
||||
// The return value is the size in bytes or a net error code. The default
|
||||
// implementation returns ERR_NOT_IMPLEMENTED and should only be overwritten
|
||||
// if there is an efficient way for the backend to determine the size for a
|
||||
// subset of the cache without reading the whole cache from disk.
|
||||
// If this method returns ERR_IO_PENDING, the |callback| will be invoked when
|
||||
// the operation completes.
|
||||
virtual int CalculateSizeOfEntriesBetween(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
// Returns an iterator which will enumerate all entries of the cache in an
|
||||
// undefined order.
|
||||
virtual std::unique_ptr<Iterator> CreateIterator() = 0;
|
||||
|
||||
// Return a list of cache statistics.
|
||||
virtual void GetStats(base::StringPairs* stats) = 0;
|
||||
|
||||
// Called whenever an external cache in the system reuses the resource
|
||||
// referred to by |key|.
|
||||
virtual void OnExternalCacheHit(const std::string& key) = 0;
|
||||
|
||||
// Returns the estimate of dynamically allocated memory in bytes.
|
||||
virtual size_t DumpMemoryStats(
|
||||
base::trace_event::ProcessMemoryDump* pmd,
|
||||
const std::string& parent_absolute_name) const = 0;
|
||||
|
||||
// Backends can optionally permit one to store, probabilistically, up to a
|
||||
// byte associated with a key of an existing entry in memory.
|
||||
|
||||
// GetEntryInMemoryData has the following behavior:
|
||||
// - If the data is not available at this time for any reason, returns 0.
|
||||
// - Otherwise, returns a value that was with very high probability
|
||||
// given to SetEntryInMemoryData(|key|) (and with a very low probability
|
||||
// to a different key that collides in the in-memory index).
|
||||
//
|
||||
// Due to the probability of collisions, including those that can be induced
|
||||
// by hostile 3rd parties, this interface should not be used to make decisions
|
||||
// that affect correctness (especially security).
|
||||
virtual uint8_t GetEntryInMemoryData(const std::string& key);
|
||||
virtual void SetEntryInMemoryData(const std::string& key, uint8_t data);
|
||||
};
|
||||
|
||||
// This interface represents an entry in the disk cache.
|
||||
class NET_EXPORT Entry {
|
||||
public:
|
||||
typedef net::CompletionCallback CompletionCallback;
|
||||
typedef net::IOBuffer IOBuffer;
|
||||
|
||||
// Marks this cache entry for deletion.
|
||||
virtual void Doom() = 0;
|
||||
|
||||
// Releases this entry. Calling this method does not cancel pending IO
|
||||
// operations on this entry. Even after the last reference to this object has
|
||||
// been released, pending completion callbacks may be invoked.
|
||||
virtual void Close() = 0;
|
||||
|
||||
// Returns the key associated with this cache entry.
|
||||
virtual std::string GetKey() const = 0;
|
||||
|
||||
// Returns the time when this cache entry was last used.
|
||||
virtual base::Time GetLastUsed() const = 0;
|
||||
|
||||
// Returns the time when this cache entry was last modified.
|
||||
virtual base::Time GetLastModified() const = 0;
|
||||
|
||||
// Returns the size of the cache data with the given index.
|
||||
virtual int32_t GetDataSize(int index) const = 0;
|
||||
|
||||
// Copies cached data into the given buffer of length |buf_len|. Returns the
|
||||
// number of bytes read or a network error code. If this function returns
|
||||
// ERR_IO_PENDING, the completion callback will be called on the current
|
||||
// thread when the operation completes, and a reference to |buf| will be
|
||||
// retained until the callback is called. Note that as long as the function
|
||||
// does not complete immediately, the callback will always be invoked, even
|
||||
// after Close has been called; in other words, the caller may close this
|
||||
// entry without having to wait for all the callbacks, and still rely on the
|
||||
// cleanup performed from the callback code.
|
||||
virtual int ReadData(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Copies data from the given buffer of length |buf_len| into the cache.
|
||||
// Returns the number of bytes written or a network error code. If this
|
||||
// function returns ERR_IO_PENDING, the completion callback will be called
|
||||
// on the current thread when the operation completes, and a reference to
|
||||
// |buf| will be retained until the callback is called. Note that as long as
|
||||
// the function does not complete immediately, the callback will always be
|
||||
// invoked, even after Close has been called; in other words, the caller may
|
||||
// close this entry without having to wait for all the callbacks, and still
|
||||
// rely on the cleanup performed from the callback code.
|
||||
// If truncate is true, this call will truncate the stored data at the end of
|
||||
// what we are writing here.
|
||||
virtual int WriteData(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
const CompletionCallback& callback,
|
||||
bool truncate) = 0;
|
||||
|
||||
// Sparse entries support:
|
||||
//
|
||||
// A Backend implementation can support sparse entries, so the cache keeps
|
||||
// track of which parts of the entry have been written before. The backend
|
||||
// will never return data that was not written previously, so reading from
|
||||
// such region will return 0 bytes read (or actually the number of bytes read
|
||||
// before reaching that region).
|
||||
//
|
||||
// There are only two streams for sparse entries: a regular control stream
|
||||
// (index 0) that must be accessed through the regular API (ReadData and
|
||||
// WriteData), and one sparse stream that must me accessed through the sparse-
|
||||
// aware API that follows. Calling a non-sparse aware method with an index
|
||||
// argument other than 0 is a mistake that results in implementation specific
|
||||
// behavior. Using a sparse-aware method with an entry that was not stored
|
||||
// using the same API, or with a backend that doesn't support sparse entries
|
||||
// will return ERR_CACHE_OPERATION_NOT_SUPPORTED.
|
||||
//
|
||||
// The storage granularity of the implementation should be at least 1 KB. In
|
||||
// other words, storing less than 1 KB may result in an implementation
|
||||
// dropping the data completely, and writing at offsets not aligned with 1 KB,
|
||||
// or with lengths not a multiple of 1 KB may result in the first or last part
|
||||
// of the data being discarded. However, two consecutive writes should not
|
||||
// result in a hole in between the two parts as long as they are sequential
|
||||
// (the second one starts where the first one ended), and there is no other
|
||||
// write between them.
|
||||
//
|
||||
// The Backend implementation is free to evict any range from the cache at any
|
||||
// moment, so in practice, the previously stated granularity of 1 KB is not
|
||||
// as bad as it sounds.
|
||||
//
|
||||
// The sparse methods don't support multiple simultaneous IO operations to the
|
||||
// same physical entry, so in practice a single object should be instantiated
|
||||
// for a given key at any given time. Once an operation has been issued, the
|
||||
// caller should wait until it completes before starting another one. This
|
||||
// requirement includes the case when an entry is closed while some operation
|
||||
// is in progress and another object is instantiated; any IO operation will
|
||||
// fail while the previous operation is still in-flight. In order to deal with
|
||||
// this requirement, the caller could either wait until the operation
|
||||
// completes before closing the entry, or call CancelSparseIO() before closing
|
||||
// the entry, and call ReadyForSparseIO() on the new entry and wait for the
|
||||
// callback before issuing new operations.
|
||||
|
||||
// Behaves like ReadData() except that this method is used to access sparse
|
||||
// entries.
|
||||
virtual int ReadSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Behaves like WriteData() except that this method is used to access sparse
|
||||
// entries. |truncate| is not part of this interface because a sparse entry
|
||||
// is not expected to be reused with new data. To delete the old data and
|
||||
// start again, or to reduce the total size of the stream data (which implies
|
||||
// that the content has changed), the whole entry should be doomed and
|
||||
// re-created.
|
||||
virtual int WriteSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Returns information about the currently stored portion of a sparse entry.
|
||||
// |offset| and |len| describe a particular range that should be scanned to
|
||||
// find out if it is stored or not. |start| will contain the offset of the
|
||||
// first byte that is stored within this range, and the return value is the
|
||||
// minimum number of consecutive stored bytes. Note that it is possible that
|
||||
// this entry has stored more than the returned value. This method returns a
|
||||
// net error code whenever the request cannot be completed successfully. If
|
||||
// this method returns ERR_IO_PENDING, the |callback| will be invoked when the
|
||||
// operation completes, and |start| must remain valid until that point.
|
||||
virtual int GetAvailableRange(int64_t offset,
|
||||
int len,
|
||||
int64_t* start,
|
||||
const CompletionCallback& callback) = 0;
|
||||
|
||||
// Returns true if this entry could be a sparse entry or false otherwise. This
|
||||
// is a quick test that may return true even if the entry is not really
|
||||
// sparse. This method doesn't modify the state of this entry (it will not
|
||||
// create sparse tracking data). GetAvailableRange or ReadSparseData can be
|
||||
// used to perform a definitive test of whether an existing entry is sparse or
|
||||
// not, but that method may modify the current state of the entry (making it
|
||||
// sparse, for instance). The purpose of this method is to test an existing
|
||||
// entry, but without generating actual IO to perform a thorough check.
|
||||
virtual bool CouldBeSparse() const = 0;
|
||||
|
||||
// Cancels any pending sparse IO operation (if any). The completion callback
|
||||
// of the operation in question will still be called when the operation
|
||||
// finishes, but the operation will finish sooner when this method is used.
|
||||
virtual void CancelSparseIO() = 0;
|
||||
|
||||
// Returns OK if this entry can be used immediately. If that is not the
|
||||
// case, returns ERR_IO_PENDING and invokes the provided callback when this
|
||||
// entry is ready to use. This method always returns OK for non-sparse
|
||||
// entries, and returns ERR_IO_PENDING when a previous operation was cancelled
|
||||
// (by calling CancelSparseIO), but the cache is still busy with it. If there
|
||||
// is a pending operation that has not been cancelled, this method will return
|
||||
// OK although another IO operation cannot be issued at this time; in this
|
||||
// case the caller should just wait for the regular callback to be invoked
|
||||
// instead of using this method to provide another callback.
|
||||
//
|
||||
// Note that CancelSparseIO may have been called on another instance of this
|
||||
// object that refers to the same physical disk entry.
|
||||
// Note: This method is deprecated.
|
||||
virtual int ReadyForSparseIO(const CompletionCallback& callback) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~Entry() {}
|
||||
};
|
||||
|
||||
struct EntryDeleter {
|
||||
void operator()(Entry* entry) {
|
||||
// Note that |entry| is ref-counted.
|
||||
entry->Close();
|
||||
}
|
||||
};
|
||||
|
||||
// Automatically closes an entry when it goes out of scope.
|
||||
typedef std::unique_ptr<Entry, EntryDeleter> ScopedEntryPtr;
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_DISK_CACHE_H_
|
||||
368
net/disk_cache/disk_cache_test_base.cc
Normal file
368
net/disk_cache/disk_cache_test_base.cc
Normal file
@@ -0,0 +1,368 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/disk_cache_test_base.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/test/scoped_task_environment.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/base/test_completion_callback.h"
|
||||
#include "net/disk_cache/backend_cleanup_tracker.h"
|
||||
#include "net/disk_cache/blockfile/backend_impl.h"
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/disk_cache/disk_cache_test_util.h"
|
||||
#include "net/disk_cache/memory/mem_backend_impl.h"
|
||||
#include "net/disk_cache/simple/simple_backend_impl.h"
|
||||
#include "net/disk_cache/simple/simple_file_tracker.h"
|
||||
#include "net/disk_cache/simple/simple_index.h"
|
||||
#include "net/test/gtest_util.h"
|
||||
#include "net/test/net_test_suite.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
using net::test::IsOk;
|
||||
|
||||
DiskCacheTest::DiskCacheTest() {
|
||||
CHECK(temp_dir_.CreateUniqueTempDir());
|
||||
cache_path_ = temp_dir_.GetPath();
|
||||
}
|
||||
|
||||
DiskCacheTest::~DiskCacheTest() = default;
|
||||
|
||||
bool DiskCacheTest::CopyTestCache(const std::string& name) {
|
||||
base::FilePath path;
|
||||
PathService::Get(base::DIR_SOURCE_ROOT, &path);
|
||||
path = path.AppendASCII("net");
|
||||
path = path.AppendASCII("data");
|
||||
path = path.AppendASCII("cache_tests");
|
||||
path = path.AppendASCII(name);
|
||||
|
||||
if (!CleanupCacheDir())
|
||||
return false;
|
||||
return base::CopyDirectory(path, cache_path_, false);
|
||||
}
|
||||
|
||||
bool DiskCacheTest::CleanupCacheDir() {
|
||||
return DeleteCache(cache_path_);
|
||||
}
|
||||
|
||||
void DiskCacheTest::TearDown() {
|
||||
base::RunLoop().RunUntilIdle();
|
||||
}
|
||||
|
||||
DiskCacheTestWithCache::TestIterator::TestIterator(
|
||||
std::unique_ptr<disk_cache::Backend::Iterator> iterator)
|
||||
: iterator_(std::move(iterator)) {}
|
||||
|
||||
DiskCacheTestWithCache::TestIterator::~TestIterator() = default;
|
||||
|
||||
int DiskCacheTestWithCache::TestIterator::OpenNextEntry(
|
||||
disk_cache::Entry** next_entry) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = iterator_->OpenNextEntry(next_entry, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
DiskCacheTestWithCache::DiskCacheTestWithCache()
|
||||
: DiskCacheTestWithCache(NetTestSuite::GetScopedTaskEnvironment()) {}
|
||||
|
||||
DiskCacheTestWithCache::DiskCacheTestWithCache(
|
||||
base::test::ScopedTaskEnvironment* scoped_task_env)
|
||||
: cache_impl_(NULL),
|
||||
simple_cache_impl_(NULL),
|
||||
mem_cache_(NULL),
|
||||
mask_(0),
|
||||
size_(0),
|
||||
type_(net::DISK_CACHE),
|
||||
memory_only_(false),
|
||||
simple_cache_mode_(false),
|
||||
simple_cache_wait_for_index_(true),
|
||||
force_creation_(false),
|
||||
new_eviction_(false),
|
||||
first_cleanup_(true),
|
||||
integrity_(true),
|
||||
use_current_thread_(false),
|
||||
scoped_task_env_(scoped_task_env) {}
|
||||
|
||||
DiskCacheTestWithCache::~DiskCacheTestWithCache() = default;
|
||||
|
||||
void DiskCacheTestWithCache::InitCache() {
|
||||
if (memory_only_)
|
||||
InitMemoryCache();
|
||||
else
|
||||
InitDiskCache();
|
||||
|
||||
ASSERT_TRUE(NULL != cache_);
|
||||
if (first_cleanup_)
|
||||
ASSERT_EQ(0, cache_->GetEntryCount());
|
||||
}
|
||||
|
||||
// We are expected to leak memory when simulating crashes.
|
||||
void DiskCacheTestWithCache::SimulateCrash() {
|
||||
ASSERT_TRUE(!memory_only_);
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_impl_->FlushQueueForTest(cb.callback());
|
||||
ASSERT_THAT(cb.GetResult(rv), IsOk());
|
||||
cache_impl_->ClearRefCountForTest();
|
||||
|
||||
cache_.reset();
|
||||
EXPECT_TRUE(CheckCacheIntegrity(cache_path_, new_eviction_, mask_));
|
||||
|
||||
CreateBackend(disk_cache::kNoRandom);
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::SetTestMode() {
|
||||
ASSERT_TRUE(!memory_only_);
|
||||
cache_impl_->SetUnitTestMode();
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::SetMaxSize(int size) {
|
||||
size_ = size;
|
||||
if (simple_cache_impl_)
|
||||
EXPECT_TRUE(simple_cache_impl_->SetMaxSize(size));
|
||||
|
||||
if (cache_impl_)
|
||||
EXPECT_TRUE(cache_impl_->SetMaxSize(size));
|
||||
|
||||
if (mem_cache_)
|
||||
EXPECT_TRUE(mem_cache_->SetMaxSize(size));
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::OpenEntry(const std::string& key,
|
||||
disk_cache::Entry** entry) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->OpenEntry(key, entry, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::CreateEntry(const std::string& key,
|
||||
disk_cache::Entry** entry) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->CreateEntry(key, entry, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::DoomEntry(const std::string& key) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->DoomEntry(key, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::DoomAllEntries() {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->DoomAllEntries(cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::DoomEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->DoomEntriesBetween(initial_time, end_time, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::DoomEntriesSince(const base::Time initial_time) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->DoomEntriesSince(initial_time, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::CalculateSizeOfAllEntries() {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->CalculateSizeOfAllEntries(cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::CalculateSizeOfEntriesBetween(
|
||||
const base::Time initial_time,
|
||||
const base::Time end_time) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_->CalculateSizeOfEntriesBetween(initial_time, end_time,
|
||||
cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
std::unique_ptr<DiskCacheTestWithCache::TestIterator>
|
||||
DiskCacheTestWithCache::CreateIterator() {
|
||||
return std::unique_ptr<TestIterator>(
|
||||
new TestIterator(cache_->CreateIterator()));
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::FlushQueueForTest() {
|
||||
if (memory_only_ || !cache_impl_)
|
||||
return;
|
||||
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_impl_->FlushQueueForTest(cb.callback());
|
||||
EXPECT_THAT(cb.GetResult(rv), IsOk());
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::RunTaskForTest(const base::Closure& closure) {
|
||||
if (memory_only_ || !cache_impl_) {
|
||||
closure.Run();
|
||||
return;
|
||||
}
|
||||
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_impl_->RunTaskForTest(closure, cb.callback());
|
||||
EXPECT_THAT(cb.GetResult(rv), IsOk());
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::ReadData(disk_cache::Entry* entry, int index,
|
||||
int offset, net::IOBuffer* buf, int len) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = entry->ReadData(index, offset, buf, len, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::WriteData(disk_cache::Entry* entry, int index,
|
||||
int offset, net::IOBuffer* buf, int len,
|
||||
bool truncate) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = entry->WriteData(index, offset, buf, len, cb.callback(), truncate);
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::ReadSparseData(disk_cache::Entry* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int len) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = entry->ReadSparseData(offset, buf, len, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
int DiskCacheTestWithCache::WriteSparseData(disk_cache::Entry* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int len) {
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = entry->WriteSparseData(offset, buf, len, cb.callback());
|
||||
return cb.GetResult(rv);
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::TrimForTest(bool empty) {
|
||||
RunTaskForTest(base::Bind(&disk_cache::BackendImpl::TrimForTest,
|
||||
base::Unretained(cache_impl_),
|
||||
empty));
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::TrimDeletedListForTest(bool empty) {
|
||||
RunTaskForTest(base::Bind(&disk_cache::BackendImpl::TrimDeletedListForTest,
|
||||
base::Unretained(cache_impl_),
|
||||
empty));
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::AddDelay() {
|
||||
if (simple_cache_mode_) {
|
||||
// The simple cache uses second resolution for many timeouts, so it's safest
|
||||
// to advance by at least whole seconds before falling back into the normal
|
||||
// disk cache epsilon advance.
|
||||
const base::Time initial_time = base::Time::Now();
|
||||
do {
|
||||
base::PlatformThread::YieldCurrentThread();
|
||||
} while (base::Time::Now() -
|
||||
initial_time < base::TimeDelta::FromSeconds(1));
|
||||
}
|
||||
|
||||
base::Time initial = base::Time::Now();
|
||||
while (base::Time::Now() <= initial) {
|
||||
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1));
|
||||
};
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::TearDown() {
|
||||
scoped_task_env_->RunUntilIdle();
|
||||
cache_.reset();
|
||||
|
||||
if (!memory_only_ && !simple_cache_mode_ && integrity_) {
|
||||
EXPECT_TRUE(CheckCacheIntegrity(cache_path_, new_eviction_, mask_));
|
||||
}
|
||||
scoped_task_env_->RunUntilIdle();
|
||||
if (simple_cache_mode_ && simple_file_tracker_)
|
||||
EXPECT_TRUE(simple_file_tracker_->IsEmptyForTesting());
|
||||
|
||||
DiskCacheTest::TearDown();
|
||||
|
||||
// We are documented as not keeping this past TearDown, and net_perftests
|
||||
// is written under this assumption.
|
||||
scoped_task_env_ = nullptr;
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::InitMemoryCache() {
|
||||
mem_cache_ = new disk_cache::MemBackendImpl(NULL);
|
||||
cache_.reset(mem_cache_);
|
||||
ASSERT_TRUE(cache_);
|
||||
|
||||
if (size_)
|
||||
EXPECT_TRUE(mem_cache_->SetMaxSize(size_));
|
||||
|
||||
ASSERT_TRUE(mem_cache_->Init());
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::InitDiskCache() {
|
||||
if (first_cleanup_)
|
||||
ASSERT_TRUE(CleanupCacheDir());
|
||||
|
||||
CreateBackend(disk_cache::kNoRandom);
|
||||
}
|
||||
|
||||
void DiskCacheTestWithCache::CreateBackend(uint32_t flags) {
|
||||
scoped_refptr<base::SingleThreadTaskRunner> runner;
|
||||
if (use_current_thread_)
|
||||
runner = base::ThreadTaskRunnerHandle::Get();
|
||||
else
|
||||
runner = nullptr; // let the backend sort it out.
|
||||
|
||||
if (simple_cache_mode_) {
|
||||
net::TestCompletionCallback cb;
|
||||
if (!simple_file_tracker_)
|
||||
simple_file_tracker_ = std::make_unique<disk_cache::SimpleFileTracker>();
|
||||
std::unique_ptr<disk_cache::SimpleBackendImpl> simple_backend =
|
||||
std::make_unique<disk_cache::SimpleBackendImpl>(
|
||||
cache_path_, /* cleanup_tracker = */ nullptr,
|
||||
simple_file_tracker_.get(), size_, type_, runner,
|
||||
/*net_log = */ nullptr);
|
||||
int rv = simple_backend->Init(cb.callback());
|
||||
ASSERT_THAT(cb.GetResult(rv), IsOk());
|
||||
simple_cache_impl_ = simple_backend.get();
|
||||
cache_ = std::move(simple_backend);
|
||||
if (simple_cache_wait_for_index_) {
|
||||
net::TestCompletionCallback wait_for_index_cb;
|
||||
rv = simple_cache_impl_->index()->ExecuteWhenReady(
|
||||
wait_for_index_cb.callback());
|
||||
ASSERT_THAT(wait_for_index_cb.GetResult(rv), IsOk());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (mask_)
|
||||
cache_impl_ = new disk_cache::BackendImpl(cache_path_, mask_, runner,
|
||||
/* net_log = */ nullptr);
|
||||
else
|
||||
cache_impl_ = new disk_cache::BackendImpl(cache_path_,
|
||||
/* cleanup_tracker = */ nullptr,
|
||||
runner, /* net_log = */ nullptr);
|
||||
cache_.reset(cache_impl_);
|
||||
ASSERT_TRUE(cache_);
|
||||
if (size_)
|
||||
EXPECT_TRUE(cache_impl_->SetMaxSize(size_));
|
||||
if (new_eviction_)
|
||||
cache_impl_->SetNewEviction();
|
||||
cache_impl_->SetType(type_);
|
||||
cache_impl_->SetFlags(flags);
|
||||
net::TestCompletionCallback cb;
|
||||
int rv = cache_impl_->Init(cb.callback());
|
||||
ASSERT_THAT(cb.GetResult(rv), IsOk());
|
||||
}
|
||||
212
net/disk_cache/disk_cache_test_base.h
Normal file
212
net/disk_cache/disk_cache_test_base.h
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
|
||||
#define NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "testing/platform_test.h"
|
||||
|
||||
namespace base {
|
||||
namespace test {
|
||||
|
||||
class ScopedTaskEnvironment;
|
||||
|
||||
} // namespace test
|
||||
} // namespace base
|
||||
|
||||
namespace net {
|
||||
|
||||
class IOBuffer;
|
||||
|
||||
} // namespace net
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class Backend;
|
||||
class BackendImpl;
|
||||
class Entry;
|
||||
class MemBackendImpl;
|
||||
class SimpleBackendImpl;
|
||||
class SimpleFileTracker;
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
// These tests can use the path service, which uses autoreleased objects on the
|
||||
// Mac, so this needs to be a PlatformTest. Even tests that do not require a
|
||||
// cache (and that do not need to be a DiskCacheTestWithCache) are susceptible
|
||||
// to this problem; all such tests should use TEST_F(DiskCacheTest, ...).
|
||||
class DiskCacheTest : public PlatformTest {
|
||||
protected:
|
||||
DiskCacheTest();
|
||||
~DiskCacheTest() override;
|
||||
|
||||
// Copies a set of cache files from the data folder to the test folder.
|
||||
bool CopyTestCache(const std::string& name);
|
||||
|
||||
// Deletes the contents of |cache_path_|.
|
||||
bool CleanupCacheDir();
|
||||
|
||||
void TearDown() override;
|
||||
|
||||
base::FilePath cache_path_;
|
||||
|
||||
private:
|
||||
base::ScopedTempDir temp_dir_;
|
||||
};
|
||||
|
||||
// Provides basic support for cache related tests.
|
||||
class DiskCacheTestWithCache : public DiskCacheTest {
|
||||
protected:
|
||||
class TestIterator {
|
||||
public:
|
||||
explicit TestIterator(
|
||||
std::unique_ptr<disk_cache::Backend::Iterator> iterator);
|
||||
~TestIterator();
|
||||
|
||||
int OpenNextEntry(disk_cache::Entry** next_entry);
|
||||
|
||||
private:
|
||||
std::unique_ptr<disk_cache::Backend::Iterator> iterator_;
|
||||
};
|
||||
|
||||
// Assumes NetTestSuite is available.
|
||||
DiskCacheTestWithCache();
|
||||
|
||||
// Does not take ownership of |scoped_task_env|, and will not use it past
|
||||
// TearDown(). Does not require NetTestSuite.
|
||||
explicit DiskCacheTestWithCache(
|
||||
base::test::ScopedTaskEnvironment* scoped_task_env);
|
||||
~DiskCacheTestWithCache() override;
|
||||
|
||||
void CreateBackend(uint32_t flags);
|
||||
|
||||
void InitCache();
|
||||
void SimulateCrash();
|
||||
void SetTestMode();
|
||||
|
||||
void SetMemoryOnlyMode() {
|
||||
memory_only_ = true;
|
||||
}
|
||||
|
||||
void SetSimpleCacheMode() {
|
||||
simple_cache_mode_ = true;
|
||||
}
|
||||
|
||||
void SetMask(uint32_t mask) { mask_ = mask; }
|
||||
|
||||
void SetMaxSize(int size);
|
||||
|
||||
// Deletes and re-creates the files on initialization errors.
|
||||
void SetForceCreation() {
|
||||
force_creation_ = true;
|
||||
}
|
||||
|
||||
void SetNewEviction() {
|
||||
new_eviction_ = true;
|
||||
}
|
||||
|
||||
void DisableSimpleCacheWaitForIndex() {
|
||||
simple_cache_wait_for_index_ = false;
|
||||
}
|
||||
|
||||
void DisableFirstCleanup() {
|
||||
first_cleanup_ = false;
|
||||
}
|
||||
|
||||
void DisableIntegrityCheck() {
|
||||
integrity_ = false;
|
||||
}
|
||||
|
||||
void UseCurrentThread() {
|
||||
use_current_thread_ = true;
|
||||
}
|
||||
|
||||
void SetCacheType(net::CacheType type) {
|
||||
type_ = type;
|
||||
}
|
||||
|
||||
// Utility methods to access the cache and wait for each operation to finish.
|
||||
int OpenEntry(const std::string& key, disk_cache::Entry** entry);
|
||||
int CreateEntry(const std::string& key, disk_cache::Entry** entry);
|
||||
int DoomEntry(const std::string& key);
|
||||
int DoomAllEntries();
|
||||
int DoomEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time);
|
||||
int CalculateSizeOfAllEntries();
|
||||
int CalculateSizeOfEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time);
|
||||
int DoomEntriesSince(const base::Time initial_time);
|
||||
std::unique_ptr<TestIterator> CreateIterator();
|
||||
void FlushQueueForTest();
|
||||
void RunTaskForTest(const base::Closure& closure);
|
||||
int ReadData(disk_cache::Entry* entry, int index, int offset,
|
||||
net::IOBuffer* buf, int len);
|
||||
int WriteData(disk_cache::Entry* entry, int index, int offset,
|
||||
net::IOBuffer* buf, int len, bool truncate);
|
||||
int ReadSparseData(disk_cache::Entry* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int len);
|
||||
int WriteSparseData(disk_cache::Entry* entry,
|
||||
int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int len);
|
||||
|
||||
// Asks the cache to trim an entry. If |empty| is true, the whole cache is
|
||||
// deleted.
|
||||
void TrimForTest(bool empty);
|
||||
|
||||
// Asks the cache to trim an entry from the deleted list. If |empty| is
|
||||
// true, the whole list is deleted.
|
||||
void TrimDeletedListForTest(bool empty);
|
||||
|
||||
// Makes sure that some time passes before continuing the test. Time::Now()
|
||||
// before and after this method will not be the same.
|
||||
void AddDelay();
|
||||
|
||||
// DiskCacheTest:
|
||||
void TearDown() override;
|
||||
|
||||
// cache_ will always have a valid object, regardless of how the cache was
|
||||
// initialized. The implementation pointers can be NULL.
|
||||
std::unique_ptr<disk_cache::Backend> cache_;
|
||||
disk_cache::BackendImpl* cache_impl_;
|
||||
std::unique_ptr<disk_cache::SimpleFileTracker> simple_file_tracker_;
|
||||
disk_cache::SimpleBackendImpl* simple_cache_impl_;
|
||||
disk_cache::MemBackendImpl* mem_cache_;
|
||||
|
||||
uint32_t mask_;
|
||||
int size_;
|
||||
net::CacheType type_;
|
||||
bool memory_only_;
|
||||
bool simple_cache_mode_;
|
||||
bool simple_cache_wait_for_index_;
|
||||
bool force_creation_;
|
||||
bool new_eviction_;
|
||||
bool first_cleanup_;
|
||||
bool integrity_;
|
||||
bool use_current_thread_;
|
||||
// This is intentionally left uninitialized, to be used by any test.
|
||||
bool success_;
|
||||
|
||||
private:
|
||||
void InitMemoryCache();
|
||||
void InitDiskCache();
|
||||
base::test::ScopedTaskEnvironment* scoped_task_env_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DiskCacheTestWithCache);
|
||||
};
|
||||
|
||||
#endif // NET_DISK_CACHE_DISK_CACHE_TEST_BASE_H_
|
||||
146
net/disk_cache/disk_cache_test_util.cc
Normal file
146
net/disk_cache/disk_cache_test_util.cc
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/disk_cache_test_util.h"
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/blockfile/backend_impl.h"
|
||||
#include "net/disk_cache/blockfile/file.h"
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
|
||||
using base::Time;
|
||||
using base::TimeDelta;
|
||||
|
||||
std::string GenerateKey(bool same_length) {
|
||||
char key[200];
|
||||
CacheTestFillBuffer(key, sizeof(key), same_length);
|
||||
|
||||
key[199] = '\0';
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
void CacheTestFillBuffer(char* buffer, size_t len, bool no_nulls) {
|
||||
static bool called = false;
|
||||
if (!called) {
|
||||
called = true;
|
||||
int seed = static_cast<int>(Time::Now().ToInternalValue());
|
||||
srand(seed);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
buffer[i] = static_cast<char>(rand());
|
||||
if (!buffer[i] && no_nulls)
|
||||
buffer[i] = 'g';
|
||||
}
|
||||
if (len && !buffer[0])
|
||||
buffer[0] = 'g';
|
||||
}
|
||||
|
||||
bool CreateCacheTestFile(const base::FilePath& name) {
|
||||
int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
|
||||
base::File::FLAG_WRITE;
|
||||
|
||||
base::File file(name, flags);
|
||||
if (!file.IsValid())
|
||||
return false;
|
||||
|
||||
file.SetLength(4 * 1024 * 1024);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteCache(const base::FilePath& path) {
|
||||
disk_cache::DeleteCache(path, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckCacheIntegrity(const base::FilePath& path,
|
||||
bool new_eviction,
|
||||
uint32_t mask) {
|
||||
std::unique_ptr<disk_cache::BackendImpl> cache(new disk_cache::BackendImpl(
|
||||
path, mask, base::ThreadTaskRunnerHandle::Get(), NULL));
|
||||
if (!cache.get())
|
||||
return false;
|
||||
if (new_eviction)
|
||||
cache->SetNewEviction();
|
||||
cache->SetFlags(disk_cache::kNoRandom);
|
||||
if (cache->SyncInit() != net::OK)
|
||||
return false;
|
||||
return cache->SelfCheck() >= 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
MessageLoopHelper::MessageLoopHelper()
|
||||
: num_callbacks_(0),
|
||||
num_iterations_(0),
|
||||
last_(0),
|
||||
completed_(false),
|
||||
callback_reused_error_(false),
|
||||
callbacks_called_(0) {}
|
||||
|
||||
MessageLoopHelper::~MessageLoopHelper() = default;
|
||||
|
||||
bool MessageLoopHelper::WaitUntilCacheIoFinished(int num_callbacks) {
|
||||
if (num_callbacks == callbacks_called_)
|
||||
return true;
|
||||
|
||||
ExpectCallbacks(num_callbacks);
|
||||
// Create a recurrent timer of 50 ms.
|
||||
base::RepeatingTimer timer;
|
||||
timer.Start(FROM_HERE, TimeDelta::FromMilliseconds(50), this,
|
||||
&MessageLoopHelper::TimerExpired);
|
||||
run_loop_ = std::make_unique<base::RunLoop>();
|
||||
run_loop_->Run();
|
||||
run_loop_.reset();
|
||||
|
||||
return completed_;
|
||||
}
|
||||
|
||||
// Quits the message loop when all callbacks are called or we've been waiting
|
||||
// too long for them (2 secs without a callback).
|
||||
void MessageLoopHelper::TimerExpired() {
|
||||
CHECK_LE(callbacks_called_, num_callbacks_);
|
||||
if (callbacks_called_ == num_callbacks_) {
|
||||
completed_ = true;
|
||||
run_loop_->Quit();
|
||||
} else {
|
||||
// Not finished yet. See if we have to abort.
|
||||
if (last_ == callbacks_called_)
|
||||
num_iterations_++;
|
||||
else
|
||||
last_ = callbacks_called_;
|
||||
if (40 == num_iterations_)
|
||||
run_loop_->Quit();
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
CallbackTest::CallbackTest(MessageLoopHelper* helper,
|
||||
bool reuse)
|
||||
: helper_(helper),
|
||||
reuse_(reuse ? 0 : 1) {
|
||||
}
|
||||
|
||||
CallbackTest::~CallbackTest() = default;
|
||||
|
||||
// On the actual callback, increase the number of tests received and check for
|
||||
// errors (an unexpected test received)
|
||||
void CallbackTest::Run(int result) {
|
||||
last_result_ = result;
|
||||
|
||||
if (reuse_) {
|
||||
DCHECK_EQ(1, reuse_);
|
||||
if (2 == reuse_)
|
||||
helper_->set_callback_reused_error(true);
|
||||
reuse_++;
|
||||
}
|
||||
|
||||
helper_->CallbackWasCalled();
|
||||
}
|
||||
110
net/disk_cache/disk_cache_test_util.h
Normal file
110
net/disk_cache/disk_cache_test_util.h
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H_
|
||||
#define NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
// Re-creates a given test file inside the cache test folder.
|
||||
bool CreateCacheTestFile(const base::FilePath& name);
|
||||
|
||||
// Deletes all file son the cache.
|
||||
bool DeleteCache(const base::FilePath& path);
|
||||
|
||||
// Fills buffer with random values (may contain nulls unless no_nulls is true).
|
||||
void CacheTestFillBuffer(char* buffer, size_t len, bool no_nulls);
|
||||
|
||||
// Generates a random key of up to 200 bytes.
|
||||
std::string GenerateKey(bool same_length);
|
||||
|
||||
// Returns true if the cache is not corrupt.
|
||||
bool CheckCacheIntegrity(const base::FilePath& path,
|
||||
bool new_eviction,
|
||||
uint32_t mask);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Simple helper to deal with the message loop on a test.
|
||||
class MessageLoopHelper {
|
||||
public:
|
||||
MessageLoopHelper();
|
||||
~MessageLoopHelper();
|
||||
|
||||
// Run the message loop and wait for num_callbacks before returning. Returns
|
||||
// false if we are waiting to long. Each callback that will be waited on is
|
||||
// required to call CallbackWasCalled() to indicate when it was called.
|
||||
bool WaitUntilCacheIoFinished(int num_callbacks);
|
||||
|
||||
// True if a given callback was called more times than it expected.
|
||||
bool callback_reused_error() const { return callback_reused_error_; }
|
||||
void set_callback_reused_error(bool error) {
|
||||
callback_reused_error_ = error;
|
||||
}
|
||||
|
||||
int callbacks_called() const { return callbacks_called_; }
|
||||
// Report that a callback was called. Each callback that will be waited on
|
||||
// via WaitUntilCacheIoFinished() is expected to call this method to
|
||||
// indicate when it has been executed.
|
||||
void CallbackWasCalled() { ++callbacks_called_; }
|
||||
|
||||
private:
|
||||
// Sets the number of callbacks that can be received so far.
|
||||
void ExpectCallbacks(int num_callbacks) {
|
||||
num_callbacks_ = num_callbacks;
|
||||
num_iterations_ = last_ = 0;
|
||||
completed_ = false;
|
||||
}
|
||||
|
||||
// Called periodically to test if WaitUntilCacheIoFinished should return.
|
||||
void TimerExpired();
|
||||
|
||||
std::unique_ptr<base::RunLoop> run_loop_;
|
||||
int num_callbacks_;
|
||||
int num_iterations_;
|
||||
int last_;
|
||||
bool completed_;
|
||||
|
||||
// True if a callback was called/reused more than expected.
|
||||
bool callback_reused_error_;
|
||||
int callbacks_called_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MessageLoopHelper);
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Simple callback to process IO completions from the cache. It allows tests
|
||||
// with multiple simultaneous IO operations.
|
||||
class CallbackTest {
|
||||
public:
|
||||
// Creates a new CallbackTest object. When the callback is called, it will
|
||||
// update |helper|. If |reuse| is false and a callback is called more than
|
||||
// once, or if |reuse| is true and a callback is called more than twice, an
|
||||
// error will be reported to |helper|.
|
||||
CallbackTest(MessageLoopHelper* helper, bool reuse);
|
||||
~CallbackTest();
|
||||
|
||||
void Run(int result);
|
||||
|
||||
int last_result() const { return last_result_; }
|
||||
|
||||
private:
|
||||
MessageLoopHelper* helper_;
|
||||
int reuse_;
|
||||
int last_result_;
|
||||
DISALLOW_COPY_AND_ASSIGN(CallbackTest);
|
||||
};
|
||||
|
||||
#endif // NET_DISK_CACHE_DISK_CACHE_TEST_UTIL_H_
|
||||
341
net/disk_cache/memory/mem_backend_impl.cc
Normal file
341
net/disk_cache/memory/mem_backend_impl.cc
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/memory/mem_backend_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/sys_info.h"
|
||||
#include "base/task_scheduler/post_task.h"
|
||||
#include "base/threading/sequenced_task_runner_handle.h"
|
||||
#include "base/trace_event/memory_usage_estimator.h"
|
||||
#include "base/trace_event/process_memory_dump.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
#include "net/disk_cache/memory/mem_entry_impl.h"
|
||||
|
||||
using base::Time;
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kDefaultInMemoryCacheSize = 10 * 1024 * 1024;
|
||||
const int kDefaultEvictionSize = kDefaultInMemoryCacheSize / 10;
|
||||
|
||||
bool CheckLRUListOrder(const base::LinkedList<MemEntryImpl>& lru_list) {
|
||||
// TODO(gavinp): Check MemBackendImpl::current_size_ here as well.
|
||||
base::Time previous_last_use_time;
|
||||
for (base::LinkNode<MemEntryImpl>* node = lru_list.head();
|
||||
node != lru_list.end(); node = node->next()) {
|
||||
if (node->value()->GetLastUsed() < previous_last_use_time)
|
||||
return false;
|
||||
previous_last_use_time = node->value()->GetLastUsed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MemBackendImpl::MemBackendImpl(net::NetLog* net_log)
|
||||
: max_size_(0), current_size_(0), net_log_(net_log), weak_factory_(this) {
|
||||
}
|
||||
|
||||
MemBackendImpl::~MemBackendImpl() {
|
||||
DCHECK(CheckLRUListOrder(lru_list_));
|
||||
while (!entries_.empty())
|
||||
entries_.begin()->second->Doom();
|
||||
DCHECK_EQ(0, current_size_);
|
||||
|
||||
if (!post_cleanup_callback_.is_null())
|
||||
base::SequencedTaskRunnerHandle::Get()->PostTask(
|
||||
FROM_HERE, std::move(post_cleanup_callback_));
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<MemBackendImpl> MemBackendImpl::CreateBackend(
|
||||
int max_bytes,
|
||||
net::NetLog* net_log) {
|
||||
std::unique_ptr<MemBackendImpl> cache(
|
||||
std::make_unique<MemBackendImpl>(net_log));
|
||||
cache->SetMaxSize(max_bytes);
|
||||
if (cache->Init())
|
||||
return cache;
|
||||
|
||||
LOG(ERROR) << "Unable to create cache";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool MemBackendImpl::Init() {
|
||||
if (max_size_)
|
||||
return true;
|
||||
|
||||
int64_t total_memory = base::SysInfo::AmountOfPhysicalMemory();
|
||||
|
||||
if (total_memory <= 0) {
|
||||
max_size_ = kDefaultInMemoryCacheSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We want to use up to 2% of the computer's memory, with a limit of 50 MB,
|
||||
// reached on system with more than 2.5 GB of RAM.
|
||||
total_memory = total_memory * 2 / 100;
|
||||
if (total_memory > kDefaultInMemoryCacheSize * 5)
|
||||
max_size_ = kDefaultInMemoryCacheSize * 5;
|
||||
else
|
||||
max_size_ = static_cast<int32_t>(total_memory);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemBackendImpl::SetMaxSize(int max_bytes) {
|
||||
static_assert(sizeof(max_bytes) == sizeof(max_size_),
|
||||
"unsupported int model");
|
||||
if (max_bytes < 0)
|
||||
return false;
|
||||
|
||||
// Zero size means use the default.
|
||||
if (!max_bytes)
|
||||
return true;
|
||||
|
||||
max_size_ = max_bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
int MemBackendImpl::MaxFileSize() const {
|
||||
return max_size_ / 8;
|
||||
}
|
||||
|
||||
void MemBackendImpl::OnEntryInserted(MemEntryImpl* entry) {
|
||||
lru_list_.Append(entry);
|
||||
}
|
||||
|
||||
void MemBackendImpl::OnEntryUpdated(MemEntryImpl* entry) {
|
||||
DCHECK(CheckLRUListOrder(lru_list_));
|
||||
// LinkedList<>::RemoveFromList() removes |entry| from |lru_list_|.
|
||||
entry->RemoveFromList();
|
||||
lru_list_.Append(entry);
|
||||
}
|
||||
|
||||
void MemBackendImpl::OnEntryDoomed(MemEntryImpl* entry) {
|
||||
DCHECK(CheckLRUListOrder(lru_list_));
|
||||
if (entry->type() == MemEntryImpl::PARENT_ENTRY)
|
||||
entries_.erase(entry->key());
|
||||
// LinkedList<>::RemoveFromList() removes |entry| from |lru_list_|.
|
||||
entry->RemoveFromList();
|
||||
}
|
||||
|
||||
void MemBackendImpl::ModifyStorageSize(int32_t delta) {
|
||||
current_size_ += delta;
|
||||
if (delta > 0)
|
||||
EvictIfNeeded();
|
||||
}
|
||||
|
||||
bool MemBackendImpl::HasExceededStorageSize() const {
|
||||
return current_size_ > max_size_;
|
||||
}
|
||||
|
||||
void MemBackendImpl::SetPostCleanupCallback(base::OnceClosure cb) {
|
||||
DCHECK(post_cleanup_callback_.is_null());
|
||||
post_cleanup_callback_ = std::move(cb);
|
||||
}
|
||||
|
||||
net::CacheType MemBackendImpl::GetCacheType() const {
|
||||
return net::MEMORY_CACHE;
|
||||
}
|
||||
|
||||
int32_t MemBackendImpl::GetEntryCount() const {
|
||||
return static_cast<int32_t>(entries_.size());
|
||||
}
|
||||
|
||||
int MemBackendImpl::OpenEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) {
|
||||
EntryMap::iterator it = entries_.find(key);
|
||||
if (it == entries_.end())
|
||||
return net::ERR_FAILED;
|
||||
|
||||
it->second->Open();
|
||||
|
||||
*entry = it->second;
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
int MemBackendImpl::CreateEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) {
|
||||
std::pair<EntryMap::iterator, bool> create_result =
|
||||
entries_.insert(EntryMap::value_type(key, nullptr));
|
||||
const bool did_insert = create_result.second;
|
||||
if (!did_insert)
|
||||
return net::ERR_FAILED;
|
||||
|
||||
MemEntryImpl* cache_entry = new MemEntryImpl(this, key, net_log_);
|
||||
create_result.first->second = cache_entry;
|
||||
*entry = cache_entry;
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
int MemBackendImpl::DoomEntry(const std::string& key,
|
||||
const CompletionCallback& callback) {
|
||||
EntryMap::iterator it = entries_.find(key);
|
||||
if (it == entries_.end())
|
||||
return net::ERR_FAILED;
|
||||
|
||||
it->second->Doom();
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
int MemBackendImpl::DoomAllEntries(const CompletionCallback& callback) {
|
||||
return DoomEntriesBetween(Time(), Time(), callback);
|
||||
}
|
||||
|
||||
int MemBackendImpl::DoomEntriesBetween(Time initial_time,
|
||||
Time end_time,
|
||||
const CompletionCallback& callback) {
|
||||
if (end_time.is_null())
|
||||
end_time = Time::Max();
|
||||
DCHECK_GE(end_time, initial_time);
|
||||
|
||||
base::LinkNode<MemEntryImpl>* node = lru_list_.head();
|
||||
while (node != lru_list_.end() && node->value()->GetLastUsed() < initial_time)
|
||||
node = node->next();
|
||||
while (node != lru_list_.end() && node->value()->GetLastUsed() < end_time) {
|
||||
MemEntryImpl* to_doom = node->value();
|
||||
node = node->next();
|
||||
to_doom->Doom();
|
||||
}
|
||||
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
int MemBackendImpl::DoomEntriesSince(Time initial_time,
|
||||
const CompletionCallback& callback) {
|
||||
return DoomEntriesBetween(initial_time, Time::Max(), callback);
|
||||
}
|
||||
|
||||
int MemBackendImpl::CalculateSizeOfAllEntries(
|
||||
const CompletionCallback& callback) {
|
||||
return current_size_;
|
||||
}
|
||||
|
||||
int MemBackendImpl::CalculateSizeOfEntriesBetween(
|
||||
base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) {
|
||||
if (end_time.is_null())
|
||||
end_time = Time::Max();
|
||||
DCHECK_GE(end_time, initial_time);
|
||||
|
||||
int size = 0;
|
||||
base::LinkNode<MemEntryImpl>* node = lru_list_.head();
|
||||
while (node != lru_list_.end() && node->value()->GetLastUsed() < initial_time)
|
||||
node = node->next();
|
||||
while (node != lru_list_.end() && node->value()->GetLastUsed() < end_time) {
|
||||
MemEntryImpl* entry = node->value();
|
||||
size += entry->GetStorageSize();
|
||||
node = node->next();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
class MemBackendImpl::MemIterator final : public Backend::Iterator {
|
||||
public:
|
||||
explicit MemIterator(base::WeakPtr<MemBackendImpl> backend)
|
||||
: backend_(backend) {}
|
||||
|
||||
int OpenNextEntry(Entry** next_entry,
|
||||
const CompletionCallback& callback) override {
|
||||
if (!backend_)
|
||||
return net::ERR_FAILED;
|
||||
|
||||
if (!backend_keys_) {
|
||||
backend_keys_ = std::make_unique<Strings>(backend_->entries_.size());
|
||||
for (const auto& iter : backend_->entries_)
|
||||
backend_keys_->push_back(iter.first);
|
||||
current_ = backend_keys_->begin();
|
||||
} else {
|
||||
current_++;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (current_ == backend_keys_->end()) {
|
||||
*next_entry = nullptr;
|
||||
backend_keys_.reset();
|
||||
return net::ERR_FAILED;
|
||||
}
|
||||
|
||||
const auto& entry_iter = backend_->entries_.find(*current_);
|
||||
if (entry_iter == backend_->entries_.end()) {
|
||||
// The key is no longer in the cache, move on to the next key.
|
||||
current_++;
|
||||
continue;
|
||||
}
|
||||
|
||||
entry_iter->second->Open();
|
||||
*next_entry = entry_iter->second;
|
||||
return net::OK;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
using Strings = std::vector<std::string>;
|
||||
|
||||
base::WeakPtr<MemBackendImpl> backend_;
|
||||
std::unique_ptr<Strings> backend_keys_;
|
||||
Strings::iterator current_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Backend::Iterator> MemBackendImpl::CreateIterator() {
|
||||
return std::unique_ptr<Backend::Iterator>(
|
||||
new MemIterator(weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void MemBackendImpl::OnExternalCacheHit(const std::string& key) {
|
||||
EntryMap::iterator it = entries_.find(key);
|
||||
if (it != entries_.end())
|
||||
it->second->UpdateStateOnUse(MemEntryImpl::ENTRY_WAS_NOT_MODIFIED);
|
||||
}
|
||||
|
||||
size_t MemBackendImpl::DumpMemoryStats(
|
||||
base::trace_event::ProcessMemoryDump* pmd,
|
||||
const std::string& parent_absolute_name) const {
|
||||
base::trace_event::MemoryAllocatorDump* dump =
|
||||
pmd->CreateAllocatorDump(parent_absolute_name + "/memory_backend");
|
||||
|
||||
// Entries in lru_list_ will be counted by EMU but not in entries_ since
|
||||
// they're pointers.
|
||||
size_t size = base::trace_event::EstimateMemoryUsage(lru_list_) +
|
||||
base::trace_event::EstimateMemoryUsage(entries_);
|
||||
dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
|
||||
base::trace_event::MemoryAllocatorDump::kUnitsBytes, size);
|
||||
dump->AddScalar("mem_backend_size",
|
||||
base::trace_event::MemoryAllocatorDump::kUnitsBytes,
|
||||
current_size_);
|
||||
dump->AddScalar("mem_backend_max_size",
|
||||
base::trace_event::MemoryAllocatorDump::kUnitsBytes,
|
||||
max_size_);
|
||||
return size;
|
||||
}
|
||||
|
||||
void MemBackendImpl::EvictIfNeeded() {
|
||||
if (current_size_ <= max_size_)
|
||||
return;
|
||||
|
||||
int target_size = std::max(0, max_size_ - kDefaultEvictionSize);
|
||||
|
||||
base::LinkNode<MemEntryImpl>* entry = lru_list_.head();
|
||||
while (current_size_ > target_size && entry != lru_list_.end()) {
|
||||
MemEntryImpl* to_doom = entry->value();
|
||||
entry = entry->next();
|
||||
if (!to_doom->InUse())
|
||||
to_doom->Doom();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
142
net/disk_cache/memory/mem_backend_impl.h
Normal file
142
net/disk_cache/memory/mem_backend_impl.h
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// See net/disk_cache/disk_cache.h for the public interface of the cache.
|
||||
|
||||
#ifndef NET_DISK_CACHE_MEMORY_MEM_BACKEND_IMPL_H_
|
||||
#define NET_DISK_CACHE_MEMORY_MEM_BACKEND_IMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/containers/linked_list.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/disk_cache/memory/mem_entry_impl.h"
|
||||
|
||||
namespace net {
|
||||
class NetLog;
|
||||
} // namespace net
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// This class implements the Backend interface. An object of this class handles
|
||||
// the operations of the cache without writing to disk.
|
||||
class NET_EXPORT_PRIVATE MemBackendImpl final : public Backend {
|
||||
public:
|
||||
explicit MemBackendImpl(net::NetLog* net_log);
|
||||
~MemBackendImpl() override;
|
||||
|
||||
// Returns an instance of a Backend implemented only in memory. The returned
|
||||
// object should be deleted when not needed anymore. max_bytes is the maximum
|
||||
// size the cache can grow to. If zero is passed in as max_bytes, the cache
|
||||
// will determine the value to use based on the available memory. The returned
|
||||
// pointer can be NULL if a fatal error is found.
|
||||
static std::unique_ptr<MemBackendImpl> CreateBackend(int max_bytes,
|
||||
net::NetLog* net_log);
|
||||
|
||||
// Performs general initialization for this current instance of the cache.
|
||||
bool Init();
|
||||
|
||||
// Sets the maximum size for the total amount of data stored by this instance.
|
||||
bool SetMaxSize(int max_bytes);
|
||||
|
||||
// Returns the maximum size for a file to reside on the cache.
|
||||
int MaxFileSize() const;
|
||||
|
||||
// These next methods (before the implementation of the Backend interface) are
|
||||
// called by MemEntryImpl to update the state of the backend during the entry
|
||||
// lifecycle.
|
||||
|
||||
// Signals that new entry has been created, and should be placed in
|
||||
// |lru_list_| so that it is eligable for eviction.
|
||||
void OnEntryInserted(MemEntryImpl* entry);
|
||||
|
||||
// Signals that an entry has been updated, and thus should be moved to the end
|
||||
// of |lru_list_|.
|
||||
void OnEntryUpdated(MemEntryImpl* entry);
|
||||
|
||||
// Signals that an entry has been doomed, and so it should be removed from the
|
||||
// list of active entries as appropriate, as well as removed from the
|
||||
// |lru_list_|.
|
||||
void OnEntryDoomed(MemEntryImpl* entry);
|
||||
|
||||
// Adjust the current size of this backend by |delta|. This is used to
|
||||
// determine if eviction is neccessary and when eviction is finished.
|
||||
void ModifyStorageSize(int32_t delta);
|
||||
|
||||
// Returns true if the cache's size is greater than the maximum allowed
|
||||
// size.
|
||||
bool HasExceededStorageSize() const;
|
||||
|
||||
// Sets a callback to be posted after we are destroyed. Should be called at
|
||||
// most once.
|
||||
void SetPostCleanupCallback(base::OnceClosure cb);
|
||||
|
||||
// Backend interface.
|
||||
net::CacheType GetCacheType() const override;
|
||||
int32_t GetEntryCount() const override;
|
||||
int OpenEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) override;
|
||||
int CreateEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomEntry(const std::string& key,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomAllEntries(const CompletionCallback& callback) override;
|
||||
int DoomEntriesBetween(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomEntriesSince(base::Time initial_time,
|
||||
const CompletionCallback& callback) override;
|
||||
int CalculateSizeOfAllEntries(const CompletionCallback& callback) override;
|
||||
int CalculateSizeOfEntriesBetween(
|
||||
base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) override;
|
||||
std::unique_ptr<Iterator> CreateIterator() override;
|
||||
void GetStats(base::StringPairs* stats) override {}
|
||||
void OnExternalCacheHit(const std::string& key) override;
|
||||
size_t DumpMemoryStats(
|
||||
base::trace_event::ProcessMemoryDump* pmd,
|
||||
const std::string& parent_absolute_name) const override;
|
||||
|
||||
private:
|
||||
class MemIterator;
|
||||
friend class MemIterator;
|
||||
|
||||
using EntryMap = std::unordered_map<std::string, MemEntryImpl*>;
|
||||
|
||||
// Deletes entries from the cache until the current size is below the limit.
|
||||
void EvictIfNeeded();
|
||||
|
||||
EntryMap entries_;
|
||||
|
||||
// Stored in increasing order of last use time, from least recently used to
|
||||
// most recently used.
|
||||
base::LinkedList<MemEntryImpl> lru_list_;
|
||||
|
||||
int32_t max_size_; // Maximum data size for this instance.
|
||||
int32_t current_size_;
|
||||
|
||||
net::NetLog* net_log_;
|
||||
base::OnceClosure post_cleanup_callback_;
|
||||
|
||||
base::WeakPtrFactory<MemBackendImpl> weak_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MemBackendImpl);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_MEMORY_MEM_BACKEND_IMPL_H_
|
||||
624
net/disk_cache/memory/mem_entry_impl.cc
Normal file
624
net/disk_cache/memory/mem_entry_impl.cc
Normal file
@@ -0,0 +1,624 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/memory/mem_entry_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/values.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/memory/mem_backend_impl.h"
|
||||
#include "net/disk_cache/net_log_parameters.h"
|
||||
#include "net/log/net_log_event_type.h"
|
||||
#include "net/log/net_log_source_type.h"
|
||||
|
||||
using base::Time;
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kSparseData = 1;
|
||||
|
||||
// Maximum size of a sparse entry is 2 to the power of this number.
|
||||
const int kMaxSparseEntryBits = 12;
|
||||
|
||||
// Sparse entry has maximum size of 4KB.
|
||||
const int kMaxSparseEntrySize = 1 << kMaxSparseEntryBits;
|
||||
|
||||
// This enum is used for histograms, so only append to the end.
|
||||
enum WriteResult {
|
||||
WRITE_RESULT_SUCCESS = 0,
|
||||
WRITE_RESULT_INVALID_ARGUMENT = 1,
|
||||
WRITE_RESULT_OVER_MAX_ENTRY_SIZE = 2,
|
||||
WRITE_RESULT_EXCEEDED_CACHE_STORAGE_SIZE = 3,
|
||||
WRITE_RESULT_MAX = 4,
|
||||
};
|
||||
|
||||
void RecordWriteResult(WriteResult result) {
|
||||
UMA_HISTOGRAM_ENUMERATION("MemCache.WriteResult", result, WRITE_RESULT_MAX);
|
||||
}
|
||||
|
||||
// Convert global offset to child index.
|
||||
int ToChildIndex(int64_t offset) {
|
||||
return static_cast<int>(offset >> kMaxSparseEntryBits);
|
||||
}
|
||||
|
||||
// Convert global offset to offset in child entry.
|
||||
int ToChildOffset(int64_t offset) {
|
||||
return static_cast<int>(offset & (kMaxSparseEntrySize - 1));
|
||||
}
|
||||
|
||||
// Returns a name for a child entry given the base_name of the parent and the
|
||||
// child_id. This name is only used for logging purposes.
|
||||
// If the entry is called entry_name, child entries will be named something
|
||||
// like Range_entry_name:YYY where YYY is the number of the particular child.
|
||||
std::string GenerateChildName(const std::string& base_name, int child_id) {
|
||||
return base::StringPrintf("Range_%s:%i", base_name.c_str(), child_id);
|
||||
}
|
||||
|
||||
// Returns NetLog parameters for the creation of a MemEntryImpl. A separate
|
||||
// function is needed because child entries don't store their key().
|
||||
std::unique_ptr<base::Value> NetLogEntryCreationCallback(
|
||||
const MemEntryImpl* entry,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
std::string key;
|
||||
switch (entry->type()) {
|
||||
case MemEntryImpl::PARENT_ENTRY:
|
||||
key = entry->key();
|
||||
break;
|
||||
case MemEntryImpl::CHILD_ENTRY:
|
||||
key = GenerateChildName(entry->parent()->key(), entry->child_id());
|
||||
break;
|
||||
}
|
||||
dict->SetString("key", key);
|
||||
dict->SetBoolean("created", true);
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MemEntryImpl::MemEntryImpl(MemBackendImpl* backend,
|
||||
const std::string& key,
|
||||
net::NetLog* net_log)
|
||||
: MemEntryImpl(backend,
|
||||
key,
|
||||
0, // child_id
|
||||
nullptr, // parent
|
||||
net_log) {
|
||||
Open();
|
||||
// Just creating the entry (without any data) could cause the storage to
|
||||
// grow beyond capacity, but we allow such infractions.
|
||||
backend_->ModifyStorageSize(GetStorageSize());
|
||||
}
|
||||
|
||||
MemEntryImpl::MemEntryImpl(MemBackendImpl* backend,
|
||||
int child_id,
|
||||
MemEntryImpl* parent,
|
||||
net::NetLog* net_log)
|
||||
: MemEntryImpl(backend,
|
||||
std::string(), // key
|
||||
child_id,
|
||||
parent,
|
||||
net_log) {
|
||||
(*parent_->children_)[child_id] = this;
|
||||
}
|
||||
|
||||
void MemEntryImpl::Open() {
|
||||
// Only a parent entry can be opened.
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
++ref_count_;
|
||||
DCHECK_GE(ref_count_, 1);
|
||||
DCHECK(!doomed_);
|
||||
}
|
||||
|
||||
bool MemEntryImpl::InUse() const {
|
||||
if (type() == CHILD_ENTRY)
|
||||
return parent_->InUse();
|
||||
|
||||
return ref_count_ > 0;
|
||||
}
|
||||
|
||||
int MemEntryImpl::GetStorageSize() const {
|
||||
int storage_size = static_cast<int32_t>(key_.size());
|
||||
for (const auto& i : data_)
|
||||
storage_size += i.size();
|
||||
return storage_size;
|
||||
}
|
||||
|
||||
void MemEntryImpl::UpdateStateOnUse(EntryModified modified_enum) {
|
||||
if (!doomed_)
|
||||
backend_->OnEntryUpdated(this);
|
||||
|
||||
last_used_ = Time::Now();
|
||||
if (modified_enum == ENTRY_WAS_MODIFIED)
|
||||
last_modified_ = last_used_;
|
||||
}
|
||||
|
||||
void MemEntryImpl::Doom() {
|
||||
if (!doomed_) {
|
||||
doomed_ = true;
|
||||
backend_->OnEntryDoomed(this);
|
||||
net_log_.AddEvent(net::NetLogEventType::ENTRY_DOOM);
|
||||
}
|
||||
if (!ref_count_)
|
||||
delete this;
|
||||
}
|
||||
|
||||
void MemEntryImpl::Close() {
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
--ref_count_;
|
||||
DCHECK_GE(ref_count_, 0);
|
||||
if (!ref_count_ && doomed_)
|
||||
delete this;
|
||||
}
|
||||
|
||||
std::string MemEntryImpl::GetKey() const {
|
||||
// A child entry doesn't have key so this method should not be called.
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
return key_;
|
||||
}
|
||||
|
||||
Time MemEntryImpl::GetLastUsed() const {
|
||||
return last_used_;
|
||||
}
|
||||
|
||||
Time MemEntryImpl::GetLastModified() const {
|
||||
return last_modified_;
|
||||
}
|
||||
|
||||
int32_t MemEntryImpl::GetDataSize(int index) const {
|
||||
if (index < 0 || index >= kNumStreams)
|
||||
return 0;
|
||||
return data_[index].size();
|
||||
}
|
||||
|
||||
int MemEntryImpl::ReadData(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
const CompletionCallback& callback) {
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.BeginEvent(
|
||||
net::NetLogEventType::ENTRY_READ_DATA,
|
||||
CreateNetLogReadWriteDataCallback(index, offset, buf_len, false));
|
||||
}
|
||||
|
||||
int result = InternalReadData(index, offset, buf, buf_len);
|
||||
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.EndEvent(net::NetLogEventType::ENTRY_READ_DATA,
|
||||
CreateNetLogReadWriteCompleteCallback(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int MemEntryImpl::WriteData(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
const CompletionCallback& callback, bool truncate) {
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.BeginEvent(
|
||||
net::NetLogEventType::ENTRY_WRITE_DATA,
|
||||
CreateNetLogReadWriteDataCallback(index, offset, buf_len, truncate));
|
||||
}
|
||||
|
||||
int result = InternalWriteData(index, offset, buf, buf_len, truncate);
|
||||
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.EndEvent(net::NetLogEventType::ENTRY_WRITE_DATA,
|
||||
CreateNetLogReadWriteCompleteCallback(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int MemEntryImpl::ReadSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) {
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.BeginEvent(net::NetLogEventType::SPARSE_READ,
|
||||
CreateNetLogSparseOperationCallback(offset, buf_len));
|
||||
}
|
||||
int result = InternalReadSparseData(offset, buf, buf_len);
|
||||
if (net_log_.IsCapturing())
|
||||
net_log_.EndEvent(net::NetLogEventType::SPARSE_READ);
|
||||
return result;
|
||||
}
|
||||
|
||||
int MemEntryImpl::WriteSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) {
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.BeginEvent(net::NetLogEventType::SPARSE_WRITE,
|
||||
CreateNetLogSparseOperationCallback(offset, buf_len));
|
||||
}
|
||||
int result = InternalWriteSparseData(offset, buf, buf_len);
|
||||
if (net_log_.IsCapturing())
|
||||
net_log_.EndEvent(net::NetLogEventType::SPARSE_WRITE);
|
||||
return result;
|
||||
}
|
||||
|
||||
int MemEntryImpl::GetAvailableRange(int64_t offset,
|
||||
int len,
|
||||
int64_t* start,
|
||||
const CompletionCallback& callback) {
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.BeginEvent(net::NetLogEventType::SPARSE_GET_RANGE,
|
||||
CreateNetLogSparseOperationCallback(offset, len));
|
||||
}
|
||||
int result = InternalGetAvailableRange(offset, len, start);
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.EndEvent(
|
||||
net::NetLogEventType::SPARSE_GET_RANGE,
|
||||
CreateNetLogGetAvailableRangeResultCallback(*start, result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MemEntryImpl::CouldBeSparse() const {
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
return (children_.get() != nullptr);
|
||||
}
|
||||
|
||||
int MemEntryImpl::ReadyForSparseIO(const CompletionCallback& callback) {
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
size_t MemEntryImpl::EstimateMemoryUsage() const {
|
||||
// Subtlety: the entries in children_ are not double counted, as the entry
|
||||
// pointers won't be followed by EstimateMemoryUsage.
|
||||
return base::trace_event::EstimateMemoryUsage(data_) +
|
||||
base::trace_event::EstimateMemoryUsage(key_) +
|
||||
base::trace_event::EstimateMemoryUsage(children_);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
MemEntryImpl::MemEntryImpl(MemBackendImpl* backend,
|
||||
const ::std::string& key,
|
||||
int child_id,
|
||||
MemEntryImpl* parent,
|
||||
net::NetLog* net_log)
|
||||
: key_(key),
|
||||
ref_count_(0),
|
||||
child_id_(child_id),
|
||||
child_first_pos_(0),
|
||||
parent_(parent),
|
||||
last_modified_(Time::Now()),
|
||||
last_used_(last_modified_),
|
||||
backend_(backend),
|
||||
doomed_(false) {
|
||||
backend_->OnEntryInserted(this);
|
||||
net_log_ = net::NetLogWithSource::Make(
|
||||
net_log, net::NetLogSourceType::MEMORY_CACHE_ENTRY);
|
||||
net_log_.BeginEvent(net::NetLogEventType::DISK_CACHE_MEM_ENTRY_IMPL,
|
||||
base::Bind(&NetLogEntryCreationCallback, this));
|
||||
}
|
||||
|
||||
MemEntryImpl::~MemEntryImpl() {
|
||||
backend_->ModifyStorageSize(-GetStorageSize());
|
||||
|
||||
if (type() == PARENT_ENTRY) {
|
||||
if (children_) {
|
||||
EntryMap children;
|
||||
children_->swap(children);
|
||||
|
||||
for (auto& it : children) {
|
||||
// Since |this| is stored in the map, it should be guarded against
|
||||
// double dooming, which will result in double destruction.
|
||||
if (it.second != this)
|
||||
it.second->Doom();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parent_->children_->erase(child_id_);
|
||||
}
|
||||
net_log_.EndEvent(net::NetLogEventType::DISK_CACHE_MEM_ENTRY_IMPL);
|
||||
}
|
||||
|
||||
int MemEntryImpl::InternalReadData(int index, int offset, IOBuffer* buf,
|
||||
int buf_len) {
|
||||
DCHECK(type() == PARENT_ENTRY || index == kSparseData);
|
||||
|
||||
if (index < 0 || index >= kNumStreams || buf_len < 0)
|
||||
return net::ERR_INVALID_ARGUMENT;
|
||||
|
||||
int entry_size = data_[index].size();
|
||||
if (offset >= entry_size || offset < 0 || !buf_len)
|
||||
return 0;
|
||||
|
||||
if (offset + buf_len > entry_size)
|
||||
buf_len = entry_size - offset;
|
||||
|
||||
UpdateStateOnUse(ENTRY_WAS_NOT_MODIFIED);
|
||||
std::copy(data_[index].begin() + offset,
|
||||
data_[index].begin() + offset + buf_len, buf->data());
|
||||
return buf_len;
|
||||
}
|
||||
|
||||
int MemEntryImpl::InternalWriteData(int index, int offset, IOBuffer* buf,
|
||||
int buf_len, bool truncate) {
|
||||
DCHECK(type() == PARENT_ENTRY || index == kSparseData);
|
||||
|
||||
if (index < 0 || index >= kNumStreams) {
|
||||
RecordWriteResult(WRITE_RESULT_INVALID_ARGUMENT);
|
||||
return net::ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (offset < 0 || buf_len < 0) {
|
||||
RecordWriteResult(WRITE_RESULT_INVALID_ARGUMENT);
|
||||
return net::ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
int max_file_size = backend_->MaxFileSize();
|
||||
|
||||
// offset of buf_len could be negative numbers.
|
||||
if (offset > max_file_size || buf_len > max_file_size ||
|
||||
offset + buf_len > max_file_size) {
|
||||
RecordWriteResult(WRITE_RESULT_OVER_MAX_ENTRY_SIZE);
|
||||
return net::ERR_FAILED;
|
||||
}
|
||||
|
||||
int old_data_size = data_[index].size();
|
||||
if (truncate || old_data_size < offset + buf_len) {
|
||||
int delta = offset + buf_len - old_data_size;
|
||||
backend_->ModifyStorageSize(delta);
|
||||
if (backend_->HasExceededStorageSize()) {
|
||||
backend_->ModifyStorageSize(-delta);
|
||||
RecordWriteResult(WRITE_RESULT_EXCEEDED_CACHE_STORAGE_SIZE);
|
||||
return net::ERR_INSUFFICIENT_RESOURCES;
|
||||
}
|
||||
|
||||
data_[index].resize(offset + buf_len);
|
||||
|
||||
// Zero fill any hole.
|
||||
if (old_data_size < offset) {
|
||||
std::fill(data_[index].begin() + old_data_size,
|
||||
data_[index].begin() + offset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStateOnUse(ENTRY_WAS_MODIFIED);
|
||||
RecordWriteResult(WRITE_RESULT_SUCCESS);
|
||||
|
||||
if (!buf_len)
|
||||
return 0;
|
||||
|
||||
std::copy(buf->data(), buf->data() + buf_len, data_[index].begin() + offset);
|
||||
return buf_len;
|
||||
}
|
||||
|
||||
int MemEntryImpl::InternalReadSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len) {
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
|
||||
if (!InitSparseInfo())
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
if (offset < 0 || buf_len < 0)
|
||||
return net::ERR_INVALID_ARGUMENT;
|
||||
|
||||
// We will keep using this buffer and adjust the offset in this buffer.
|
||||
scoped_refptr<net::DrainableIOBuffer> io_buf(
|
||||
new net::DrainableIOBuffer(buf, buf_len));
|
||||
|
||||
// Iterate until we have read enough.
|
||||
while (io_buf->BytesRemaining()) {
|
||||
MemEntryImpl* child = GetChild(offset + io_buf->BytesConsumed(), false);
|
||||
|
||||
// No child present for that offset.
|
||||
if (!child)
|
||||
break;
|
||||
|
||||
// We then need to prepare the child offset and len.
|
||||
int child_offset = ToChildOffset(offset + io_buf->BytesConsumed());
|
||||
|
||||
// If we are trying to read from a position that the child entry has no data
|
||||
// we should stop.
|
||||
if (child_offset < child->child_first_pos_)
|
||||
break;
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.BeginEvent(
|
||||
net::NetLogEventType::SPARSE_READ_CHILD_DATA,
|
||||
CreateNetLogSparseReadWriteCallback(child->net_log_.source(),
|
||||
io_buf->BytesRemaining()));
|
||||
}
|
||||
int ret = child->ReadData(kSparseData, child_offset, io_buf.get(),
|
||||
io_buf->BytesRemaining(), CompletionCallback());
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.EndEventWithNetErrorCode(
|
||||
net::NetLogEventType::SPARSE_READ_CHILD_DATA, ret);
|
||||
}
|
||||
|
||||
// If we encounter an error in one entry, return immediately.
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret == 0)
|
||||
break;
|
||||
|
||||
// Increment the counter by number of bytes read in the child entry.
|
||||
io_buf->DidConsume(ret);
|
||||
}
|
||||
|
||||
UpdateStateOnUse(ENTRY_WAS_NOT_MODIFIED);
|
||||
return io_buf->BytesConsumed();
|
||||
}
|
||||
|
||||
int MemEntryImpl::InternalWriteSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len) {
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
|
||||
if (!InitSparseInfo())
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
if (offset < 0 || buf_len < 0)
|
||||
return net::ERR_INVALID_ARGUMENT;
|
||||
|
||||
scoped_refptr<net::DrainableIOBuffer> io_buf(
|
||||
new net::DrainableIOBuffer(buf, buf_len));
|
||||
|
||||
// This loop walks through child entries continuously starting from |offset|
|
||||
// and writes blocks of data (of maximum size kMaxSparseEntrySize) into each
|
||||
// child entry until all |buf_len| bytes are written. The write operation can
|
||||
// start in the middle of an entry.
|
||||
while (io_buf->BytesRemaining()) {
|
||||
MemEntryImpl* child = GetChild(offset + io_buf->BytesConsumed(), true);
|
||||
int child_offset = ToChildOffset(offset + io_buf->BytesConsumed());
|
||||
|
||||
// Find the right amount to write, this evaluates the remaining bytes to
|
||||
// write and remaining capacity of this child entry.
|
||||
int write_len = std::min(static_cast<int>(io_buf->BytesRemaining()),
|
||||
kMaxSparseEntrySize - child_offset);
|
||||
|
||||
// Keep a record of the last byte position (exclusive) in the child.
|
||||
int data_size = child->GetDataSize(kSparseData);
|
||||
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.BeginEvent(net::NetLogEventType::SPARSE_WRITE_CHILD_DATA,
|
||||
CreateNetLogSparseReadWriteCallback(
|
||||
child->net_log_.source(), write_len));
|
||||
}
|
||||
|
||||
// Always writes to the child entry. This operation may overwrite data
|
||||
// previously written.
|
||||
// TODO(hclam): if there is data in the entry and this write is not
|
||||
// continuous we may want to discard this write.
|
||||
int ret = child->WriteData(kSparseData, child_offset, io_buf.get(),
|
||||
write_len, CompletionCallback(), true);
|
||||
if (net_log_.IsCapturing()) {
|
||||
net_log_.EndEventWithNetErrorCode(
|
||||
net::NetLogEventType::SPARSE_WRITE_CHILD_DATA, ret);
|
||||
}
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret == 0)
|
||||
break;
|
||||
|
||||
// Keep a record of the first byte position in the child if the write was
|
||||
// not aligned nor continuous. This is to enable witting to the middle
|
||||
// of an entry and still keep track of data off the aligned edge.
|
||||
if (data_size != child_offset)
|
||||
child->child_first_pos_ = child_offset;
|
||||
|
||||
// Adjust the offset in the IO buffer.
|
||||
io_buf->DidConsume(ret);
|
||||
}
|
||||
|
||||
UpdateStateOnUse(ENTRY_WAS_MODIFIED);
|
||||
return io_buf->BytesConsumed();
|
||||
}
|
||||
|
||||
int MemEntryImpl::InternalGetAvailableRange(int64_t offset,
|
||||
int len,
|
||||
int64_t* start) {
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
DCHECK(start);
|
||||
|
||||
if (!InitSparseInfo())
|
||||
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
|
||||
|
||||
if (offset < 0 || len < 0 || !start)
|
||||
return net::ERR_INVALID_ARGUMENT;
|
||||
|
||||
MemEntryImpl* current_child = nullptr;
|
||||
|
||||
// Find the first child and record the number of empty bytes.
|
||||
int empty = FindNextChild(offset, len, ¤t_child);
|
||||
if (current_child && empty < len) {
|
||||
*start = offset + empty;
|
||||
len -= empty;
|
||||
|
||||
// Counts the number of continuous bytes.
|
||||
int continuous = 0;
|
||||
|
||||
// This loop scan for continuous bytes.
|
||||
while (len && current_child) {
|
||||
// Number of bytes available in this child.
|
||||
int data_size = current_child->GetDataSize(kSparseData) -
|
||||
ToChildOffset(*start + continuous);
|
||||
if (data_size > len)
|
||||
data_size = len;
|
||||
|
||||
// We have found more continuous bytes so increment the count. Also
|
||||
// decrement the length we should scan.
|
||||
continuous += data_size;
|
||||
len -= data_size;
|
||||
|
||||
// If the next child is discontinuous, break the loop.
|
||||
if (FindNextChild(*start + continuous, len, ¤t_child))
|
||||
break;
|
||||
}
|
||||
return continuous;
|
||||
}
|
||||
*start = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool MemEntryImpl::InitSparseInfo() {
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
|
||||
if (!children_) {
|
||||
// If we already have some data in sparse stream but we are being
|
||||
// initialized as a sparse entry, we should fail.
|
||||
if (GetDataSize(kSparseData))
|
||||
return false;
|
||||
children_.reset(new EntryMap());
|
||||
|
||||
// The parent entry stores data for the first block, so save this object to
|
||||
// index 0.
|
||||
(*children_)[0] = this;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MemEntryImpl* MemEntryImpl::GetChild(int64_t offset, bool create) {
|
||||
DCHECK_EQ(PARENT_ENTRY, type());
|
||||
int index = ToChildIndex(offset);
|
||||
EntryMap::iterator i = children_->find(index);
|
||||
if (i != children_->end())
|
||||
return i->second;
|
||||
if (create)
|
||||
return new MemEntryImpl(backend_, index, this, net_log_.net_log());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int MemEntryImpl::FindNextChild(int64_t offset, int len, MemEntryImpl** child) {
|
||||
DCHECK(child);
|
||||
*child = nullptr;
|
||||
int scanned_len = 0;
|
||||
|
||||
// This loop tries to find the first existing child.
|
||||
while (scanned_len < len) {
|
||||
// This points to the current offset in the child.
|
||||
int current_child_offset = ToChildOffset(offset + scanned_len);
|
||||
MemEntryImpl* current_child = GetChild(offset + scanned_len, false);
|
||||
if (current_child) {
|
||||
int child_first_pos = current_child->child_first_pos_;
|
||||
|
||||
// This points to the first byte that we should be reading from, we need
|
||||
// to take care of the filled region and the current offset in the child.
|
||||
int first_pos = std::max(current_child_offset, child_first_pos);
|
||||
|
||||
// If the first byte position we should read from doesn't exceed the
|
||||
// filled region, we have found the first child.
|
||||
if (first_pos < current_child->GetDataSize(kSparseData)) {
|
||||
*child = current_child;
|
||||
|
||||
// We need to advance the scanned length.
|
||||
scanned_len += first_pos - current_child_offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
scanned_len += kMaxSparseEntrySize - current_child_offset;
|
||||
}
|
||||
return scanned_len;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
196
net/disk_cache/memory/mem_entry_impl.h
Normal file
196
net/disk_cache/memory/mem_entry_impl.h
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_MEMORY_MEM_ENTRY_IMPL_H_
|
||||
#define NET_DISK_CACHE_MEMORY_MEM_ENTRY_IMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/linked_list.h"
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/trace_event/memory_usage_estimator.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/log/net_log_with_source.h"
|
||||
|
||||
namespace net {
|
||||
class NetLog;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class MemBackendImpl;
|
||||
|
||||
// This class implements the Entry interface for the memory-only cache. An
|
||||
// object of this class represents a single entry on the cache. We use two types
|
||||
// of entries, parent and child to support sparse caching.
|
||||
//
|
||||
// A parent entry is non-sparse until a sparse method is invoked (i.e.
|
||||
// ReadSparseData, WriteSparseData, GetAvailableRange) when sparse information
|
||||
// is initialized. It then manages a list of child entries and delegates the
|
||||
// sparse API calls to the child entries. It creates and deletes child entries
|
||||
// and updates the list when needed.
|
||||
//
|
||||
// A child entry is used to carry partial cache content, non-sparse methods like
|
||||
// ReadData and WriteData cannot be applied to them. The lifetime of a child
|
||||
// entry is managed by the parent entry that created it except that the entry
|
||||
// can be evicted independently. A child entry does not have a key and it is not
|
||||
// registered in the backend's entry map.
|
||||
//
|
||||
// A sparse child entry has a fixed maximum size and can be partially
|
||||
// filled. There can only be one continous filled region in a sparse entry, as
|
||||
// illustrated by the following example:
|
||||
// | xxx ooooo |
|
||||
// x = unfilled region
|
||||
// o = filled region
|
||||
// It is guaranteed that there is at most one unfilled region and one filled
|
||||
// region, and the unfilled region (if there is one) is always before the filled
|
||||
// region. The book keeping for filled region in a sparse entry is done by using
|
||||
// the variable |child_first_pos_|.
|
||||
|
||||
class NET_EXPORT_PRIVATE MemEntryImpl final
|
||||
: public Entry,
|
||||
public base::LinkNode<MemEntryImpl> {
|
||||
public:
|
||||
enum EntryType {
|
||||
PARENT_ENTRY,
|
||||
CHILD_ENTRY,
|
||||
};
|
||||
|
||||
// Provided to better document calls to |UpdateStateOnUse()|.
|
||||
enum EntryModified {
|
||||
ENTRY_WAS_NOT_MODIFIED,
|
||||
ENTRY_WAS_MODIFIED,
|
||||
};
|
||||
|
||||
// Constructor for parent entries.
|
||||
MemEntryImpl(MemBackendImpl* backend,
|
||||
const std::string& key,
|
||||
net::NetLog* net_log);
|
||||
|
||||
// Constructor for child entries.
|
||||
MemEntryImpl(MemBackendImpl* backend,
|
||||
int child_id,
|
||||
MemEntryImpl* parent,
|
||||
net::NetLog* net_log);
|
||||
|
||||
void Open();
|
||||
bool InUse() const;
|
||||
|
||||
EntryType type() const { return parent_ ? CHILD_ENTRY : PARENT_ENTRY; }
|
||||
const std::string& key() const { return key_; }
|
||||
const MemEntryImpl* parent() const { return parent_; }
|
||||
int child_id() const { return child_id_; }
|
||||
base::Time last_used() const { return last_used_; }
|
||||
|
||||
// The in-memory size of this entry to use for the purposes of eviction.
|
||||
int GetStorageSize() const;
|
||||
|
||||
// Update an entry's position in the backend LRU list and set |last_used_|. If
|
||||
// the entry was modified, also update |last_modified_|.
|
||||
void UpdateStateOnUse(EntryModified modified_enum);
|
||||
|
||||
// From disk_cache::Entry:
|
||||
void Doom() override;
|
||||
void Close() override;
|
||||
std::string GetKey() const override;
|
||||
base::Time GetLastUsed() const override;
|
||||
base::Time GetLastModified() const override;
|
||||
int32_t GetDataSize(int index) const override;
|
||||
int ReadData(int index,
|
||||
int offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int WriteData(int index,
|
||||
int offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback,
|
||||
bool truncate) override;
|
||||
int ReadSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int WriteSparseData(int64_t offset,
|
||||
IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int GetAvailableRange(int64_t offset,
|
||||
int len,
|
||||
int64_t* start,
|
||||
const CompletionCallback& callback) override;
|
||||
bool CouldBeSparse() const override;
|
||||
void CancelSparseIO() override {}
|
||||
int ReadyForSparseIO(const CompletionCallback& callback) override;
|
||||
size_t EstimateMemoryUsage() const;
|
||||
|
||||
private:
|
||||
MemEntryImpl(MemBackendImpl* backend,
|
||||
const std::string& key,
|
||||
int child_id,
|
||||
MemEntryImpl* parent,
|
||||
net::NetLog* net_log);
|
||||
|
||||
using EntryMap = std::unordered_map<int, MemEntryImpl*>;
|
||||
|
||||
static const int kNumStreams = 3;
|
||||
|
||||
~MemEntryImpl() override;
|
||||
|
||||
// Do all the work for corresponding public functions. Implemented as
|
||||
// separate functions to make logging of results simpler.
|
||||
int InternalReadData(int index, int offset, IOBuffer* buf, int buf_len);
|
||||
int InternalWriteData(int index, int offset, IOBuffer* buf, int buf_len,
|
||||
bool truncate);
|
||||
int InternalReadSparseData(int64_t offset, IOBuffer* buf, int buf_len);
|
||||
int InternalWriteSparseData(int64_t offset, IOBuffer* buf, int buf_len);
|
||||
int InternalGetAvailableRange(int64_t offset, int len, int64_t* start);
|
||||
|
||||
// Initializes the children map and sparse info. This method is only called
|
||||
// on a parent entry.
|
||||
bool InitSparseInfo();
|
||||
|
||||
// Returns an entry responsible for |offset|. The returned entry can be a
|
||||
// child entry or this entry itself if |offset| points to the first range.
|
||||
// If such entry does not exist and |create| is true, a new child entry is
|
||||
// created.
|
||||
MemEntryImpl* GetChild(int64_t offset, bool create);
|
||||
|
||||
// Finds the first child located within the range [|offset|, |offset + len|).
|
||||
// Returns the number of bytes ahead of |offset| to reach the first available
|
||||
// bytes in the entry. The first child found is output to |child|.
|
||||
int FindNextChild(int64_t offset, int len, MemEntryImpl** child);
|
||||
|
||||
std::string key_;
|
||||
std::vector<char> data_[kNumStreams]; // User data.
|
||||
int ref_count_;
|
||||
|
||||
int child_id_; // The ID of a child entry.
|
||||
int child_first_pos_; // The position of the first byte in a child
|
||||
// entry.
|
||||
// Pointer to the parent entry, or nullptr if this entry is a parent entry.
|
||||
MemEntryImpl* parent_;
|
||||
std::unique_ptr<EntryMap> children_;
|
||||
|
||||
base::Time last_modified_;
|
||||
base::Time last_used_;
|
||||
MemBackendImpl* backend_; // Back pointer to the cache.
|
||||
bool doomed_; // True if this entry was removed from the cache.
|
||||
|
||||
net::NetLogWithSource net_log_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MemEntryImpl);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_MEMORY_MEM_ENTRY_IMPL_H_
|
||||
136
net/disk_cache/net_log_parameters.cc
Normal file
136
net/disk_cache/net_log_parameters.cc
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/net_log_parameters.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/log/net_log_capture_mode.h"
|
||||
#include "net/log/net_log_source.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<base::Value> NetLogEntryCreationCallback(
|
||||
const disk_cache::Entry* entry,
|
||||
bool created,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
dict->SetString("key", entry->GetKey());
|
||||
dict->SetBoolean("created", created);
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
std::unique_ptr<base::Value> NetLogReadWriteDataCallback(
|
||||
int index,
|
||||
int offset,
|
||||
int buf_len,
|
||||
bool truncate,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
dict->SetInteger("index", index);
|
||||
dict->SetInteger("offset", offset);
|
||||
dict->SetInteger("buf_len", buf_len);
|
||||
if (truncate)
|
||||
dict->SetBoolean("truncate", truncate);
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
std::unique_ptr<base::Value> NetLogReadWriteCompleteCallback(
|
||||
int bytes_copied,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
DCHECK_NE(bytes_copied, net::ERR_IO_PENDING);
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
if (bytes_copied < 0) {
|
||||
dict->SetInteger("net_error", bytes_copied);
|
||||
} else {
|
||||
dict->SetInteger("bytes_copied", bytes_copied);
|
||||
}
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
std::unique_ptr<base::Value> NetLogSparseOperationCallback(
|
||||
int64_t offset,
|
||||
int buf_len,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
// Values can only be created with at most 32-bit integers. Using a string
|
||||
// instead circumvents that restriction.
|
||||
dict->SetString("offset", base::Int64ToString(offset));
|
||||
dict->SetInteger("buf_len", buf_len);
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
std::unique_ptr<base::Value> NetLogSparseReadWriteCallback(
|
||||
const net::NetLogSource& source,
|
||||
int child_len,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
source.AddToEventParameters(dict.get());
|
||||
dict->SetInteger("child_len", child_len);
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
std::unique_ptr<base::Value> NetLogGetAvailableRangeResultCallback(
|
||||
int64_t start,
|
||||
int result,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
if (result > 0) {
|
||||
dict->SetInteger("length", result);
|
||||
dict->SetString("start", base::Int64ToString(start));
|
||||
} else {
|
||||
dict->SetInteger("net_error", result);
|
||||
}
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogEntryCreationCallback(
|
||||
const Entry* entry,
|
||||
bool created) {
|
||||
DCHECK(entry);
|
||||
return base::Bind(&NetLogEntryCreationCallback, entry, created);
|
||||
}
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogReadWriteDataCallback(int index,
|
||||
int offset,
|
||||
int buf_len,
|
||||
bool truncate) {
|
||||
return base::Bind(&NetLogReadWriteDataCallback,
|
||||
index, offset, buf_len, truncate);
|
||||
}
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogReadWriteCompleteCallback(
|
||||
int bytes_copied) {
|
||||
return base::Bind(&NetLogReadWriteCompleteCallback, bytes_copied);
|
||||
}
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogSparseOperationCallback(
|
||||
int64_t offset,
|
||||
int buf_len) {
|
||||
return base::Bind(&NetLogSparseOperationCallback, offset, buf_len);
|
||||
}
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogSparseReadWriteCallback(
|
||||
const net::NetLogSource& source,
|
||||
int child_len) {
|
||||
return base::Bind(&NetLogSparseReadWriteCallback, source, child_len);
|
||||
}
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogGetAvailableRangeResultCallback(
|
||||
int64_t start,
|
||||
int result) {
|
||||
return base::Bind(&NetLogGetAvailableRangeResultCallback, start, result);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
67
net/disk_cache/net_log_parameters.h
Normal file
67
net/disk_cache/net_log_parameters.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_NET_LOG_PARAMETERS_H_
|
||||
#define NET_DISK_CACHE_NET_LOG_PARAMETERS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "net/log/net_log_parameters_callback.h"
|
||||
|
||||
namespace net {
|
||||
struct NetLogSource;
|
||||
}
|
||||
|
||||
// This file contains a set of functions to create NetLogParametersCallbacks
|
||||
// shared by EntryImpls and MemEntryImpls.
|
||||
namespace disk_cache {
|
||||
|
||||
class Entry;
|
||||
|
||||
// Creates a NetLog callback that returns parameters for the creation of an
|
||||
// Entry. Contains the Entry's key and whether it was created or opened.
|
||||
// |entry| can't be NULL, must support GetKey(), and must outlive the returned
|
||||
// callback.
|
||||
net::NetLogParametersCallback CreateNetLogEntryCreationCallback(
|
||||
const Entry* entry,
|
||||
bool created);
|
||||
|
||||
// Creates a NetLog callback that returns parameters for start of a non-sparse
|
||||
// read or write of an Entry. For reads, |truncate| must be false.
|
||||
net::NetLogParametersCallback CreateNetLogReadWriteDataCallback(int index,
|
||||
int offset,
|
||||
int buf_len,
|
||||
bool truncate);
|
||||
|
||||
// Creates a NetLog callback that returns parameters for when a non-sparse
|
||||
// read or write completes. For reads, |truncate| must be false.
|
||||
// |bytes_copied| is either the number of bytes copied or a network error
|
||||
// code. |bytes_copied| must not be ERR_IO_PENDING, as it's not a valid
|
||||
// result for an operation.
|
||||
net::NetLogParametersCallback CreateNetLogReadWriteCompleteCallback(
|
||||
int bytes_copied);
|
||||
|
||||
// Creates a NetLog callback that returns parameters for when a sparse
|
||||
// operation is started.
|
||||
net::NetLogParametersCallback CreateNetLogSparseOperationCallback(
|
||||
int64_t offset,
|
||||
int buf_len);
|
||||
|
||||
// Creates a NetLog callback that returns parameters for when a read or write
|
||||
// for a sparse entry's child is started.
|
||||
net::NetLogParametersCallback CreateNetLogSparseReadWriteCallback(
|
||||
const net::NetLogSource& source,
|
||||
int child_len);
|
||||
|
||||
// Creates a NetLog callback that returns parameters for when a call to
|
||||
// GetAvailableRange returns.
|
||||
net::NetLogParametersCallback CreateNetLogGetAvailableRangeResultCallback(
|
||||
int64_t start,
|
||||
int result);
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_NET_LOG_PARAMETERS_H_
|
||||
4
net/disk_cache/simple/OWNERS
Normal file
4
net/disk_cache/simple/OWNERS
Normal file
@@ -0,0 +1,4 @@
|
||||
gavinp@chromium.org
|
||||
pasko@chromium.org
|
||||
|
||||
# COMPONENT: Internals>Network>Cache>Simple
|
||||
821
net/disk_cache/simple/simple_backend_impl.cc
Normal file
821
net/disk_cache/simple/simple_backend_impl.cc
Normal file
@@ -0,0 +1,821 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_backend_impl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/callback.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/location.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/metrics/field_trial.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/metrics/sparse_histogram.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/sys_info.h"
|
||||
#include "base/task_runner_util.h"
|
||||
#include "base/task_scheduler/post_task.h"
|
||||
#include "base/task_scheduler/task_scheduler.h"
|
||||
#include "base/threading/thread_task_runner_handle.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/trace_event/memory_usage_estimator.h"
|
||||
#include "base/trace_event/process_memory_dump.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/backend_cleanup_tracker.h"
|
||||
#include "net/disk_cache/cache_util.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
#include "net/disk_cache/simple/simple_entry_impl.h"
|
||||
#include "net/disk_cache/simple/simple_experiment.h"
|
||||
#include "net/disk_cache/simple/simple_file_tracker.h"
|
||||
#include "net/disk_cache/simple/simple_histogram_macros.h"
|
||||
#include "net/disk_cache/simple/simple_index.h"
|
||||
#include "net/disk_cache/simple/simple_index_file.h"
|
||||
#include "net/disk_cache/simple/simple_synchronous_entry.h"
|
||||
#include "net/disk_cache/simple/simple_util.h"
|
||||
#include "net/disk_cache/simple/simple_version_upgrade.h"
|
||||
|
||||
using base::Callback;
|
||||
using base::Closure;
|
||||
using base::FilePath;
|
||||
using base::Time;
|
||||
using base::DirectoryExists;
|
||||
using base::CreateDirectory;
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace {
|
||||
|
||||
// Maximum fraction of the cache that one entry can consume.
|
||||
const int kMaxFileRatio = 8;
|
||||
|
||||
scoped_refptr<base::SequencedTaskRunner> FallbackToInternalIfNull(
|
||||
const scoped_refptr<base::SequencedTaskRunner>& cache_runner) {
|
||||
if (cache_runner)
|
||||
return cache_runner;
|
||||
return base::CreateSequencedTaskRunnerWithTraits(
|
||||
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
|
||||
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
|
||||
}
|
||||
|
||||
bool g_fd_limit_histogram_has_been_populated = false;
|
||||
|
||||
void MaybeHistogramFdLimit(net::CacheType cache_type) {
|
||||
if (g_fd_limit_histogram_has_been_populated)
|
||||
return;
|
||||
|
||||
// Used in histograms; add new entries at end.
|
||||
enum FdLimitStatus {
|
||||
FD_LIMIT_STATUS_UNSUPPORTED = 0,
|
||||
FD_LIMIT_STATUS_FAILED = 1,
|
||||
FD_LIMIT_STATUS_SUCCEEDED = 2,
|
||||
FD_LIMIT_STATUS_MAX = 3
|
||||
};
|
||||
FdLimitStatus fd_limit_status = FD_LIMIT_STATUS_UNSUPPORTED;
|
||||
int soft_fd_limit = 0;
|
||||
int hard_fd_limit = 0;
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
struct rlimit nofile;
|
||||
if (!getrlimit(RLIMIT_NOFILE, &nofile)) {
|
||||
soft_fd_limit = nofile.rlim_cur;
|
||||
hard_fd_limit = nofile.rlim_max;
|
||||
fd_limit_status = FD_LIMIT_STATUS_SUCCEEDED;
|
||||
} else {
|
||||
fd_limit_status = FD_LIMIT_STATUS_FAILED;
|
||||
}
|
||||
#endif
|
||||
|
||||
SIMPLE_CACHE_UMA(ENUMERATION,
|
||||
"FileDescriptorLimitStatus", cache_type,
|
||||
fd_limit_status, FD_LIMIT_STATUS_MAX);
|
||||
if (fd_limit_status == FD_LIMIT_STATUS_SUCCEEDED) {
|
||||
SIMPLE_CACHE_UMA(SPARSE_SLOWLY,
|
||||
"FileDescriptorLimitSoft", cache_type, soft_fd_limit);
|
||||
SIMPLE_CACHE_UMA(SPARSE_SLOWLY,
|
||||
"FileDescriptorLimitHard", cache_type, hard_fd_limit);
|
||||
}
|
||||
|
||||
g_fd_limit_histogram_has_been_populated = true;
|
||||
}
|
||||
|
||||
// Global context of all the files we have open --- this permits some to be
|
||||
// closed on demand if too many FDs are being used, to avoid running out.
|
||||
base::LazyInstance<SimpleFileTracker>::Leaky g_simple_file_tracker =
|
||||
LAZY_INSTANCE_INITIALIZER;
|
||||
|
||||
// Detects if the files in the cache directory match the current disk cache
|
||||
// backend type and version. If the directory contains no cache, occupies it
|
||||
// with the fresh structure.
|
||||
bool FileStructureConsistent(const base::FilePath& path,
|
||||
const SimpleExperiment& experiment) {
|
||||
if (!base::PathExists(path) && !base::CreateDirectory(path)) {
|
||||
LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName();
|
||||
return false;
|
||||
}
|
||||
return disk_cache::UpgradeSimpleCacheOnDisk(path, experiment);
|
||||
}
|
||||
|
||||
// A context used by a BarrierCompletionCallback to track state.
|
||||
struct BarrierContext {
|
||||
explicit BarrierContext(int expected)
|
||||
: expected(expected), count(0), had_error(false) {}
|
||||
|
||||
const int expected;
|
||||
int count;
|
||||
bool had_error;
|
||||
};
|
||||
|
||||
void BarrierCompletionCallbackImpl(
|
||||
BarrierContext* context,
|
||||
const net::CompletionCallback& final_callback,
|
||||
int result) {
|
||||
DCHECK_GT(context->expected, context->count);
|
||||
if (context->had_error)
|
||||
return;
|
||||
if (result != net::OK) {
|
||||
context->had_error = true;
|
||||
final_callback.Run(result);
|
||||
return;
|
||||
}
|
||||
++context->count;
|
||||
if (context->count == context->expected)
|
||||
final_callback.Run(net::OK);
|
||||
}
|
||||
|
||||
// A barrier completion callback is a net::CompletionCallback that waits for
|
||||
// |count| successful results before invoking |final_callback|. In the case of
|
||||
// an error, the first error is passed to |final_callback| and all others
|
||||
// are ignored.
|
||||
net::CompletionCallback MakeBarrierCompletionCallback(
|
||||
int count,
|
||||
const net::CompletionCallback& final_callback) {
|
||||
BarrierContext* context = new BarrierContext(count);
|
||||
return base::Bind(&BarrierCompletionCallbackImpl,
|
||||
base::Owned(context), final_callback);
|
||||
}
|
||||
|
||||
// A short bindable thunk that ensures a completion callback is always called
|
||||
// after running an operation asynchronously.
|
||||
void RunOperationAndCallback(
|
||||
const Callback<int(const net::CompletionCallback&)>& operation,
|
||||
const net::CompletionCallback& operation_callback) {
|
||||
const int operation_result = operation.Run(operation_callback);
|
||||
if (operation_result != net::ERR_IO_PENDING)
|
||||
operation_callback.Run(operation_result);
|
||||
}
|
||||
|
||||
void RecordIndexLoad(net::CacheType cache_type,
|
||||
base::TimeTicks constructed_since,
|
||||
int result) {
|
||||
const base::TimeDelta creation_to_index = base::TimeTicks::Now() -
|
||||
constructed_since;
|
||||
if (result == net::OK) {
|
||||
SIMPLE_CACHE_UMA(TIMES, "CreationToIndex", cache_type, creation_to_index);
|
||||
} else {
|
||||
SIMPLE_CACHE_UMA(TIMES,
|
||||
"CreationToIndexFail", cache_type, creation_to_index);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Static function which is called by base::trace_event::EstimateMemoryUsage()
|
||||
// to estimate the memory of SimpleEntryImpl* type.
|
||||
// This needs to be in disk_cache namespace.
|
||||
size_t EstimateMemoryUsage(const SimpleEntryImpl* const& entry_impl) {
|
||||
return sizeof(SimpleEntryImpl) + entry_impl->EstimateMemoryUsage();
|
||||
}
|
||||
|
||||
class SimpleBackendImpl::ActiveEntryProxy
|
||||
: public SimpleEntryImpl::ActiveEntryProxy {
|
||||
public:
|
||||
~ActiveEntryProxy() override {
|
||||
if (backend_) {
|
||||
DCHECK_EQ(1U, backend_->active_entries_.count(entry_hash_));
|
||||
backend_->active_entries_.erase(entry_hash_);
|
||||
}
|
||||
}
|
||||
|
||||
static std::unique_ptr<SimpleEntryImpl::ActiveEntryProxy> Create(
|
||||
int64_t entry_hash,
|
||||
SimpleBackendImpl* backend) {
|
||||
std::unique_ptr<SimpleEntryImpl::ActiveEntryProxy> proxy(
|
||||
new ActiveEntryProxy(entry_hash, backend));
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private:
|
||||
ActiveEntryProxy(uint64_t entry_hash, SimpleBackendImpl* backend)
|
||||
: entry_hash_(entry_hash), backend_(backend->AsWeakPtr()) {}
|
||||
|
||||
uint64_t entry_hash_;
|
||||
base::WeakPtr<SimpleBackendImpl> backend_;
|
||||
};
|
||||
|
||||
SimpleBackendImpl::SimpleBackendImpl(
|
||||
const FilePath& path,
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker,
|
||||
SimpleFileTracker* file_tracker,
|
||||
int max_bytes,
|
||||
net::CacheType cache_type,
|
||||
const scoped_refptr<base::SequencedTaskRunner>& cache_runner,
|
||||
net::NetLog* net_log)
|
||||
: cleanup_tracker_(std::move(cleanup_tracker)),
|
||||
file_tracker_(file_tracker ? file_tracker
|
||||
: g_simple_file_tracker.Pointer()),
|
||||
path_(path),
|
||||
cache_type_(cache_type),
|
||||
cache_runner_(FallbackToInternalIfNull(cache_runner)),
|
||||
orig_max_size_(max_bytes),
|
||||
entry_operations_mode_(cache_type == net::DISK_CACHE
|
||||
? SimpleEntryImpl::OPTIMISTIC_OPERATIONS
|
||||
: SimpleEntryImpl::NON_OPTIMISTIC_OPERATIONS),
|
||||
net_log_(net_log) {
|
||||
// Treat negative passed-in sizes same as SetMaxSize would here and in other
|
||||
// backends, as default (if first call).
|
||||
if (orig_max_size_ < 0)
|
||||
orig_max_size_ = 0;
|
||||
MaybeHistogramFdLimit(cache_type_);
|
||||
}
|
||||
|
||||
SimpleBackendImpl::~SimpleBackendImpl() {
|
||||
index_->WriteToDisk(SimpleIndex::INDEX_WRITE_REASON_SHUTDOWN);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::Init(const CompletionCallback& completion_callback) {
|
||||
worker_pool_ = base::TaskScheduler::GetInstance()->CreateTaskRunnerWithTraits(
|
||||
{base::MayBlock(), base::WithBaseSyncPrimitives(),
|
||||
base::TaskPriority::USER_BLOCKING,
|
||||
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
|
||||
|
||||
index_ = std::make_unique<SimpleIndex>(
|
||||
base::ThreadTaskRunnerHandle::Get(), cleanup_tracker_.get(), this,
|
||||
cache_type_,
|
||||
std::make_unique<SimpleIndexFile>(cache_runner_, worker_pool_.get(),
|
||||
cache_type_, path_));
|
||||
index_->ExecuteWhenReady(
|
||||
base::Bind(&RecordIndexLoad, cache_type_, base::TimeTicks::Now()));
|
||||
|
||||
PostTaskAndReplyWithResult(
|
||||
cache_runner_.get(), FROM_HERE,
|
||||
base::Bind(&SimpleBackendImpl::InitCacheStructureOnDisk, path_,
|
||||
orig_max_size_, GetSimpleExperiment(cache_type_)),
|
||||
base::Bind(&SimpleBackendImpl::InitializeIndex, AsWeakPtr(),
|
||||
completion_callback));
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
bool SimpleBackendImpl::SetMaxSize(int max_bytes) {
|
||||
if (max_bytes < 0)
|
||||
return false;
|
||||
orig_max_size_ = max_bytes;
|
||||
index_->SetMaxSize(max_bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::GetMaxFileSize() const {
|
||||
return static_cast<int>(index_->max_size() / kMaxFileRatio);
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::OnDoomStart(uint64_t entry_hash) {
|
||||
DCHECK_EQ(0u, entries_pending_doom_.count(entry_hash));
|
||||
entries_pending_doom_.insert(
|
||||
std::make_pair(entry_hash, std::vector<Closure>()));
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::OnDoomComplete(uint64_t entry_hash) {
|
||||
DCHECK_EQ(1u, entries_pending_doom_.count(entry_hash));
|
||||
std::unordered_map<uint64_t, std::vector<Closure>>::iterator it =
|
||||
entries_pending_doom_.find(entry_hash);
|
||||
std::vector<Closure> to_run_closures;
|
||||
to_run_closures.swap(it->second);
|
||||
entries_pending_doom_.erase(it);
|
||||
|
||||
for (auto& closure : to_run_closures)
|
||||
closure.Run();
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::DoomEntries(std::vector<uint64_t>* entry_hashes,
|
||||
const net::CompletionCallback& callback) {
|
||||
std::unique_ptr<std::vector<uint64_t>> mass_doom_entry_hashes(
|
||||
new std::vector<uint64_t>());
|
||||
mass_doom_entry_hashes->swap(*entry_hashes);
|
||||
|
||||
std::vector<uint64_t> to_doom_individually_hashes;
|
||||
|
||||
// For each of the entry hashes, there are two cases:
|
||||
// 1. There are corresponding entries in active set, pending doom, or both
|
||||
// sets, and so the hash should be doomed individually to avoid flakes.
|
||||
// 2. The hash is not in active use at all, so we can call
|
||||
// SimpleSynchronousEntry::DoomEntrySet and delete the files en masse.
|
||||
for (int i = mass_doom_entry_hashes->size() - 1; i >= 0; --i) {
|
||||
const uint64_t entry_hash = (*mass_doom_entry_hashes)[i];
|
||||
if (!active_entries_.count(entry_hash) &&
|
||||
!entries_pending_doom_.count(entry_hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
to_doom_individually_hashes.push_back(entry_hash);
|
||||
|
||||
(*mass_doom_entry_hashes)[i] = mass_doom_entry_hashes->back();
|
||||
mass_doom_entry_hashes->resize(mass_doom_entry_hashes->size() - 1);
|
||||
}
|
||||
|
||||
net::CompletionCallback barrier_callback =
|
||||
MakeBarrierCompletionCallback(to_doom_individually_hashes.size() + 1,
|
||||
callback);
|
||||
for (std::vector<uint64_t>::const_iterator
|
||||
it = to_doom_individually_hashes.begin(),
|
||||
end = to_doom_individually_hashes.end();
|
||||
it != end; ++it) {
|
||||
const int doom_result = DoomEntryFromHash(*it, barrier_callback);
|
||||
DCHECK_EQ(net::ERR_IO_PENDING, doom_result);
|
||||
index_->Remove(*it);
|
||||
}
|
||||
|
||||
for (std::vector<uint64_t>::const_iterator
|
||||
it = mass_doom_entry_hashes->begin(),
|
||||
end = mass_doom_entry_hashes->end();
|
||||
it != end; ++it) {
|
||||
index_->Remove(*it);
|
||||
OnDoomStart(*it);
|
||||
}
|
||||
|
||||
// Taking this pointer here avoids undefined behaviour from calling
|
||||
// base::Passed before mass_doom_entry_hashes.get().
|
||||
std::vector<uint64_t>* mass_doom_entry_hashes_ptr =
|
||||
mass_doom_entry_hashes.get();
|
||||
PostTaskAndReplyWithResult(worker_pool_.get(),
|
||||
FROM_HERE,
|
||||
base::Bind(&SimpleSynchronousEntry::DoomEntrySet,
|
||||
mass_doom_entry_hashes_ptr,
|
||||
path_),
|
||||
base::Bind(&SimpleBackendImpl::DoomEntriesComplete,
|
||||
AsWeakPtr(),
|
||||
base::Passed(&mass_doom_entry_hashes),
|
||||
barrier_callback));
|
||||
}
|
||||
|
||||
net::CacheType SimpleBackendImpl::GetCacheType() const {
|
||||
return net::DISK_CACHE;
|
||||
}
|
||||
|
||||
int32_t SimpleBackendImpl::GetEntryCount() const {
|
||||
// TODO(pasko): Use directory file count when index is not ready.
|
||||
return index_->GetEntryCount();
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::OpenEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) {
|
||||
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
||||
|
||||
std::vector<Closure>* post_doom = nullptr;
|
||||
scoped_refptr<SimpleEntryImpl> simple_entry =
|
||||
CreateOrFindActiveOrDoomedEntry(entry_hash, key, &post_doom);
|
||||
if (!simple_entry) {
|
||||
Callback<int(const net::CompletionCallback&)> operation =
|
||||
base::Bind(&SimpleBackendImpl::OpenEntry,
|
||||
base::Unretained(this), key, entry);
|
||||
post_doom->push_back(
|
||||
base::Bind(&RunOperationAndCallback, operation, callback));
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
return simple_entry->OpenEntry(entry, callback);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::CreateEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) {
|
||||
DCHECK_LT(0u, key.size());
|
||||
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
||||
|
||||
std::vector<Closure>* post_doom = nullptr;
|
||||
scoped_refptr<SimpleEntryImpl> simple_entry =
|
||||
CreateOrFindActiveOrDoomedEntry(entry_hash, key, &post_doom);
|
||||
|
||||
if (!simple_entry) {
|
||||
// We would like to optimistically have create go ahead, for benefit of
|
||||
// HTTP cache use. This can only be sanely done if we are the only op
|
||||
// serialized after doom's completion.
|
||||
if (post_doom->empty() &&
|
||||
entry_operations_mode_ == SimpleEntryImpl::OPTIMISTIC_OPERATIONS) {
|
||||
simple_entry = new SimpleEntryImpl(
|
||||
cache_type_, path_, cleanup_tracker_.get(), entry_hash,
|
||||
entry_operations_mode_, this, file_tracker_, net_log_);
|
||||
simple_entry->SetKey(key);
|
||||
simple_entry->SetActiveEntryProxy(
|
||||
ActiveEntryProxy::Create(entry_hash, this));
|
||||
simple_entry->SetCreatePendingDoom();
|
||||
std::pair<EntryMap::iterator, bool> insert_result =
|
||||
active_entries_.insert(
|
||||
EntryMap::value_type(entry_hash, simple_entry.get()));
|
||||
post_doom->push_back(base::Bind(
|
||||
&SimpleEntryImpl::NotifyDoomBeforeCreateComplete, simple_entry));
|
||||
DCHECK(insert_result.second);
|
||||
} else {
|
||||
Callback<int(const net::CompletionCallback&)> operation = base::Bind(
|
||||
&SimpleBackendImpl::CreateEntry, base::Unretained(this), key, entry);
|
||||
post_doom->push_back(
|
||||
base::Bind(&RunOperationAndCallback, operation, callback));
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
return simple_entry->CreateEntry(entry, callback);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::DoomEntry(const std::string& key,
|
||||
const net::CompletionCallback& callback) {
|
||||
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
||||
|
||||
std::vector<Closure>* post_doom = nullptr;
|
||||
scoped_refptr<SimpleEntryImpl> simple_entry =
|
||||
CreateOrFindActiveOrDoomedEntry(entry_hash, key, &post_doom);
|
||||
if (!simple_entry) {
|
||||
// At first glance, it appears exceedingly silly to queue up a doom
|
||||
// when we get here because the files corresponding to our key are being
|
||||
// deleted... but it's possible that one of the things in post_doom is a
|
||||
// create for our key, in which case we still have work to do.
|
||||
Callback<int(const net::CompletionCallback&)> operation =
|
||||
base::Bind(&SimpleBackendImpl::DoomEntry, base::Unretained(this), key);
|
||||
post_doom->push_back(
|
||||
base::Bind(&RunOperationAndCallback, operation, callback));
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
return simple_entry->DoomEntry(callback);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::DoomAllEntries(const CompletionCallback& callback) {
|
||||
return DoomEntriesBetween(Time(), Time(), callback);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::DoomEntriesBetween(
|
||||
const Time initial_time,
|
||||
const Time end_time,
|
||||
const CompletionCallback& callback) {
|
||||
return index_->ExecuteWhenReady(
|
||||
base::Bind(&SimpleBackendImpl::IndexReadyForDoom, AsWeakPtr(),
|
||||
initial_time, end_time, callback));
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::DoomEntriesSince(
|
||||
const Time initial_time,
|
||||
const CompletionCallback& callback) {
|
||||
return DoomEntriesBetween(initial_time, Time(), callback);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::CalculateSizeOfAllEntries(
|
||||
const CompletionCallback& callback) {
|
||||
return index_->ExecuteWhenReady(base::Bind(
|
||||
&SimpleBackendImpl::IndexReadyForSizeCalculation, AsWeakPtr(), callback));
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::CalculateSizeOfEntriesBetween(
|
||||
base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) {
|
||||
return index_->ExecuteWhenReady(
|
||||
base::Bind(&SimpleBackendImpl::IndexReadyForSizeBetweenCalculation,
|
||||
AsWeakPtr(), initial_time, end_time, callback));
|
||||
}
|
||||
|
||||
class SimpleBackendImpl::SimpleIterator final : public Iterator {
|
||||
public:
|
||||
explicit SimpleIterator(base::WeakPtr<SimpleBackendImpl> backend)
|
||||
: backend_(backend),
|
||||
weak_factory_(this) {
|
||||
}
|
||||
|
||||
// From Backend::Iterator:
|
||||
int OpenNextEntry(Entry** next_entry,
|
||||
const CompletionCallback& callback) override {
|
||||
CompletionCallback open_next_entry_impl =
|
||||
base::Bind(&SimpleIterator::OpenNextEntryImpl,
|
||||
weak_factory_.GetWeakPtr(), next_entry, callback);
|
||||
return backend_->index_->ExecuteWhenReady(open_next_entry_impl);
|
||||
}
|
||||
|
||||
void OpenNextEntryImpl(Entry** next_entry,
|
||||
const CompletionCallback& callback,
|
||||
int index_initialization_error_code) {
|
||||
if (!backend_) {
|
||||
callback.Run(net::ERR_FAILED);
|
||||
return;
|
||||
}
|
||||
if (index_initialization_error_code != net::OK) {
|
||||
callback.Run(index_initialization_error_code);
|
||||
return;
|
||||
}
|
||||
if (!hashes_to_enumerate_)
|
||||
hashes_to_enumerate_ = backend_->index()->GetAllHashes();
|
||||
|
||||
while (!hashes_to_enumerate_->empty()) {
|
||||
uint64_t entry_hash = hashes_to_enumerate_->back();
|
||||
hashes_to_enumerate_->pop_back();
|
||||
if (backend_->index()->Has(entry_hash)) {
|
||||
*next_entry = NULL;
|
||||
CompletionCallback continue_iteration = base::Bind(
|
||||
&SimpleIterator::CheckIterationReturnValue,
|
||||
weak_factory_.GetWeakPtr(),
|
||||
next_entry,
|
||||
callback);
|
||||
int error_code_open = backend_->OpenEntryFromHash(entry_hash,
|
||||
next_entry,
|
||||
continue_iteration);
|
||||
if (error_code_open == net::ERR_IO_PENDING)
|
||||
return;
|
||||
if (error_code_open != net::ERR_FAILED) {
|
||||
callback.Run(error_code_open);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
callback.Run(net::ERR_FAILED);
|
||||
}
|
||||
|
||||
void CheckIterationReturnValue(Entry** entry,
|
||||
const CompletionCallback& callback,
|
||||
int error_code) {
|
||||
if (error_code == net::ERR_FAILED) {
|
||||
OpenNextEntry(entry, callback);
|
||||
return;
|
||||
}
|
||||
callback.Run(error_code);
|
||||
}
|
||||
|
||||
private:
|
||||
base::WeakPtr<SimpleBackendImpl> backend_;
|
||||
std::unique_ptr<std::vector<uint64_t>> hashes_to_enumerate_;
|
||||
base::WeakPtrFactory<SimpleIterator> weak_factory_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Backend::Iterator> SimpleBackendImpl::CreateIterator() {
|
||||
return std::unique_ptr<Iterator>(new SimpleIterator(AsWeakPtr()));
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::GetStats(base::StringPairs* stats) {
|
||||
std::pair<std::string, std::string> item;
|
||||
item.first = "Cache type";
|
||||
item.second = "Simple Cache";
|
||||
stats->push_back(item);
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::OnExternalCacheHit(const std::string& key) {
|
||||
index_->UseIfExists(simple_util::GetEntryHashKey(key));
|
||||
}
|
||||
|
||||
size_t SimpleBackendImpl::DumpMemoryStats(
|
||||
base::trace_event::ProcessMemoryDump* pmd,
|
||||
const std::string& parent_absolute_name) const {
|
||||
base::trace_event::MemoryAllocatorDump* dump =
|
||||
pmd->CreateAllocatorDump(parent_absolute_name + "/simple_backend");
|
||||
|
||||
size_t size = base::trace_event::EstimateMemoryUsage(index_) +
|
||||
base::trace_event::EstimateMemoryUsage(active_entries_);
|
||||
// TODO(xunjieli): crbug.com/669108. Track |entries_pending_doom_| once
|
||||
// base::Closure is suppported in memory_usage_estimator.h.
|
||||
dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
|
||||
base::trace_event::MemoryAllocatorDump::kUnitsBytes, size);
|
||||
return size;
|
||||
}
|
||||
|
||||
uint8_t SimpleBackendImpl::GetEntryInMemoryData(const std::string& key) {
|
||||
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
||||
return index_->GetEntryInMemoryData(entry_hash);
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::SetEntryInMemoryData(const std::string& key,
|
||||
uint8_t data) {
|
||||
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
||||
index_->SetEntryInMemoryData(entry_hash, data);
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::InitializeIndex(const CompletionCallback& callback,
|
||||
const DiskStatResult& result) {
|
||||
if (result.net_error == net::OK) {
|
||||
index_->SetMaxSize(result.max_size);
|
||||
index_->Initialize(result.cache_dir_mtime);
|
||||
}
|
||||
callback.Run(result.net_error);
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::IndexReadyForDoom(Time initial_time,
|
||||
Time end_time,
|
||||
const CompletionCallback& callback,
|
||||
int result) {
|
||||
if (result != net::OK) {
|
||||
callback.Run(result);
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<std::vector<uint64_t>> removed_key_hashes(
|
||||
index_->GetEntriesBetween(initial_time, end_time).release());
|
||||
DoomEntries(removed_key_hashes.get(), callback);
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::IndexReadyForSizeCalculation(
|
||||
const CompletionCallback& callback,
|
||||
int result) {
|
||||
if (result == net::OK)
|
||||
result = static_cast<int>(index_->GetCacheSize());
|
||||
callback.Run(result);
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::IndexReadyForSizeBetweenCalculation(
|
||||
base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback,
|
||||
int result) {
|
||||
if (result == net::OK) {
|
||||
result =
|
||||
static_cast<int>(index_->GetCacheSizeBetween(initial_time, end_time));
|
||||
}
|
||||
callback.Run(result);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleBackendImpl::DiskStatResult SimpleBackendImpl::InitCacheStructureOnDisk(
|
||||
const base::FilePath& path,
|
||||
uint64_t suggested_max_size,
|
||||
const SimpleExperiment& experiment) {
|
||||
DiskStatResult result;
|
||||
result.max_size = suggested_max_size;
|
||||
result.net_error = net::OK;
|
||||
if (!FileStructureConsistent(path, experiment)) {
|
||||
LOG(ERROR) << "Simple Cache Backend: wrong file structure on disk: "
|
||||
<< path.LossyDisplayName();
|
||||
result.net_error = net::ERR_FAILED;
|
||||
} else {
|
||||
bool mtime_result =
|
||||
disk_cache::simple_util::GetMTime(path, &result.cache_dir_mtime);
|
||||
DCHECK(mtime_result);
|
||||
if (!result.max_size) {
|
||||
int64_t available = base::SysInfo::AmountOfFreeDiskSpace(path);
|
||||
result.max_size = disk_cache::PreferredCacheSize(available);
|
||||
|
||||
if (experiment.type == SimpleExperimentType::SIZE) {
|
||||
int64_t adjusted_max_size = (result.max_size * experiment.param) / 100;
|
||||
adjusted_max_size =
|
||||
std::min(static_cast<int64_t>(std::numeric_limits<int32_t>::max()),
|
||||
adjusted_max_size);
|
||||
result.max_size = adjusted_max_size;
|
||||
}
|
||||
}
|
||||
DCHECK(result.max_size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
scoped_refptr<SimpleEntryImpl>
|
||||
SimpleBackendImpl::CreateOrFindActiveOrDoomedEntry(
|
||||
const uint64_t entry_hash,
|
||||
const std::string& key,
|
||||
std::vector<Closure>** post_doom) {
|
||||
DCHECK_EQ(entry_hash, simple_util::GetEntryHashKey(key));
|
||||
|
||||
// If there is a doom pending, we would want to serialize after it.
|
||||
std::unordered_map<uint64_t, std::vector<Closure>>::iterator doom_it =
|
||||
entries_pending_doom_.find(entry_hash);
|
||||
if (doom_it != entries_pending_doom_.end()) {
|
||||
*post_doom = &doom_it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::pair<EntryMap::iterator, bool> insert_result =
|
||||
active_entries_.insert(EntryMap::value_type(entry_hash, NULL));
|
||||
EntryMap::iterator& it = insert_result.first;
|
||||
const bool did_insert = insert_result.second;
|
||||
if (did_insert) {
|
||||
SimpleEntryImpl* entry = it->second = new SimpleEntryImpl(
|
||||
cache_type_, path_, cleanup_tracker_.get(), entry_hash,
|
||||
entry_operations_mode_, this, file_tracker_, net_log_);
|
||||
entry->SetKey(key);
|
||||
entry->SetActiveEntryProxy(ActiveEntryProxy::Create(entry_hash, this));
|
||||
}
|
||||
DCHECK(it->second);
|
||||
// It's possible, but unlikely, that we have an entry hash collision with a
|
||||
// currently active entry.
|
||||
if (key != it->second->key()) {
|
||||
it->second->Doom();
|
||||
DCHECK_EQ(0U, active_entries_.count(entry_hash));
|
||||
DCHECK_EQ(1U, entries_pending_doom_.count(entry_hash));
|
||||
// Re-run ourselves to handle the now-pending doom.
|
||||
return CreateOrFindActiveOrDoomedEntry(entry_hash, key, post_doom);
|
||||
}
|
||||
return base::WrapRefCounted(it->second);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::OpenEntryFromHash(uint64_t entry_hash,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) {
|
||||
std::unordered_map<uint64_t, std::vector<Closure>>::iterator it =
|
||||
entries_pending_doom_.find(entry_hash);
|
||||
if (it != entries_pending_doom_.end()) {
|
||||
Callback<int(const net::CompletionCallback&)> operation =
|
||||
base::Bind(&SimpleBackendImpl::OpenEntryFromHash,
|
||||
base::Unretained(this), entry_hash, entry);
|
||||
it->second.push_back(base::Bind(&RunOperationAndCallback,
|
||||
operation, callback));
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
EntryMap::iterator has_active = active_entries_.find(entry_hash);
|
||||
if (has_active != active_entries_.end()) {
|
||||
return OpenEntry(has_active->second->key(), entry, callback);
|
||||
}
|
||||
|
||||
scoped_refptr<SimpleEntryImpl> simple_entry = new SimpleEntryImpl(
|
||||
cache_type_, path_, cleanup_tracker_.get(), entry_hash,
|
||||
entry_operations_mode_, this, file_tracker_, net_log_);
|
||||
CompletionCallback backend_callback =
|
||||
base::Bind(&SimpleBackendImpl::OnEntryOpenedFromHash,
|
||||
AsWeakPtr(), entry_hash, entry, simple_entry, callback);
|
||||
return simple_entry->OpenEntry(entry, backend_callback);
|
||||
}
|
||||
|
||||
int SimpleBackendImpl::DoomEntryFromHash(uint64_t entry_hash,
|
||||
const CompletionCallback& callback) {
|
||||
Entry** entry = new Entry*();
|
||||
std::unique_ptr<Entry*> scoped_entry(entry);
|
||||
|
||||
std::unordered_map<uint64_t, std::vector<Closure>>::iterator pending_it =
|
||||
entries_pending_doom_.find(entry_hash);
|
||||
if (pending_it != entries_pending_doom_.end()) {
|
||||
Callback<int(const net::CompletionCallback&)> operation =
|
||||
base::Bind(&SimpleBackendImpl::DoomEntryFromHash,
|
||||
base::Unretained(this), entry_hash);
|
||||
pending_it->second.push_back(base::Bind(&RunOperationAndCallback,
|
||||
operation, callback));
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
EntryMap::iterator active_it = active_entries_.find(entry_hash);
|
||||
if (active_it != active_entries_.end())
|
||||
return active_it->second->DoomEntry(callback);
|
||||
|
||||
// There's no pending dooms, nor any open entry. We can make a trivial
|
||||
// call to DoomEntries() to delete this entry.
|
||||
std::vector<uint64_t> entry_hash_vector;
|
||||
entry_hash_vector.push_back(entry_hash);
|
||||
DoomEntries(&entry_hash_vector, callback);
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::OnEntryOpenedFromHash(
|
||||
uint64_t hash,
|
||||
Entry** entry,
|
||||
const scoped_refptr<SimpleEntryImpl>& simple_entry,
|
||||
const CompletionCallback& callback,
|
||||
int error_code) {
|
||||
if (error_code != net::OK) {
|
||||
callback.Run(error_code);
|
||||
return;
|
||||
}
|
||||
DCHECK(*entry);
|
||||
std::pair<EntryMap::iterator, bool> insert_result =
|
||||
active_entries_.insert(EntryMap::value_type(hash, simple_entry.get()));
|
||||
EntryMap::iterator& it = insert_result.first;
|
||||
const bool did_insert = insert_result.second;
|
||||
if (did_insert) {
|
||||
// There was no active entry corresponding to this hash. We've already put
|
||||
// the entry opened from hash in the |active_entries_|. We now provide the
|
||||
// proxy object to the entry.
|
||||
it->second->SetActiveEntryProxy(ActiveEntryProxy::Create(hash, this));
|
||||
callback.Run(net::OK);
|
||||
} else {
|
||||
// The entry was made active while we waiting for the open from hash to
|
||||
// finish. The entry created from hash needs to be closed, and the one
|
||||
// in |active_entries_| can be returned to the caller.
|
||||
simple_entry->Close();
|
||||
it->second->OpenEntry(entry, callback);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleBackendImpl::DoomEntriesComplete(
|
||||
std::unique_ptr<std::vector<uint64_t>> entry_hashes,
|
||||
const net::CompletionCallback& callback,
|
||||
int result) {
|
||||
for (const uint64_t& entry_hash : *entry_hashes)
|
||||
OnDoomComplete(entry_hash);
|
||||
callback.Run(result);
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleBackendImpl::FlushWorkerPoolForTesting() {
|
||||
// TODO(morlovich): Remove this, move everything over to disk_cache:: use.
|
||||
base::TaskScheduler::GetInstance()->FlushForTesting();
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
258
net/disk_cache/simple/simple_backend_impl.h
Normal file
258
net/disk_cache/simple/simple_backend_impl.h
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_IMPL_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_IMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/task_runner.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/disk_cache/simple/simple_entry_impl.h"
|
||||
#include "net/disk_cache/simple/simple_experiment.h"
|
||||
#include "net/disk_cache/simple/simple_index_delegate.h"
|
||||
|
||||
namespace base {
|
||||
class SequencedTaskRunner;
|
||||
class TaskRunner;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// SimpleBackendImpl is a new cache backend that stores entries in individual
|
||||
// files.
|
||||
// See http://www.chromium.org/developers/design-documents/network-stack/disk-cache/very-simple-backend
|
||||
//
|
||||
// The SimpleBackendImpl provides safe iteration; mutating entries during
|
||||
// iteration cannot cause a crash. It is undefined whether entries created or
|
||||
// destroyed during the iteration will be included in any pre-existing
|
||||
// iterations.
|
||||
//
|
||||
// The non-static functions below must be called on the IO thread unless
|
||||
// otherwise stated.
|
||||
|
||||
class BackendCleanupTracker;
|
||||
class SimpleEntryImpl;
|
||||
class SimpleFileTracker;
|
||||
class SimpleIndex;
|
||||
|
||||
class NET_EXPORT_PRIVATE SimpleBackendImpl : public Backend,
|
||||
public SimpleIndexDelegate,
|
||||
public base::SupportsWeakPtr<SimpleBackendImpl> {
|
||||
public:
|
||||
// Note: only pass non-nullptr for |file_tracker| if you don't want the global
|
||||
// one (which things other than tests would want). |file_tracker| must outlive
|
||||
// the backend and all the entries, including their asynchronous close.
|
||||
SimpleBackendImpl(
|
||||
const base::FilePath& path,
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker,
|
||||
SimpleFileTracker* file_tracker,
|
||||
int max_bytes,
|
||||
net::CacheType cache_type,
|
||||
const scoped_refptr<base::SequencedTaskRunner>& cache_runner,
|
||||
net::NetLog* net_log);
|
||||
|
||||
~SimpleBackendImpl() override;
|
||||
|
||||
net::CacheType cache_type() const { return cache_type_; }
|
||||
SimpleIndex* index() { return index_.get(); }
|
||||
|
||||
base::TaskRunner* worker_pool() { return worker_pool_.get(); }
|
||||
|
||||
int Init(const CompletionCallback& completion_callback);
|
||||
|
||||
// Sets the maximum size for the total amount of data stored by this instance.
|
||||
bool SetMaxSize(int max_bytes);
|
||||
|
||||
// Returns the maximum file size permitted in this backend.
|
||||
int GetMaxFileSize() const;
|
||||
|
||||
// Flush our SequencedWorkerPool.
|
||||
static void FlushWorkerPoolForTesting();
|
||||
|
||||
// The entry for |entry_hash| is being doomed; the backend will not attempt
|
||||
// run new operations for this |entry_hash| until the Doom is completed.
|
||||
void OnDoomStart(uint64_t entry_hash);
|
||||
|
||||
// The entry for |entry_hash| has been successfully doomed, we can now allow
|
||||
// operations on this entry, and we can run any operations enqueued while the
|
||||
// doom completed.
|
||||
void OnDoomComplete(uint64_t entry_hash);
|
||||
|
||||
// SimpleIndexDelegate:
|
||||
void DoomEntries(std::vector<uint64_t>* entry_hashes,
|
||||
const CompletionCallback& callback) override;
|
||||
|
||||
// Backend:
|
||||
net::CacheType GetCacheType() const override;
|
||||
int32_t GetEntryCount() const override;
|
||||
int OpenEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) override;
|
||||
int CreateEntry(const std::string& key,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomEntry(const std::string& key,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomAllEntries(const CompletionCallback& callback) override;
|
||||
int DoomEntriesBetween(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) override;
|
||||
int DoomEntriesSince(base::Time initial_time,
|
||||
const CompletionCallback& callback) override;
|
||||
int CalculateSizeOfAllEntries(const CompletionCallback& callback) override;
|
||||
int CalculateSizeOfEntriesBetween(
|
||||
base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback) override;
|
||||
std::unique_ptr<Iterator> CreateIterator() override;
|
||||
void GetStats(base::StringPairs* stats) override;
|
||||
void OnExternalCacheHit(const std::string& key) override;
|
||||
size_t DumpMemoryStats(
|
||||
base::trace_event::ProcessMemoryDump* pmd,
|
||||
const std::string& parent_absolute_name) const override;
|
||||
uint8_t GetEntryInMemoryData(const std::string& key) override;
|
||||
void SetEntryInMemoryData(const std::string& key, uint8_t data) override;
|
||||
|
||||
private:
|
||||
class SimpleIterator;
|
||||
friend class SimpleIterator;
|
||||
|
||||
using EntryMap = std::unordered_map<uint64_t, SimpleEntryImpl*>;
|
||||
|
||||
using InitializeIndexCallback =
|
||||
base::Callback<void(base::Time mtime, uint64_t max_size, int result)>;
|
||||
|
||||
class ActiveEntryProxy;
|
||||
friend class ActiveEntryProxy;
|
||||
|
||||
// Return value of InitCacheStructureOnDisk().
|
||||
struct DiskStatResult {
|
||||
base::Time cache_dir_mtime;
|
||||
uint64_t max_size;
|
||||
bool detected_magic_number_mismatch;
|
||||
int net_error;
|
||||
};
|
||||
|
||||
void InitializeIndex(const CompletionCallback& callback,
|
||||
const DiskStatResult& result);
|
||||
|
||||
// Dooms all entries previously accessed between |initial_time| and
|
||||
// |end_time|. Invoked when the index is ready.
|
||||
void IndexReadyForDoom(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback,
|
||||
int result);
|
||||
|
||||
// Calculates the size of the entire cache. Invoked when the index is ready.
|
||||
void IndexReadyForSizeCalculation(const CompletionCallback& callback,
|
||||
int result);
|
||||
|
||||
// Calculates the size all cache entries between |initial_time| and
|
||||
// |end_time|. Invoked when the index is ready.
|
||||
void IndexReadyForSizeBetweenCalculation(base::Time initial_time,
|
||||
base::Time end_time,
|
||||
const CompletionCallback& callback,
|
||||
int result);
|
||||
|
||||
// Try to create the directory if it doesn't exist. This must run on the IO
|
||||
// thread.
|
||||
static DiskStatResult InitCacheStructureOnDisk(
|
||||
const base::FilePath& path,
|
||||
uint64_t suggested_max_size,
|
||||
const SimpleExperiment& experiment);
|
||||
|
||||
// Looks at current state of |entries_pending_doom_| and |active_entries_|
|
||||
// relevant to |entry_hash|, and, as appropriate, either returns a valid entry
|
||||
// matching |entry_hash| and |key|, or returns nullptr and sets |*post_doom|
|
||||
// to point to a vector of closures which will be invoked when it's an
|
||||
// appropriate time to try again. The caller is expected to append its retry
|
||||
// closure to that vector.
|
||||
scoped_refptr<SimpleEntryImpl> CreateOrFindActiveOrDoomedEntry(
|
||||
uint64_t entry_hash,
|
||||
const std::string& key,
|
||||
std::vector<base::Closure>** post_doom);
|
||||
|
||||
// Given a hash, will try to open the corresponding Entry. If we have an Entry
|
||||
// corresponding to |hash| in the map of active entries, opens it. Otherwise,
|
||||
// a new empty Entry will be created, opened and filled with information from
|
||||
// the disk.
|
||||
int OpenEntryFromHash(uint64_t entry_hash,
|
||||
Entry** entry,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
// Doom the entry corresponding to |entry_hash|, if it's active or currently
|
||||
// pending doom. This function does not block if there is an active entry,
|
||||
// which is very important to prevent races in DoomEntries() above.
|
||||
int DoomEntryFromHash(uint64_t entry_hash,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
// Called when we tried to open an entry with hash alone. When a blank entry
|
||||
// has been created and filled in with information from the disk - based on a
|
||||
// hash alone - this checks that a duplicate active entry was not created
|
||||
// using a key in the meantime.
|
||||
void OnEntryOpenedFromHash(uint64_t hash,
|
||||
Entry** entry,
|
||||
const scoped_refptr<SimpleEntryImpl>& simple_entry,
|
||||
const CompletionCallback& callback,
|
||||
int error_code);
|
||||
|
||||
// Called when we tried to open an entry from key. When the entry has been
|
||||
// opened, a check for key mismatch is performed.
|
||||
void OnEntryOpenedFromKey(const std::string key,
|
||||
Entry** entry,
|
||||
const scoped_refptr<SimpleEntryImpl>& simple_entry,
|
||||
const CompletionCallback& callback,
|
||||
int error_code);
|
||||
|
||||
// A callback thunk used by DoomEntries to clear the |entries_pending_doom_|
|
||||
// after a mass doom.
|
||||
void DoomEntriesComplete(std::unique_ptr<std::vector<uint64_t>> entry_hashes,
|
||||
const CompletionCallback& callback,
|
||||
int result);
|
||||
|
||||
// We want this destroyed after every other field.
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker_;
|
||||
|
||||
SimpleFileTracker* const file_tracker_;
|
||||
|
||||
const base::FilePath path_;
|
||||
const net::CacheType cache_type_;
|
||||
std::unique_ptr<SimpleIndex> index_;
|
||||
const scoped_refptr<base::SequencedTaskRunner> cache_runner_;
|
||||
scoped_refptr<base::TaskRunner> worker_pool_;
|
||||
|
||||
int orig_max_size_;
|
||||
const SimpleEntryImpl::OperationsMode entry_operations_mode_;
|
||||
|
||||
EntryMap active_entries_;
|
||||
|
||||
// The set of all entries which are currently being doomed. To avoid races,
|
||||
// these entries cannot have Doom/Create/Open operations run until the doom
|
||||
// is complete. The base::Closure map target is used to store deferred
|
||||
// operations to be run at the completion of the Doom.
|
||||
std::unordered_map<uint64_t, std::vector<base::Closure>>
|
||||
entries_pending_doom_;
|
||||
|
||||
net::NetLog* const net_log_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_IMPL_H_
|
||||
30
net/disk_cache/simple/simple_backend_version.h
Normal file
30
net/disk_cache/simple/simple_backend_version.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Short rules helping to think about data upgrades within Simple Cache:
|
||||
// * ALL changes of on-disk data format, backward-compatible or not,
|
||||
// forward-compatible or not, require updating the |kSimpleVersion|.
|
||||
// * All cache Upgrades are performed on backend start, must be finished
|
||||
// before the new backend starts processing any incoming operations.
|
||||
// * If the Upgrade is not implemented for transition from
|
||||
// |kSimpleVersion - 1| then the whole cache directory will be cleared.
|
||||
// * Dropping cache data on disk or some of its parts can be a valid way to
|
||||
// Upgrade.
|
||||
const uint32_t kLastCompatSparseVersion = 7;
|
||||
const uint32_t kSimpleVersion = 8;
|
||||
|
||||
// The version of the entry file(s) as written to disk. Must be updated iff the
|
||||
// entry format changes with the overall backend version update.
|
||||
const uint32_t kSimpleEntryVersionOnDisk = 5;
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_BACKEND_VERSION_H_
|
||||
26
net/disk_cache/simple/simple_entry_format.cc
Normal file
26
net/disk_cache/simple/simple_entry_format.cc
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
SimpleFileHeader::SimpleFileHeader() {
|
||||
// Make hashing repeatable: leave no padding bytes untouched.
|
||||
std::memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
SimpleFileEOF::SimpleFileEOF() {
|
||||
// Make hashing repeatable: leave no padding bytes untouched.
|
||||
std::memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
SimpleFileSparseRangeHeader::SimpleFileSparseRangeHeader() {
|
||||
// Make hashing repeatable: leave no padding bytes untouched.
|
||||
std::memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
86
net/disk_cache/simple/simple_entry_format.h
Normal file
86
net/disk_cache/simple/simple_entry_format.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
const uint64_t kSimpleInitialMagicNumber = UINT64_C(0xfcfb6d1ba7725c30);
|
||||
const uint64_t kSimpleFinalMagicNumber = UINT64_C(0xf4fa6f45970d41d8);
|
||||
const uint64_t kSimpleSparseRangeMagicNumber = UINT64_C(0xeb97bf016553676b);
|
||||
|
||||
// A file containing stream 0 and stream 1 in the Simple cache consists of:
|
||||
// - a SimpleFileHeader.
|
||||
// - the key.
|
||||
// - the data from stream 1.
|
||||
// - a SimpleFileEOF record for stream 1.
|
||||
// - the data from stream 0.
|
||||
// - (optionally) the SHA256 of the key.
|
||||
// - a SimpleFileEOF record for stream 0.
|
||||
//
|
||||
// Because stream 0 data (typically HTTP headers) is on the critical path of
|
||||
// requests, on open, the cache reads the end of the record and does not
|
||||
// read the SimpleFileHeader. If the key can be validated with a SHA256, then
|
||||
// the stream 0 data can be returned to the caller without reading the
|
||||
// SimpleFileHeader. If the key SHA256 is not present, then the cache must
|
||||
// read the SimpleFileHeader to confirm key equality.
|
||||
|
||||
// A file containing stream 2 in the Simple cache consists of:
|
||||
// - a SimpleFileHeader.
|
||||
// - the key.
|
||||
// - the data.
|
||||
// - at the end, a SimpleFileEOF record.
|
||||
|
||||
// This is the number of files we can use for representing normal/dense streams.
|
||||
static const int kSimpleEntryNormalFileCount = 2;
|
||||
static const int kSimpleEntryStreamCount = 3;
|
||||
|
||||
// Total # of files name we can potentially use; this includes both normal
|
||||
// API and sparse streams.
|
||||
static const int kSimpleEntryTotalFileCount = kSimpleEntryNormalFileCount + 1;
|
||||
|
||||
// Note that stream 0/stream 1 files rely on the footer to verify the entry,
|
||||
// so if the format changes, it's insufficient to change the version here;
|
||||
// likely the EOF magic should be updated as well.
|
||||
struct NET_EXPORT_PRIVATE SimpleFileHeader {
|
||||
SimpleFileHeader();
|
||||
|
||||
uint64_t initial_magic_number;
|
||||
uint32_t version;
|
||||
uint32_t key_length;
|
||||
uint32_t key_hash;
|
||||
};
|
||||
|
||||
struct NET_EXPORT_PRIVATE SimpleFileEOF {
|
||||
enum Flags {
|
||||
FLAG_HAS_CRC32 = (1U << 0),
|
||||
FLAG_HAS_KEY_SHA256 = (1U << 1), // Preceding the record if present.
|
||||
};
|
||||
|
||||
SimpleFileEOF();
|
||||
|
||||
uint64_t final_magic_number;
|
||||
uint32_t flags;
|
||||
uint32_t data_crc32;
|
||||
// |stream_size| is only used in the EOF record for stream 0.
|
||||
uint32_t stream_size;
|
||||
};
|
||||
|
||||
struct SimpleFileSparseRangeHeader {
|
||||
SimpleFileSparseRangeHeader();
|
||||
|
||||
uint64_t sparse_range_magic_number;
|
||||
int64_t offset;
|
||||
int64_t length;
|
||||
uint32_t data_crc32;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_H_
|
||||
62
net/disk_cache/simple/simple_entry_format_history.h
Normal file
62
net/disk_cache/simple/simple_entry_format_history.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace simplecache_v5 {
|
||||
|
||||
const uint64_t kSimpleInitialMagicNumber = UINT64_C(0xfcfb6d1ba7725c30);
|
||||
const uint64_t kSimpleFinalMagicNumber = UINT64_C(0xf4fa6f45970d41d8);
|
||||
|
||||
// A file containing stream 0 and stream 1 in the Simple cache consists of:
|
||||
// - a SimpleFileHeader.
|
||||
// - the key.
|
||||
// - the data from stream 1.
|
||||
// - a SimpleFileEOF record for stream 1.
|
||||
// - the data from stream 0.
|
||||
// - a SimpleFileEOF record for stream 0.
|
||||
|
||||
// A file containing stream 2 in the Simple cache consists of:
|
||||
// - a SimpleFileHeader.
|
||||
// - the key.
|
||||
// - the data.
|
||||
// - at the end, a SimpleFileEOF record.
|
||||
static const int kSimpleEntryFileCount = 2;
|
||||
static const int kSimpleEntryStreamCount = 3;
|
||||
|
||||
struct NET_EXPORT_PRIVATE SimpleFileHeader {
|
||||
SimpleFileHeader();
|
||||
|
||||
uint64_t initial_magic_number;
|
||||
uint32_t version;
|
||||
uint32_t key_length;
|
||||
uint32_t key_hash;
|
||||
};
|
||||
|
||||
struct NET_EXPORT_PRIVATE SimpleFileEOF {
|
||||
enum Flags {
|
||||
FLAG_HAS_CRC32 = (1U << 0),
|
||||
};
|
||||
|
||||
SimpleFileEOF();
|
||||
|
||||
uint64_t final_magic_number;
|
||||
uint32_t flags;
|
||||
uint32_t data_crc32;
|
||||
// |stream_size| is only used in the EOF record for stream 0.
|
||||
uint32_t stream_size;
|
||||
};
|
||||
|
||||
} // namespace simplecache_v5
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_FORMAT_HISTORY_H_
|
||||
1643
net/disk_cache/simple/simple_entry_impl.cc
Normal file
1643
net/disk_cache/simple/simple_entry_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
433
net/disk_cache/simple/simple_entry_impl.h
Normal file
433
net/disk_cache/simple/simple_entry_impl.h
Normal file
@@ -0,0 +1,433 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_IMPL_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_IMPL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/containers/queue.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
#include "net/disk_cache/simple/simple_entry_operation.h"
|
||||
#include "net/disk_cache/simple/simple_synchronous_entry.h"
|
||||
#include "net/log/net_log_event_type.h"
|
||||
#include "net/log/net_log_with_source.h"
|
||||
|
||||
namespace base {
|
||||
class TaskRunner;
|
||||
}
|
||||
|
||||
namespace net {
|
||||
class GrowableIOBuffer;
|
||||
class IOBuffer;
|
||||
class NetLog;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class BackendCleanupTracker;
|
||||
class SimpleBackendImpl;
|
||||
class SimpleEntryStat;
|
||||
class SimpleFileTracker;
|
||||
class SimpleSynchronousEntry;
|
||||
struct SimpleEntryCreationResults;
|
||||
|
||||
// SimpleEntryImpl is the IO thread interface to an entry in the very simple
|
||||
// disk cache. It proxies for the SimpleSynchronousEntry, which performs IO
|
||||
// on the worker thread.
|
||||
class NET_EXPORT_PRIVATE SimpleEntryImpl : public Entry,
|
||||
public base::RefCounted<SimpleEntryImpl> {
|
||||
friend class base::RefCounted<SimpleEntryImpl>;
|
||||
public:
|
||||
enum OperationsMode {
|
||||
NON_OPTIMISTIC_OPERATIONS,
|
||||
OPTIMISTIC_OPERATIONS,
|
||||
};
|
||||
|
||||
// The Backend provides an |ActiveEntryProxy| instance to this entry when it
|
||||
// is active, meaning it's the canonical entry for this |entry_hash_|. The
|
||||
// entry can make itself inactive by deleting its proxy.
|
||||
class ActiveEntryProxy {
|
||||
public:
|
||||
virtual ~ActiveEntryProxy() = 0;
|
||||
};
|
||||
|
||||
SimpleEntryImpl(net::CacheType cache_type,
|
||||
const base::FilePath& path,
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker,
|
||||
uint64_t entry_hash,
|
||||
OperationsMode operations_mode,
|
||||
SimpleBackendImpl* backend,
|
||||
SimpleFileTracker* file_tracker,
|
||||
net::NetLog* net_log);
|
||||
|
||||
void SetActiveEntryProxy(
|
||||
std::unique_ptr<ActiveEntryProxy> active_entry_proxy);
|
||||
|
||||
// Adds another reader/writer to this entry, if possible, returning |this| to
|
||||
// |entry|.
|
||||
int OpenEntry(Entry** entry, const CompletionCallback& callback);
|
||||
|
||||
// Creates this entry, if possible. Returns |this| to |entry|.
|
||||
int CreateEntry(Entry** entry, const CompletionCallback& callback);
|
||||
|
||||
// Identical to Backend::Doom() except that it accepts a CompletionCallback.
|
||||
int DoomEntry(const CompletionCallback& callback);
|
||||
|
||||
const std::string& key() const { return key_; }
|
||||
uint64_t entry_hash() const { return entry_hash_; }
|
||||
|
||||
// The key is not a constructor parameter to the SimpleEntryImpl, because
|
||||
// during cache iteration, it's necessary to open entries by their hash
|
||||
// alone. In that case, the SimpleSynchronousEntry will read the key from disk
|
||||
// and it will be set.
|
||||
void SetKey(const std::string& key);
|
||||
|
||||
// SetCreatePendingDoom() should be called before CreateEntry() if the
|
||||
// creation should suceed optimistically but not do any I/O until
|
||||
// NotifyDoomBeforeCreateComplete() is called.
|
||||
void SetCreatePendingDoom();
|
||||
void NotifyDoomBeforeCreateComplete();
|
||||
|
||||
// From Entry:
|
||||
void Doom() override;
|
||||
void Close() override;
|
||||
std::string GetKey() const override;
|
||||
base::Time GetLastUsed() const override;
|
||||
base::Time GetLastModified() const override;
|
||||
int32_t GetDataSize(int index) const override;
|
||||
int ReadData(int stream_index,
|
||||
int offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int WriteData(int stream_index,
|
||||
int offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback,
|
||||
bool truncate) override;
|
||||
int ReadSparseData(int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int WriteSparseData(int64_t offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback) override;
|
||||
int GetAvailableRange(int64_t offset,
|
||||
int len,
|
||||
int64_t* start,
|
||||
const CompletionCallback& callback) override;
|
||||
bool CouldBeSparse() const override;
|
||||
void CancelSparseIO() override;
|
||||
int ReadyForSparseIO(const CompletionCallback& callback) override;
|
||||
|
||||
// Returns the estimate of dynamically allocated memory in bytes.
|
||||
size_t EstimateMemoryUsage() const;
|
||||
|
||||
private:
|
||||
class ScopedOperationRunner;
|
||||
friend class ScopedOperationRunner;
|
||||
|
||||
enum State {
|
||||
// The state immediately after construction, but before |synchronous_entry_|
|
||||
// has been assigned. This is the state at construction, and is the only
|
||||
// legal state to destruct an entry in.
|
||||
STATE_UNINITIALIZED,
|
||||
|
||||
// This entry is available for regular IO.
|
||||
STATE_READY,
|
||||
|
||||
// IO is currently in flight, operations must wait for completion before
|
||||
// launching.
|
||||
STATE_IO_PENDING,
|
||||
|
||||
// A failure occurred in the current or previous operation. All operations
|
||||
// after that must fail, until we receive a Close().
|
||||
STATE_FAILURE,
|
||||
};
|
||||
|
||||
// Used in histograms, please only add entries at the end.
|
||||
enum CheckCrcResult {
|
||||
CRC_CHECK_NEVER_READ_TO_END = 0,
|
||||
CRC_CHECK_NOT_DONE = 1,
|
||||
CRC_CHECK_DONE = 2,
|
||||
CRC_CHECK_NEVER_READ_AT_ALL = 3,
|
||||
CRC_CHECK_MAX = 4,
|
||||
};
|
||||
|
||||
~SimpleEntryImpl() override;
|
||||
|
||||
// Must be used to invoke a client-provided completion callback for an
|
||||
// operation initiated through the backend (e.g. create, open, doom) so that
|
||||
// clients don't get notified after they deleted the backend (which they would
|
||||
// not expect).
|
||||
void PostClientCallback(const CompletionCallback& callback, int result);
|
||||
|
||||
// Sets entry to STATE_UNINITIALIZED.
|
||||
void MakeUninitialized();
|
||||
|
||||
// Return this entry to a user of the API in |out_entry|. Increments the user
|
||||
// count.
|
||||
void ReturnEntryToCaller(Entry** out_entry);
|
||||
|
||||
// An error occured, and the SimpleSynchronousEntry should have Doomed
|
||||
// us at this point. We need to remove |this| from the Backend and the
|
||||
// index.
|
||||
void MarkAsDoomed();
|
||||
|
||||
// Runs the next operation in the queue, if any and if there is no other
|
||||
// operation running at the moment.
|
||||
// WARNING: May delete |this|, as an operation in the queue can contain
|
||||
// the last reference.
|
||||
void RunNextOperationIfNeeded();
|
||||
|
||||
void OpenEntryInternal(bool have_index,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry);
|
||||
|
||||
void CreateEntryInternal(bool have_index,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry);
|
||||
|
||||
void CloseInternal();
|
||||
|
||||
int ReadDataInternal(bool sync_possible,
|
||||
int index,
|
||||
int offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
void WriteDataInternal(int index,
|
||||
int offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback,
|
||||
bool truncate);
|
||||
|
||||
void ReadSparseDataInternal(int64_t sparse_offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
void WriteSparseDataInternal(int64_t sparse_offset,
|
||||
net::IOBuffer* buf,
|
||||
int buf_len,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
void GetAvailableRangeInternal(int64_t sparse_offset,
|
||||
int len,
|
||||
int64_t* out_start,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
void DoomEntryInternal(const CompletionCallback& callback);
|
||||
|
||||
// Called after a SimpleSynchronousEntry has completed CreateEntry() or
|
||||
// OpenEntry(). If |in_sync_entry| is non-NULL, creation is successful and we
|
||||
// can return |this| SimpleEntryImpl to |*out_entry|. Runs
|
||||
// |completion_callback|.
|
||||
void CreationOperationComplete(
|
||||
const CompletionCallback& completion_callback,
|
||||
const base::TimeTicks& start_time,
|
||||
std::unique_ptr<SimpleEntryCreationResults> in_results,
|
||||
Entry** out_entry,
|
||||
net::NetLogEventType end_event_type);
|
||||
|
||||
// Called after we've closed and written the EOF record to our entry. Until
|
||||
// this point it hasn't been safe to OpenEntry() the same entry, but from this
|
||||
// point it is.
|
||||
void CloseOperationComplete();
|
||||
|
||||
// Internal utility method used by other completion methods. Calls
|
||||
// |completion_callback| after updating state and dooming on errors.
|
||||
void EntryOperationComplete(const CompletionCallback& completion_callback,
|
||||
const SimpleEntryStat& entry_stat,
|
||||
std::unique_ptr<int> result);
|
||||
|
||||
// Called after an asynchronous read. Updates |crc32s_| if possible.
|
||||
void ReadOperationComplete(
|
||||
int stream_index,
|
||||
int offset,
|
||||
const CompletionCallback& completion_callback,
|
||||
std::unique_ptr<SimpleSynchronousEntry::CRCRequest> crc_request,
|
||||
std::unique_ptr<SimpleEntryStat> entry_stat,
|
||||
std::unique_ptr<int> result);
|
||||
|
||||
// Called after an asynchronous write completes.
|
||||
// |buf| parameter brings back a reference to net::IOBuffer to the original
|
||||
// thread, so that we can reduce cross thread malloc/free pair.
|
||||
// See http://crbug.com/708644 for details.
|
||||
void WriteOperationComplete(int stream_index,
|
||||
const CompletionCallback& completion_callback,
|
||||
std::unique_ptr<SimpleEntryStat> entry_stat,
|
||||
std::unique_ptr<int> result,
|
||||
net::IOBuffer* buf);
|
||||
|
||||
void ReadSparseOperationComplete(
|
||||
const CompletionCallback& completion_callback,
|
||||
std::unique_ptr<base::Time> last_used,
|
||||
std::unique_ptr<int> result);
|
||||
|
||||
void WriteSparseOperationComplete(
|
||||
const CompletionCallback& completion_callback,
|
||||
std::unique_ptr<SimpleEntryStat> entry_stat,
|
||||
std::unique_ptr<int> result);
|
||||
|
||||
void GetAvailableRangeOperationComplete(
|
||||
const CompletionCallback& completion_callback,
|
||||
std::unique_ptr<int> result);
|
||||
|
||||
// Called after an asynchronous doom completes.
|
||||
void DoomOperationComplete(const CompletionCallback& callback,
|
||||
State state_to_restore,
|
||||
int result);
|
||||
|
||||
// Reports reads result potentially refining status based on |crc_result|.
|
||||
// |crc_result| is permitted to be null.
|
||||
void RecordReadResultConsideringChecksum(
|
||||
int result,
|
||||
std::unique_ptr<SimpleSynchronousEntry::CRCRequest> crc_result) const;
|
||||
|
||||
// Called after completion of an operation, to either incoproprate file info
|
||||
// received from I/O done on the worker pool, or to simply bump the
|
||||
// timestamps. Updates the metadata both in |this| and in the index.
|
||||
// Stream size information in particular may be important for following
|
||||
// operations.
|
||||
void UpdateDataFromEntryStat(const SimpleEntryStat& entry_stat);
|
||||
|
||||
int64_t GetDiskUsage() const;
|
||||
|
||||
// Used to report histograms.
|
||||
void RecordReadIsParallelizable(const SimpleEntryOperation& operation) const;
|
||||
void RecordWriteDependencyType(const SimpleEntryOperation& operation) const;
|
||||
|
||||
// Completes a read from the stream data kept in memory, logging metrics
|
||||
// and updating metadata. Returns the # of bytes read successfully.
|
||||
// This asumes the caller has already range-checked offset and buf_len
|
||||
// appropriately.
|
||||
int ReadFromBuffer(net::GrowableIOBuffer* in_buf,
|
||||
int offset,
|
||||
int buf_len,
|
||||
net::IOBuffer* out_buf);
|
||||
|
||||
// Copies data from |buf| to the internal in-memory buffer for stream 0. If
|
||||
// |truncate| is set to true, the target buffer will be truncated at |offset|
|
||||
// + |buf_len| before being written.
|
||||
int SetStream0Data(net::IOBuffer* buf,
|
||||
int offset, int buf_len,
|
||||
bool truncate);
|
||||
|
||||
// Updates |crc32s_| and |crc32s_end_offset_| for a write of the data in
|
||||
// |buffer| on |stream_index|, starting at |offset| and of length |length|.
|
||||
void AdvanceCrc(net::IOBuffer* buffer,
|
||||
int offset,
|
||||
int length,
|
||||
int stream_index);
|
||||
|
||||
// We want all async I/O on entries to complete before recycling the dir.
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker_;
|
||||
|
||||
std::unique_ptr<ActiveEntryProxy> active_entry_proxy_;
|
||||
|
||||
// All nonstatic SimpleEntryImpl methods should always be called on the IO
|
||||
// thread, in all cases. |io_thread_checker_| documents and enforces this.
|
||||
base::ThreadChecker io_thread_checker_;
|
||||
|
||||
const base::WeakPtr<SimpleBackendImpl> backend_;
|
||||
SimpleFileTracker* const file_tracker_;
|
||||
const net::CacheType cache_type_;
|
||||
const scoped_refptr<base::TaskRunner> worker_pool_;
|
||||
const base::FilePath path_;
|
||||
const uint64_t entry_hash_;
|
||||
const bool use_optimistic_operations_;
|
||||
bool is_initial_stream1_read_; // used for metrics only.
|
||||
std::string key_;
|
||||
|
||||
// |last_used_|, |last_modified_| and |data_size_| are copied from the
|
||||
// synchronous entry at the completion of each item of asynchronous IO.
|
||||
// TODO(clamy): Unify last_used_ with data in the index.
|
||||
base::Time last_used_;
|
||||
base::Time last_modified_;
|
||||
int32_t data_size_[kSimpleEntryStreamCount];
|
||||
int32_t sparse_data_size_;
|
||||
|
||||
// Number of times this object has been returned from Backend::OpenEntry() and
|
||||
// Backend::CreateEntry() without subsequent Entry::Close() calls. Used to
|
||||
// notify the backend when this entry not used by any callers.
|
||||
int open_count_;
|
||||
|
||||
bool doomed_;
|
||||
|
||||
enum {
|
||||
CREATE_NORMAL,
|
||||
CREATE_OPTIMISTIC_PENDING_DOOM,
|
||||
CREATE_OPTIMISTIC_PENDING_DOOM_FOLLOWED_BY_DOOM,
|
||||
} optimistic_create_pending_doom_state_;
|
||||
|
||||
State state_;
|
||||
|
||||
// When possible, we compute a crc32, for the data in each entry as we read or
|
||||
// write. For each stream, |crc32s_[index]| is the crc32 of that stream from
|
||||
// [0 .. |crc32s_end_offset_|). If |crc32s_end_offset_[index] == 0| then the
|
||||
// value of |crc32s_[index]| is undefined.
|
||||
// Note at this can only be done in the current implementation in the case of
|
||||
// a single entry reader that reads serially through the entire file.
|
||||
// Extending this to multiple readers is possible, but isn't currently worth
|
||||
// it; see http://crbug.com/488076#c3 for details.
|
||||
int32_t crc32s_end_offset_[kSimpleEntryStreamCount];
|
||||
uint32_t crc32s_[kSimpleEntryStreamCount];
|
||||
|
||||
// If |have_written_[index]| is true, we have written to the file that
|
||||
// contains stream |index|.
|
||||
bool have_written_[kSimpleEntryStreamCount];
|
||||
|
||||
// Reflects how much CRC checking has been done with the entry. This state is
|
||||
// reported on closing each entry stream.
|
||||
CheckCrcResult crc_check_state_[kSimpleEntryStreamCount];
|
||||
|
||||
// The |synchronous_entry_| is the worker thread object that performs IO on
|
||||
// entries. It's owned by this SimpleEntryImpl whenever |executing_operation_|
|
||||
// is false (i.e. when an operation is not pending on the worker pool). When
|
||||
// an operation is being executed no one owns the synchronous entry. Therefore
|
||||
// SimpleEntryImpl should not be deleted while an operation is running as that
|
||||
// would leak the SimpleSynchronousEntry.
|
||||
SimpleSynchronousEntry* synchronous_entry_;
|
||||
|
||||
base::queue<SimpleEntryOperation> pending_operations_;
|
||||
|
||||
net::NetLogWithSource net_log_;
|
||||
|
||||
std::unique_ptr<SimpleEntryOperation> executing_operation_;
|
||||
|
||||
// Unlike other streams, stream 0 data is read from the disk when the entry is
|
||||
// opened, and then kept in memory. All read/write operations on stream 0
|
||||
// affect the |stream_0_data_| buffer. When the entry is closed,
|
||||
// |stream_0_data_| is written to the disk.
|
||||
// Stream 0 is kept in memory because it is stored in the same file as stream
|
||||
// 1 on disk, to reduce the number of file descriptors and save disk space.
|
||||
// This strategy allows stream 1 to change size easily. Since stream 0 is only
|
||||
// used to write HTTP headers, the memory consumption of keeping it in memory
|
||||
// is acceptable.
|
||||
scoped_refptr<net::GrowableIOBuffer> stream_0_data_;
|
||||
|
||||
// Sometimes stream 1 data is prefetched when stream 0 is first read.
|
||||
// If a write to the stream occurs on the entry the prefetch buffer is
|
||||
// discarded. It may also be null if it wasn't prefetched in the first place.
|
||||
scoped_refptr<net::GrowableIOBuffer> stream_1_prefetch_data_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_IMPL_H_
|
||||
341
net/disk_cache/simple/simple_entry_operation.cc
Normal file
341
net/disk_cache/simple/simple_entry_operation.cc
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_entry_operation.h"
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "net/base/io_buffer.h"
|
||||
#include "net/disk_cache/disk_cache.h"
|
||||
#include "net/disk_cache/simple/simple_entry_impl.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsReadWriteType(unsigned int type) {
|
||||
return type == SimpleEntryOperation::TYPE_READ ||
|
||||
type == SimpleEntryOperation::TYPE_WRITE ||
|
||||
type == SimpleEntryOperation::TYPE_READ_SPARSE ||
|
||||
type == SimpleEntryOperation::TYPE_WRITE_SPARSE;
|
||||
}
|
||||
|
||||
bool IsReadType(unsigned type) {
|
||||
return type == SimpleEntryOperation::TYPE_READ ||
|
||||
type == SimpleEntryOperation::TYPE_READ_SPARSE;
|
||||
}
|
||||
|
||||
bool IsSparseType(unsigned type) {
|
||||
return type == SimpleEntryOperation::TYPE_READ_SPARSE ||
|
||||
type == SimpleEntryOperation::TYPE_WRITE_SPARSE;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
SimpleEntryOperation::SimpleEntryOperation(const SimpleEntryOperation& other)
|
||||
: entry_(other.entry_.get()),
|
||||
buf_(other.buf_),
|
||||
callback_(other.callback_),
|
||||
out_entry_(other.out_entry_),
|
||||
offset_(other.offset_),
|
||||
sparse_offset_(other.sparse_offset_),
|
||||
length_(other.length_),
|
||||
out_start_(other.out_start_),
|
||||
type_(other.type_),
|
||||
have_index_(other.have_index_),
|
||||
index_(other.index_),
|
||||
truncate_(other.truncate_),
|
||||
optimistic_(other.optimistic_),
|
||||
alone_in_queue_(other.alone_in_queue_) {
|
||||
}
|
||||
|
||||
SimpleEntryOperation::~SimpleEntryOperation() = default;
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::OpenOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
bool have_index,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry) {
|
||||
return SimpleEntryOperation(entry,
|
||||
NULL,
|
||||
callback,
|
||||
out_entry,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
NULL,
|
||||
TYPE_OPEN,
|
||||
have_index,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::CreateOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
bool have_index,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry) {
|
||||
return SimpleEntryOperation(entry,
|
||||
NULL,
|
||||
callback,
|
||||
out_entry,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
NULL,
|
||||
TYPE_CREATE,
|
||||
have_index,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::CloseOperation(
|
||||
SimpleEntryImpl* entry) {
|
||||
return SimpleEntryOperation(entry,
|
||||
NULL,
|
||||
CompletionCallback(),
|
||||
NULL,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
NULL,
|
||||
TYPE_CLOSE,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::ReadOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int index,
|
||||
int offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback,
|
||||
bool alone_in_queue) {
|
||||
return SimpleEntryOperation(entry,
|
||||
buf,
|
||||
callback,
|
||||
NULL,
|
||||
offset,
|
||||
0,
|
||||
length,
|
||||
NULL,
|
||||
TYPE_READ,
|
||||
false,
|
||||
index,
|
||||
false,
|
||||
false,
|
||||
alone_in_queue);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::WriteOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int index,
|
||||
int offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
bool truncate,
|
||||
bool optimistic,
|
||||
const CompletionCallback& callback) {
|
||||
return SimpleEntryOperation(entry,
|
||||
buf,
|
||||
callback,
|
||||
NULL,
|
||||
offset,
|
||||
0,
|
||||
length,
|
||||
NULL,
|
||||
TYPE_WRITE,
|
||||
false,
|
||||
index,
|
||||
truncate,
|
||||
optimistic,
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::ReadSparseOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback) {
|
||||
return SimpleEntryOperation(entry,
|
||||
buf,
|
||||
callback,
|
||||
NULL,
|
||||
0,
|
||||
sparse_offset,
|
||||
length,
|
||||
NULL,
|
||||
TYPE_READ_SPARSE,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::WriteSparseOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback) {
|
||||
return SimpleEntryOperation(entry,
|
||||
buf,
|
||||
callback,
|
||||
NULL,
|
||||
0,
|
||||
sparse_offset,
|
||||
length,
|
||||
NULL,
|
||||
TYPE_WRITE_SPARSE,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::GetAvailableRangeOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
int64_t* out_start,
|
||||
const CompletionCallback& callback) {
|
||||
return SimpleEntryOperation(entry,
|
||||
NULL,
|
||||
callback,
|
||||
NULL,
|
||||
0,
|
||||
sparse_offset,
|
||||
length,
|
||||
out_start,
|
||||
TYPE_GET_AVAILABLE_RANGE,
|
||||
false,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleEntryOperation SimpleEntryOperation::DoomOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
const CompletionCallback& callback) {
|
||||
net::IOBuffer* const buf = NULL;
|
||||
Entry** const out_entry = NULL;
|
||||
const int offset = 0;
|
||||
const int64_t sparse_offset = 0;
|
||||
const int length = 0;
|
||||
int64_t* const out_start = NULL;
|
||||
const bool have_index = false;
|
||||
const int index = 0;
|
||||
const bool truncate = false;
|
||||
const bool optimistic = false;
|
||||
const bool alone_in_queue = false;
|
||||
return SimpleEntryOperation(entry,
|
||||
buf,
|
||||
callback,
|
||||
out_entry,
|
||||
offset,
|
||||
sparse_offset,
|
||||
length,
|
||||
out_start,
|
||||
TYPE_DOOM,
|
||||
have_index,
|
||||
index,
|
||||
truncate,
|
||||
optimistic,
|
||||
alone_in_queue);
|
||||
}
|
||||
|
||||
bool SimpleEntryOperation::ConflictsWith(
|
||||
const SimpleEntryOperation& other_op) const {
|
||||
EntryOperationType other_type = other_op.type();
|
||||
|
||||
// Non-read/write operations conflict with everything.
|
||||
if (!IsReadWriteType(type_) || !IsReadWriteType(other_type))
|
||||
return true;
|
||||
|
||||
// Reads (sparse or otherwise) conflict with nothing.
|
||||
if (IsReadType(type_) && IsReadType(other_type))
|
||||
return false;
|
||||
|
||||
// Sparse and non-sparse operations do not conflict with each other.
|
||||
if (IsSparseType(type_) != IsSparseType(other_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There must be two read/write operations, at least one must be a write, and
|
||||
// they must be either both non-sparse or both sparse. Compare the streams
|
||||
// and offsets to see whether they overlap.
|
||||
|
||||
if (IsSparseType(type_)) {
|
||||
int64_t end = sparse_offset_ + length_;
|
||||
int64_t other_op_end = other_op.sparse_offset() + other_op.length();
|
||||
return sparse_offset_ < other_op_end && other_op.sparse_offset() < end;
|
||||
}
|
||||
|
||||
if (index_ != other_op.index_)
|
||||
return false;
|
||||
int end = (type_ == TYPE_WRITE && truncate_) ? INT_MAX : offset_ + length_;
|
||||
int other_op_end = (other_op.type() == TYPE_WRITE && other_op.truncate())
|
||||
? INT_MAX
|
||||
: other_op.offset() + other_op.length();
|
||||
return offset_ < other_op_end && other_op.offset() < end;
|
||||
}
|
||||
|
||||
void SimpleEntryOperation::ReleaseReferences() {
|
||||
callback_ = CompletionCallback();
|
||||
buf_ = NULL;
|
||||
entry_ = NULL;
|
||||
}
|
||||
|
||||
SimpleEntryOperation::SimpleEntryOperation(SimpleEntryImpl* entry,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry,
|
||||
int offset,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
int64_t* out_start,
|
||||
EntryOperationType type,
|
||||
bool have_index,
|
||||
int index,
|
||||
bool truncate,
|
||||
bool optimistic,
|
||||
bool alone_in_queue)
|
||||
: entry_(entry),
|
||||
buf_(buf),
|
||||
callback_(callback),
|
||||
out_entry_(out_entry),
|
||||
offset_(offset),
|
||||
sparse_offset_(sparse_offset),
|
||||
length_(length),
|
||||
out_start_(out_start),
|
||||
type_(type),
|
||||
have_index_(have_index),
|
||||
index_(index),
|
||||
truncate_(truncate),
|
||||
optimistic_(optimistic),
|
||||
alone_in_queue_(alone_in_queue) {}
|
||||
|
||||
} // namespace disk_cache
|
||||
159
net/disk_cache/simple/simple_entry_operation.h
Normal file
159
net/disk_cache/simple/simple_entry_operation.h
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_OPERATION_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_OPERATION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "net/base/completion_callback.h"
|
||||
|
||||
namespace net {
|
||||
class IOBuffer;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class Entry;
|
||||
class SimpleEntryImpl;
|
||||
|
||||
// SimpleEntryOperation stores the information regarding operations in
|
||||
// SimpleEntryImpl, between the moment they are issued by users of the backend,
|
||||
// and the moment when they are executed.
|
||||
class SimpleEntryOperation {
|
||||
public:
|
||||
typedef net::CompletionCallback CompletionCallback;
|
||||
|
||||
enum EntryOperationType {
|
||||
TYPE_OPEN = 0,
|
||||
TYPE_CREATE = 1,
|
||||
TYPE_CLOSE = 2,
|
||||
TYPE_READ = 3,
|
||||
TYPE_WRITE = 4,
|
||||
TYPE_READ_SPARSE = 5,
|
||||
TYPE_WRITE_SPARSE = 6,
|
||||
TYPE_GET_AVAILABLE_RANGE = 7,
|
||||
TYPE_DOOM = 8,
|
||||
};
|
||||
|
||||
SimpleEntryOperation(const SimpleEntryOperation& other);
|
||||
~SimpleEntryOperation();
|
||||
|
||||
static SimpleEntryOperation OpenOperation(SimpleEntryImpl* entry,
|
||||
bool have_index,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry);
|
||||
static SimpleEntryOperation CreateOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
bool have_index,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry);
|
||||
static SimpleEntryOperation CloseOperation(SimpleEntryImpl* entry);
|
||||
static SimpleEntryOperation ReadOperation(SimpleEntryImpl* entry,
|
||||
int index,
|
||||
int offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback,
|
||||
bool alone_in_queue);
|
||||
static SimpleEntryOperation WriteOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int index,
|
||||
int offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
bool truncate,
|
||||
bool optimistic,
|
||||
const CompletionCallback& callback);
|
||||
static SimpleEntryOperation ReadSparseOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback);
|
||||
static SimpleEntryOperation WriteSparseOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback);
|
||||
static SimpleEntryOperation GetAvailableRangeOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
int64_t* out_start,
|
||||
const CompletionCallback& callback);
|
||||
static SimpleEntryOperation DoomOperation(
|
||||
SimpleEntryImpl* entry,
|
||||
const CompletionCallback& callback);
|
||||
|
||||
bool ConflictsWith(const SimpleEntryOperation& other_op) const;
|
||||
// Releases all references. After calling this operation, SimpleEntryOperation
|
||||
// will only hold POD members.
|
||||
void ReleaseReferences();
|
||||
|
||||
EntryOperationType type() const {
|
||||
return static_cast<EntryOperationType>(type_);
|
||||
}
|
||||
const CompletionCallback& callback() const { return callback_; }
|
||||
Entry** out_entry() { return out_entry_; }
|
||||
bool have_index() const { return have_index_; }
|
||||
int index() const { return index_; }
|
||||
int offset() const { return offset_; }
|
||||
int64_t sparse_offset() const { return sparse_offset_; }
|
||||
int length() const { return length_; }
|
||||
int64_t* out_start() { return out_start_; }
|
||||
net::IOBuffer* buf() { return buf_.get(); }
|
||||
bool truncate() const { return truncate_; }
|
||||
bool optimistic() const { return optimistic_; }
|
||||
bool alone_in_queue() const { return alone_in_queue_; }
|
||||
|
||||
private:
|
||||
SimpleEntryOperation(SimpleEntryImpl* entry,
|
||||
net::IOBuffer* buf,
|
||||
const CompletionCallback& callback,
|
||||
Entry** out_entry,
|
||||
int offset,
|
||||
int64_t sparse_offset,
|
||||
int length,
|
||||
int64_t* out_start,
|
||||
EntryOperationType type,
|
||||
bool have_index,
|
||||
int index,
|
||||
bool truncate,
|
||||
bool optimistic,
|
||||
bool alone_in_queue);
|
||||
|
||||
// This ensures entry will not be deleted until the operation has ran.
|
||||
scoped_refptr<SimpleEntryImpl> entry_;
|
||||
scoped_refptr<net::IOBuffer> buf_;
|
||||
CompletionCallback callback_;
|
||||
|
||||
// Used in open and create operations.
|
||||
Entry** out_entry_;
|
||||
|
||||
// Used in write and read operations.
|
||||
const int offset_;
|
||||
const int64_t sparse_offset_;
|
||||
const int length_;
|
||||
|
||||
// Used in get available range operations.
|
||||
int64_t* const out_start_;
|
||||
|
||||
const EntryOperationType type_;
|
||||
// Used in open and create operations.
|
||||
const bool have_index_;
|
||||
// Used in write and read operations.
|
||||
const unsigned int index_;
|
||||
// Used only in write operations.
|
||||
const bool truncate_;
|
||||
const bool optimistic_;
|
||||
// Used only in SimpleCache.ReadIsParallelizable histogram.
|
||||
const bool alone_in_queue_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_ENTRY_OPERATION_H_
|
||||
71
net/disk_cache/simple/simple_experiment.cc
Normal file
71
net/disk_cache/simple/simple_experiment.cc
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_experiment.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/metrics/field_trial.h"
|
||||
#include "base/metrics/field_trial_param_associator.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
const base::Feature kSimpleSizeExperiment = {"SimpleSizeExperiment",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT};
|
||||
|
||||
const char kSizeMultiplierParam[] = "SizeMultiplier";
|
||||
|
||||
namespace {
|
||||
|
||||
struct ExperimentDescription {
|
||||
disk_cache::SimpleExperimentType experiment_type;
|
||||
const base::Feature* feature;
|
||||
const char* param_name;
|
||||
};
|
||||
|
||||
// List of experimens to be checked for.
|
||||
const ExperimentDescription experiments[] = {
|
||||
{disk_cache::SimpleExperimentType::SIZE, &kSimpleSizeExperiment,
|
||||
kSizeMultiplierParam},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Returns the experiment for the given |cache_type|.
|
||||
SimpleExperiment GetSimpleExperiment(net::CacheType cache_type) {
|
||||
SimpleExperiment experiment;
|
||||
if (cache_type != net::DISK_CACHE)
|
||||
return experiment;
|
||||
|
||||
for (size_t i = 0; i < arraysize(experiments); i++) {
|
||||
if (!base::FeatureList::IsEnabled(*experiments[i].feature))
|
||||
continue;
|
||||
|
||||
base::FieldTrial* trial =
|
||||
base::FeatureList::GetFieldTrial(*experiments[i].feature);
|
||||
if (!trial)
|
||||
continue;
|
||||
|
||||
std::map<std::string, std::string> params;
|
||||
base::FieldTrialParamAssociator::GetInstance()->GetFieldTrialParams(
|
||||
trial->trial_name(), ¶ms);
|
||||
auto iter = params.find(experiments[i].param_name);
|
||||
if (iter == params.end())
|
||||
continue;
|
||||
|
||||
uint32_t param;
|
||||
if (!base::StringToUint(iter->second, ¶m))
|
||||
continue;
|
||||
|
||||
experiment.type = experiments[i].experiment_type;
|
||||
experiment.param = param;
|
||||
return experiment;
|
||||
}
|
||||
|
||||
return experiment;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
37
net/disk_cache/simple/simple_experiment.h
Normal file
37
net/disk_cache/simple/simple_experiment.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_EXPERIMENT_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_EXPERIMENT_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
NET_EXPORT_PRIVATE extern const base::Feature kSimpleSizeExperiment;
|
||||
NET_EXPORT_PRIVATE extern const char kSizeMultiplierParam[];
|
||||
|
||||
// This lists the experiment groups for SimpleCache. Only add new groups at
|
||||
// the end of the list, and always increase the number.
|
||||
enum class SimpleExperimentType : uint32_t {
|
||||
NONE = 0,
|
||||
SIZE = 1,
|
||||
EVICT_WITH_SIZE = 2, // deprecated
|
||||
};
|
||||
|
||||
struct NET_EXPORT_PRIVATE SimpleExperiment {
|
||||
SimpleExperimentType type = SimpleExperimentType::NONE;
|
||||
uint32_t param = 0;
|
||||
};
|
||||
|
||||
NET_EXPORT_PRIVATE SimpleExperiment
|
||||
GetSimpleExperiment(net::CacheType cache_type);
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_EXPERIMENT_H_
|
||||
206
net/disk_cache/simple/simple_file_tracker.cc
Normal file
206
net/disk_cache/simple/simple_file_tracker.cc
Normal file
@@ -0,0 +1,206 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_file_tracker.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "net/disk_cache/simple/simple_synchronous_entry.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
SimpleFileTracker::SimpleFileTracker() = default;
|
||||
|
||||
SimpleFileTracker::~SimpleFileTracker() {
|
||||
DCHECK(tracked_files_.empty());
|
||||
}
|
||||
|
||||
void SimpleFileTracker::Register(const SimpleSynchronousEntry* owner,
|
||||
SubFile subfile,
|
||||
std::unique_ptr<base::File> file) {
|
||||
base::AutoLock hold_lock(lock_);
|
||||
|
||||
// Make sure the list exists.
|
||||
auto insert_status = tracked_files_.insert(std::make_pair(
|
||||
owner->entry_file_key().entry_hash, std::vector<TrackedFiles>()));
|
||||
|
||||
std::vector<TrackedFiles>& candidates = insert_status.first->second;
|
||||
|
||||
// See if entry already exists, if not append.
|
||||
TrackedFiles* owners_files = nullptr;
|
||||
for (TrackedFiles& candidate : candidates) {
|
||||
if (candidate.owner == owner) {
|
||||
owners_files = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!owners_files) {
|
||||
candidates.emplace_back();
|
||||
owners_files = &candidates.back();
|
||||
owners_files->owner = owner;
|
||||
owners_files->key = owner->entry_file_key();
|
||||
}
|
||||
|
||||
int file_index = static_cast<int>(subfile);
|
||||
DCHECK_EQ(TrackedFiles::TF_NO_REGISTRATION, owners_files->state[file_index]);
|
||||
owners_files->files[file_index] = std::move(file);
|
||||
owners_files->state[file_index] = TrackedFiles::TF_REGISTERED;
|
||||
}
|
||||
|
||||
SimpleFileTracker::FileHandle SimpleFileTracker::Acquire(
|
||||
const SimpleSynchronousEntry* owner,
|
||||
SubFile subfile) {
|
||||
base::AutoLock hold_lock(lock_);
|
||||
std::vector<TrackedFiles>::iterator owners_files = Find(owner);
|
||||
int file_index = static_cast<int>(subfile);
|
||||
|
||||
DCHECK_EQ(TrackedFiles::TF_REGISTERED, owners_files->state[file_index]);
|
||||
owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED;
|
||||
return FileHandle(this, owner, subfile,
|
||||
owners_files->files[file_index].get());
|
||||
}
|
||||
|
||||
bool SimpleFileTracker::TrackedFiles::Empty() const {
|
||||
for (State s : state)
|
||||
if (s != TF_NO_REGISTRATION)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SimpleFileTracker::Release(const SimpleSynchronousEntry* owner,
|
||||
SubFile subfile) {
|
||||
std::unique_ptr<base::File> file_to_close;
|
||||
|
||||
{
|
||||
base::AutoLock hold_lock(lock_);
|
||||
std::vector<TrackedFiles>::iterator owners_files = Find(owner);
|
||||
int file_index = static_cast<int>(subfile);
|
||||
|
||||
DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED ||
|
||||
owners_files->state[file_index] ==
|
||||
TrackedFiles::TF_ACQUIRED_PENDING_CLOSE);
|
||||
|
||||
// Prepare to executed deferred close, if any.
|
||||
if (owners_files->state[file_index] ==
|
||||
TrackedFiles::TF_ACQUIRED_PENDING_CLOSE) {
|
||||
file_to_close = PrepareClose(owners_files, file_index);
|
||||
} else {
|
||||
owners_files->state[file_index] = TrackedFiles::TF_REGISTERED;
|
||||
}
|
||||
}
|
||||
|
||||
// The destructor of file_to_close will close it if needed.
|
||||
}
|
||||
|
||||
void SimpleFileTracker::Close(const SimpleSynchronousEntry* owner,
|
||||
SubFile subfile) {
|
||||
std::unique_ptr<base::File> file_to_close;
|
||||
|
||||
{
|
||||
base::AutoLock hold_lock(lock_);
|
||||
std::vector<TrackedFiles>::iterator owners_files = Find(owner);
|
||||
int file_index = static_cast<int>(subfile);
|
||||
|
||||
DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED ||
|
||||
owners_files->state[file_index] == TrackedFiles::TF_REGISTERED);
|
||||
|
||||
if (owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED) {
|
||||
// The FD is currently acquired, so we can't clean up the TrackedFiles,
|
||||
// just yet; even if this is the last close, so delay the close until it
|
||||
// gets released.
|
||||
owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED_PENDING_CLOSE;
|
||||
} else {
|
||||
file_to_close = PrepareClose(owners_files, file_index);
|
||||
}
|
||||
}
|
||||
|
||||
// The destructor of file_to_close will close it if needed. Thing to watch
|
||||
// for impl with stealing: race between bookkeeping above and actual
|
||||
// close --- the FD is still alive for it.
|
||||
}
|
||||
|
||||
bool SimpleFileTracker::IsEmptyForTesting() {
|
||||
base::AutoLock hold_lock(lock_);
|
||||
return tracked_files_.empty();
|
||||
}
|
||||
|
||||
std::vector<SimpleFileTracker::TrackedFiles>::iterator SimpleFileTracker::Find(
|
||||
const SimpleSynchronousEntry* owner) {
|
||||
auto candidates = tracked_files_.find(owner->entry_file_key().entry_hash);
|
||||
DCHECK(candidates != tracked_files_.end());
|
||||
for (std::vector<TrackedFiles>::iterator i = candidates->second.begin();
|
||||
i != candidates->second.end(); ++i) {
|
||||
if (i->owner == owner) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
LOG(DFATAL) << "SimpleFileTracker operation on non-found entry";
|
||||
return candidates->second.end();
|
||||
}
|
||||
|
||||
std::unique_ptr<base::File> SimpleFileTracker::PrepareClose(
|
||||
std::vector<TrackedFiles>::iterator owners_files,
|
||||
int file_index) {
|
||||
std::unique_ptr<base::File> file_out =
|
||||
std::move(owners_files->files[file_index]);
|
||||
owners_files->state[file_index] = TrackedFiles::TF_NO_REGISTRATION;
|
||||
if (owners_files->Empty()) {
|
||||
auto iter = tracked_files_.find(owners_files->key.entry_hash);
|
||||
iter->second.erase(owners_files);
|
||||
if (iter->second.empty())
|
||||
tracked_files_.erase(iter);
|
||||
}
|
||||
return file_out;
|
||||
}
|
||||
|
||||
SimpleFileTracker::FileHandle::FileHandle()
|
||||
: file_tracker_(nullptr), entry_(nullptr), file_(nullptr) {}
|
||||
|
||||
SimpleFileTracker::FileHandle::FileHandle(SimpleFileTracker* file_tracker,
|
||||
const SimpleSynchronousEntry* entry,
|
||||
SimpleFileTracker::SubFile subfile,
|
||||
base::File* file)
|
||||
: file_tracker_(file_tracker),
|
||||
entry_(entry),
|
||||
subfile_(subfile),
|
||||
file_(file) {}
|
||||
|
||||
SimpleFileTracker::FileHandle::FileHandle(FileHandle&& other) {
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
SimpleFileTracker::FileHandle::~FileHandle() {
|
||||
if (entry_)
|
||||
file_tracker_->Release(entry_, subfile_);
|
||||
}
|
||||
|
||||
SimpleFileTracker::FileHandle& SimpleFileTracker::FileHandle::operator=(
|
||||
FileHandle&& other) {
|
||||
file_tracker_ = other.file_tracker_;
|
||||
entry_ = other.entry_;
|
||||
subfile_ = other.subfile_;
|
||||
file_ = other.file_;
|
||||
other.file_tracker_ = nullptr;
|
||||
other.entry_ = nullptr;
|
||||
other.file_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
base::File* SimpleFileTracker::FileHandle::operator->() const {
|
||||
return file_;
|
||||
}
|
||||
|
||||
base::File* SimpleFileTracker::FileHandle::get() const {
|
||||
return file_;
|
||||
}
|
||||
|
||||
bool SimpleFileTracker::FileHandle::IsOK() const {
|
||||
return file_ && file_->IsValid();
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
183
net/disk_cache/simple/simple_file_tracker.h
Normal file
183
net/disk_cache/simple/simple_file_tracker.h
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_FILE_TRACKER_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_FILE_TRACKER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class SimpleSynchronousEntry;
|
||||
|
||||
// This keeps track of all the files SimpleCache has open, across all the
|
||||
// backend instancess, in order to prevent us from running out of file
|
||||
// descriptors.
|
||||
// TODO(morlovich): Actually implement closing and re-opening of things if we
|
||||
// run out.
|
||||
//
|
||||
// This class is thread-safe.
|
||||
class NET_EXPORT_PRIVATE SimpleFileTracker {
|
||||
public:
|
||||
enum class SubFile { FILE_0, FILE_1, FILE_SPARSE };
|
||||
|
||||
// A RAII helper that guards access to a file grabbed for use from
|
||||
// SimpleFileTracker::Acquire(). While it's still alive, if IsOK() is true,
|
||||
// then using the underlying base::File via get() or the -> operator will be
|
||||
// safe.
|
||||
//
|
||||
// This class is movable but not copyable. It should only be used from a
|
||||
// single logical sequence of execution, and should not outlive the
|
||||
// corresponding SimpleSynchronousEntry.
|
||||
class NET_EXPORT_PRIVATE FileHandle {
|
||||
public:
|
||||
FileHandle();
|
||||
FileHandle(FileHandle&& other);
|
||||
~FileHandle();
|
||||
FileHandle& operator=(FileHandle&& other);
|
||||
base::File* operator->() const;
|
||||
base::File* get() const;
|
||||
// Returns true if this handle points to a valid file. This should normally
|
||||
// be the first thing called on the object, after getting it from
|
||||
// SimpleFileTracker::Acquire.
|
||||
bool IsOK() const;
|
||||
|
||||
private:
|
||||
friend class SimpleFileTracker;
|
||||
FileHandle(SimpleFileTracker* file_tracker,
|
||||
const SimpleSynchronousEntry* entry,
|
||||
SimpleFileTracker::SubFile subfile,
|
||||
base::File* file);
|
||||
|
||||
// All the pointer fields are nullptr in the default/moved away from form.
|
||||
SimpleFileTracker* file_tracker_;
|
||||
const SimpleSynchronousEntry* entry_;
|
||||
SimpleFileTracker::SubFile subfile_;
|
||||
base::File* file_;
|
||||
DISALLOW_COPY_AND_ASSIGN(FileHandle);
|
||||
};
|
||||
|
||||
struct EntryFileKey {
|
||||
EntryFileKey() : entry_hash(0), doom_generation(0) {}
|
||||
explicit EntryFileKey(uint64_t hash)
|
||||
: entry_hash(hash), doom_generation(0) {}
|
||||
|
||||
uint64_t entry_hash;
|
||||
|
||||
// In case of a hash collision, there may be multiple SimpleEntryImpl's
|
||||
// around which have the same entry_hash but different key. In that case,
|
||||
// we doom all but the most recent one and this number will eventually be
|
||||
// used to name the files for the doomed ones.
|
||||
// 0 here means the entry is the active one, and is the only value that's
|
||||
// presently in use here.
|
||||
uint32_t doom_generation;
|
||||
};
|
||||
|
||||
SimpleFileTracker();
|
||||
~SimpleFileTracker();
|
||||
|
||||
// Established |file| as what's backing |subfile| for |owner|. This is
|
||||
// intended to be called when SimpleSynchronousEntry first sets up the file to
|
||||
// transfer its ownership to SimpleFileTracker. Any Register() call must be
|
||||
// eventually followed by a corresponding Close() call before the |owner| is
|
||||
// destroyed.
|
||||
void Register(const SimpleSynchronousEntry* owner,
|
||||
SubFile subfile,
|
||||
std::unique_ptr<base::File> file);
|
||||
|
||||
// Lends out a file to SimpleSynchronousEntry for use. SimpleFileTracker
|
||||
// will ensure that it doesn't close the file until the handle is destroyed.
|
||||
// The caller should check .IsOK() on the returned value before using it, as
|
||||
// it's possible that the file had to be closed and re-opened due to FD
|
||||
// pressure, and that open may have failed. This should not be called twice
|
||||
// with the exact same arguments until the handle returned from the previous
|
||||
// such call is destroyed.
|
||||
FileHandle Acquire(const SimpleSynchronousEntry* owner, SubFile subfile);
|
||||
|
||||
// Tells SimpleFileTracker that SimpleSynchronousEntry will not be interested
|
||||
// in the file further, so it can be closed and forgotten about. It's OK to
|
||||
// call this while a handle to the file is alive, in which case the effect
|
||||
// takes place after the handle is destroyed.
|
||||
// If Close() has been called and the handle to the file is no longer alive,
|
||||
// a new backing file can be established by calling Register() again.
|
||||
void Close(const SimpleSynchronousEntry* owner, SubFile file);
|
||||
|
||||
// Returns true if there is no in-memory state around, e.g. everything got
|
||||
// cleaned up. This is a test-only method since this object is expected to be
|
||||
// shared between multiple threads, in which case its return value may be
|
||||
// outdated the moment it's returned.
|
||||
bool IsEmptyForTesting();
|
||||
|
||||
private:
|
||||
struct TrackedFiles {
|
||||
// We can potentially run through this state machine multiple times for
|
||||
// FILE_1, as that's often missing, so SimpleSynchronousEntry can sometimes
|
||||
// close and remove the file for an empty stream, then re-open it on actual
|
||||
// data.
|
||||
enum State {
|
||||
TF_NO_REGISTRATION = 0,
|
||||
TF_REGISTERED = 1,
|
||||
TF_ACQUIRED = 2,
|
||||
TF_ACQUIRED_PENDING_CLOSE = 3,
|
||||
};
|
||||
|
||||
TrackedFiles() {
|
||||
std::fill(state, state + kSimpleEntryTotalFileCount, TF_NO_REGISTRATION);
|
||||
}
|
||||
|
||||
bool Empty() const;
|
||||
|
||||
// We use pointers to SimpleSynchronousEntry two ways:
|
||||
// 1) As opaque keys. This is handy as it avoids having to compare paths in
|
||||
// case multiple backends use the same key. Since we access the
|
||||
// bookkeeping under |lock_|
|
||||
//
|
||||
// 2) To get info on the caller of our operation.
|
||||
// Accessing |owner| from any other TrackedFiles would be unsafe (as it
|
||||
// may be doing its own thing in a different thread).
|
||||
const SimpleSynchronousEntry* owner;
|
||||
EntryFileKey key;
|
||||
|
||||
// Some of these may be !IsValid(), if they are not open.
|
||||
// Note that these are stored indirect since we hand out pointers to these,
|
||||
// and we don't want those to become invalid if some other thread appends
|
||||
// things here.
|
||||
std::unique_ptr<base::File> files[kSimpleEntryTotalFileCount];
|
||||
|
||||
State state[kSimpleEntryTotalFileCount];
|
||||
};
|
||||
|
||||
// Marks the file that was previously returned by Acquire as eligible for
|
||||
// closing again. Called by ~FileHandle.
|
||||
void Release(const SimpleSynchronousEntry* owner, SubFile subfile);
|
||||
|
||||
// |*found| will be set to whether the entry was found or not.
|
||||
std::vector<TrackedFiles>::iterator Find(const SimpleSynchronousEntry* owner);
|
||||
|
||||
// Handles state transition of closing file (when we are not deferring it),
|
||||
// and moves the file out. Note that this may invalidate |owners_files|.
|
||||
std::unique_ptr<base::File> PrepareClose(
|
||||
std::vector<TrackedFiles>::iterator owners_files,
|
||||
int file_index);
|
||||
|
||||
base::Lock lock_;
|
||||
std::unordered_map<uint64_t, std::vector<TrackedFiles>> tracked_files_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SimpleFileTracker);
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_FILE_TRACKER_H_
|
||||
76
net/disk_cache/simple/simple_histogram_enums.h
Normal file
76
net/disk_cache/simple/simple_histogram_enums.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_HISTOGRAM_ENUMS_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_HISTOGRAM_ENUMS_H_
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// Used in histograms, please only add entries at the end.
|
||||
enum SimpleReadResult {
|
||||
READ_RESULT_SUCCESS = 0,
|
||||
READ_RESULT_INVALID_ARGUMENT = 1,
|
||||
READ_RESULT_NONBLOCK_EMPTY_RETURN = 2,
|
||||
READ_RESULT_BAD_STATE = 3,
|
||||
READ_RESULT_FAST_EMPTY_RETURN = 4,
|
||||
READ_RESULT_SYNC_READ_FAILURE = 5,
|
||||
READ_RESULT_SYNC_CHECKSUM_FAILURE = 6,
|
||||
READ_RESULT_MAX = 7,
|
||||
};
|
||||
|
||||
// Used in histograms, please only add entries at the end.
|
||||
enum OpenEntryResult {
|
||||
OPEN_ENTRY_SUCCESS = 0,
|
||||
OPEN_ENTRY_PLATFORM_FILE_ERROR = 1,
|
||||
OPEN_ENTRY_CANT_READ_HEADER = 2,
|
||||
OPEN_ENTRY_BAD_MAGIC_NUMBER = 3,
|
||||
OPEN_ENTRY_BAD_VERSION = 4,
|
||||
OPEN_ENTRY_CANT_READ_KEY = 5,
|
||||
OPEN_ENTRY_KEY_MISMATCH = 6,
|
||||
OPEN_ENTRY_KEY_HASH_MISMATCH = 7,
|
||||
OPEN_ENTRY_SPARSE_OPEN_FAILED = 8,
|
||||
OPEN_ENTRY_INVALID_FILE_LENGTH = 9,
|
||||
OPEN_ENTRY_MAX = 10,
|
||||
};
|
||||
|
||||
// Used in histograms, please only add entries at the end.
|
||||
enum SyncWriteResult {
|
||||
SYNC_WRITE_RESULT_SUCCESS = 0,
|
||||
SYNC_WRITE_RESULT_PRETRUNCATE_FAILURE = 1,
|
||||
SYNC_WRITE_RESULT_WRITE_FAILURE = 2,
|
||||
SYNC_WRITE_RESULT_TRUNCATE_FAILURE = 3,
|
||||
SYNC_WRITE_RESULT_LAZY_STREAM_ENTRY_DOOMED = 4,
|
||||
SYNC_WRITE_RESULT_LAZY_CREATE_FAILURE = 5,
|
||||
SYNC_WRITE_RESULT_LAZY_INITIALIZE_FAILURE = 6,
|
||||
SYNC_WRITE_RESULT_MAX = 7,
|
||||
};
|
||||
|
||||
// Used in histograms, please only add entries at the end.
|
||||
enum CheckEOFResult {
|
||||
CHECK_EOF_RESULT_SUCCESS = 0,
|
||||
CHECK_EOF_RESULT_READ_FAILURE = 1,
|
||||
CHECK_EOF_RESULT_MAGIC_NUMBER_MISMATCH = 2,
|
||||
CHECK_EOF_RESULT_CRC_MISMATCH = 3,
|
||||
CHECK_EOF_RESULT_KEY_SHA256_MISMATCH = 4,
|
||||
CHECK_EOF_RESULT_MAX = 5,
|
||||
};
|
||||
|
||||
// Used in histograms, please only add entries at the end.
|
||||
enum CloseResult {
|
||||
CLOSE_RESULT_SUCCESS = 0,
|
||||
CLOSE_RESULT_WRITE_FAILURE = 1,
|
||||
CLOSE_RESULT_MAX = 2,
|
||||
};
|
||||
|
||||
// Used in histograms, please only add entries at the end.
|
||||
enum class KeySHA256Result {
|
||||
NOT_PRESENT = 0,
|
||||
MATCHED = 1,
|
||||
NO_MATCH = 2,
|
||||
MAX = 3
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_HISTOGRAM_ENUMS_H_
|
||||
45
net/disk_cache/simple/simple_histogram_macros.h
Normal file
45
net/disk_cache/simple/simple_histogram_macros.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_HISTOGRAM_MACROS_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_HISTOGRAM_MACROS_H_
|
||||
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/metrics/sparse_histogram.h"
|
||||
#include "net/base/cache_type.h"
|
||||
|
||||
// This file contains macros used to report histograms. The main issue is that
|
||||
// we want to have separate histograms for each type of cache (app, http, and
|
||||
// media), while making it easy to report histograms and have all names
|
||||
// precomputed.
|
||||
|
||||
#define SIMPLE_CACHE_THUNK(uma_type, args) UMA_HISTOGRAM_##uma_type args
|
||||
|
||||
// TODO(pasko): add histograms for shader cache as soon as it becomes possible
|
||||
// for a user to get shader cache with the |SimpleBackendImpl| without altering
|
||||
// any flags.
|
||||
#define SIMPLE_CACHE_UMA(uma_type, uma_name, cache_type, ...) \
|
||||
do { \
|
||||
switch (cache_type) { \
|
||||
case net::DISK_CACHE: \
|
||||
SIMPLE_CACHE_THUNK( \
|
||||
uma_type, ("SimpleCache.Http." uma_name, ##__VA_ARGS__)); \
|
||||
break; \
|
||||
case net::APP_CACHE: \
|
||||
SIMPLE_CACHE_THUNK( \
|
||||
uma_type, ("SimpleCache.App." uma_name, ##__VA_ARGS__)); \
|
||||
break; \
|
||||
case net::MEDIA_CACHE: \
|
||||
SIMPLE_CACHE_THUNK( \
|
||||
uma_type, ("SimpleCache.Media." uma_name, ##__VA_ARGS__)); \
|
||||
break; \
|
||||
case net::SHADER_CACHE: \
|
||||
break; \
|
||||
default: \
|
||||
NOTREACHED(); \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_HISTOGRAM_MACROS_H_
|
||||
576
net/disk_cache/simple/simple_index.cc
Normal file
576
net/disk_cache/simple/simple_index.cc
Normal file
@@ -0,0 +1,576 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_index.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/bind_helpers.h"
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#include "base/metrics/field_trial.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/pickle.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_tokenizer.h"
|
||||
#include "base/task_runner.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/trace_event/memory_usage_estimator.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/backend_cleanup_tracker.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
#include "net/disk_cache/simple/simple_experiment.h"
|
||||
#include "net/disk_cache/simple/simple_histogram_macros.h"
|
||||
#include "net/disk_cache/simple/simple_index_delegate.h"
|
||||
#include "net/disk_cache/simple/simple_index_file.h"
|
||||
#include "net/disk_cache/simple/simple_synchronous_entry.h"
|
||||
#include "net/disk_cache/simple/simple_util.h"
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// How many milliseconds we delay writing the index to disk since the last cache
|
||||
// operation has happened.
|
||||
const int kWriteToDiskDelayMSecs = 20000;
|
||||
const int kWriteToDiskOnBackgroundDelayMSecs = 100;
|
||||
|
||||
// Divides the cache space into this amount of parts to evict when only one part
|
||||
// is left.
|
||||
const uint32_t kEvictionMarginDivisor = 20;
|
||||
|
||||
const uint32_t kBytesInKb = 1024;
|
||||
|
||||
// This is added to the size of each entry before using the size
|
||||
// to determine which entries to evict first. It's basically an
|
||||
// estimate of the filesystem overhead, but it also serves to flatten
|
||||
// the curve so that 1-byte entries and 2-byte entries are basically
|
||||
// treated the same.
|
||||
static const int kEstimatedEntryOverhead = 512;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
const base::Feature kSimpleCacheEvictionWithSize = {
|
||||
"SimpleCacheEvictionWithSize", base::FEATURE_ENABLED_BY_DEFAULT};
|
||||
|
||||
EntryMetadata::EntryMetadata()
|
||||
: last_used_time_seconds_since_epoch_(0),
|
||||
entry_size_256b_chunks_(0),
|
||||
in_memory_data_(0) {}
|
||||
|
||||
EntryMetadata::EntryMetadata(base::Time last_used_time,
|
||||
base::StrictNumeric<uint32_t> entry_size)
|
||||
: last_used_time_seconds_since_epoch_(0),
|
||||
entry_size_256b_chunks_(0),
|
||||
in_memory_data_(0) {
|
||||
SetEntrySize(entry_size); // to round/pack properly.
|
||||
SetLastUsedTime(last_used_time);
|
||||
}
|
||||
|
||||
base::Time EntryMetadata::GetLastUsedTime() const {
|
||||
// Preserve nullity.
|
||||
if (last_used_time_seconds_since_epoch_ == 0)
|
||||
return base::Time();
|
||||
|
||||
return base::Time::UnixEpoch() +
|
||||
base::TimeDelta::FromSeconds(last_used_time_seconds_since_epoch_);
|
||||
}
|
||||
|
||||
void EntryMetadata::SetLastUsedTime(const base::Time& last_used_time) {
|
||||
// Preserve nullity.
|
||||
if (last_used_time.is_null()) {
|
||||
last_used_time_seconds_since_epoch_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
last_used_time_seconds_since_epoch_ = base::saturated_cast<uint32_t>(
|
||||
(last_used_time - base::Time::UnixEpoch()).InSeconds());
|
||||
// Avoid accidental nullity.
|
||||
if (last_used_time_seconds_since_epoch_ == 0)
|
||||
last_used_time_seconds_since_epoch_ = 1;
|
||||
}
|
||||
|
||||
uint32_t EntryMetadata::GetEntrySize() const {
|
||||
return entry_size_256b_chunks_ << 8;
|
||||
}
|
||||
|
||||
void EntryMetadata::SetEntrySize(base::StrictNumeric<uint32_t> entry_size) {
|
||||
// This should not overflow since we limit entries to 1/8th of the cache.
|
||||
entry_size_256b_chunks_ = (static_cast<uint32_t>(entry_size) + 255) >> 8;
|
||||
}
|
||||
|
||||
void EntryMetadata::Serialize(base::Pickle* pickle) const {
|
||||
DCHECK(pickle);
|
||||
int64_t internal_last_used_time = GetLastUsedTime().ToInternalValue();
|
||||
// If you modify the size of the size of the pickle, be sure to update
|
||||
// kOnDiskSizeBytes.
|
||||
uint32_t packed_entry_info = (entry_size_256b_chunks_ << 8) | in_memory_data_;
|
||||
pickle->WriteInt64(internal_last_used_time);
|
||||
pickle->WriteUInt64(packed_entry_info);
|
||||
}
|
||||
|
||||
bool EntryMetadata::Deserialize(base::PickleIterator* it,
|
||||
bool has_entry_in_memory_data) {
|
||||
DCHECK(it);
|
||||
int64_t tmp_last_used_time;
|
||||
uint64_t tmp_entry_size;
|
||||
if (!it->ReadInt64(&tmp_last_used_time) || !it->ReadUInt64(&tmp_entry_size) ||
|
||||
tmp_entry_size > std::numeric_limits<uint32_t>::max())
|
||||
return false;
|
||||
SetLastUsedTime(base::Time::FromInternalValue(tmp_last_used_time));
|
||||
if (has_entry_in_memory_data) {
|
||||
// tmp_entry_size actually packs entry_size_256b_chunks_ and
|
||||
// in_memory_data_.
|
||||
SetEntrySize(static_cast<uint32_t>(tmp_entry_size & 0xFFFFFF00));
|
||||
SetInMemoryData(static_cast<uint8_t>(tmp_entry_size & 0xFF));
|
||||
} else {
|
||||
SetEntrySize(static_cast<uint32_t>(tmp_entry_size));
|
||||
SetInMemoryData(0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SimpleIndex::SimpleIndex(
|
||||
const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker,
|
||||
SimpleIndexDelegate* delegate,
|
||||
net::CacheType cache_type,
|
||||
std::unique_ptr<SimpleIndexFile> index_file)
|
||||
: cleanup_tracker_(std::move(cleanup_tracker)),
|
||||
delegate_(delegate),
|
||||
cache_type_(cache_type),
|
||||
cache_size_(0),
|
||||
max_size_(0),
|
||||
high_watermark_(0),
|
||||
low_watermark_(0),
|
||||
eviction_in_progress_(false),
|
||||
initialized_(false),
|
||||
init_method_(INITIALIZE_METHOD_MAX),
|
||||
index_file_(std::move(index_file)),
|
||||
io_thread_(io_thread),
|
||||
// Creating the callback once so it is reused every time
|
||||
// write_to_disk_timer_.Start() is called.
|
||||
write_to_disk_cb_(base::Bind(&SimpleIndex::WriteToDisk,
|
||||
AsWeakPtr(),
|
||||
INDEX_WRITE_REASON_IDLE)),
|
||||
app_on_background_(false) {}
|
||||
|
||||
SimpleIndex::~SimpleIndex() {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
|
||||
// Fail all callbacks waiting for the index to come up.
|
||||
for (CallbackList::iterator it = to_run_when_initialized_.begin(),
|
||||
end = to_run_when_initialized_.end(); it != end; ++it) {
|
||||
it->Run(net::ERR_ABORTED);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleIndex::Initialize(base::Time cache_mtime) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
if (base::android::IsVMInitialized()) {
|
||||
app_status_listener_.reset(new base::android::ApplicationStatusListener(
|
||||
base::Bind(&SimpleIndex::OnApplicationStateChange, AsWeakPtr())));
|
||||
}
|
||||
#endif
|
||||
|
||||
SimpleIndexLoadResult* load_result = new SimpleIndexLoadResult();
|
||||
std::unique_ptr<SimpleIndexLoadResult> load_result_scoped(load_result);
|
||||
base::Closure reply = base::Bind(
|
||||
&SimpleIndex::MergeInitializingSet,
|
||||
AsWeakPtr(),
|
||||
base::Passed(&load_result_scoped));
|
||||
index_file_->LoadIndexEntries(cache_mtime, reply, load_result);
|
||||
}
|
||||
|
||||
void SimpleIndex::SetMaxSize(uint64_t max_bytes) {
|
||||
// Zero size means use the default.
|
||||
if (max_bytes) {
|
||||
max_size_ = max_bytes;
|
||||
high_watermark_ = max_size_ - max_size_ / kEvictionMarginDivisor;
|
||||
low_watermark_ = max_size_ - 2 * (max_size_ / kEvictionMarginDivisor);
|
||||
}
|
||||
}
|
||||
|
||||
int SimpleIndex::ExecuteWhenReady(const net::CompletionCallback& task) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
if (initialized_)
|
||||
io_thread_->PostTask(FROM_HERE, base::Bind(task, net::OK));
|
||||
else
|
||||
to_run_when_initialized_.push_back(task);
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
std::unique_ptr<SimpleIndex::HashList> SimpleIndex::GetEntriesBetween(
|
||||
base::Time initial_time,
|
||||
base::Time end_time) {
|
||||
DCHECK_EQ(true, initialized_);
|
||||
|
||||
if (!initial_time.is_null())
|
||||
initial_time -= EntryMetadata::GetLowerEpsilonForTimeComparisons();
|
||||
if (end_time.is_null())
|
||||
end_time = base::Time::Max();
|
||||
else
|
||||
end_time += EntryMetadata::GetUpperEpsilonForTimeComparisons();
|
||||
DCHECK(end_time >= initial_time);
|
||||
|
||||
std::unique_ptr<HashList> ret_hashes(new HashList());
|
||||
for (const auto& entry : entries_set_) {
|
||||
const EntryMetadata& metadata = entry.second;
|
||||
base::Time entry_time = metadata.GetLastUsedTime();
|
||||
if (initial_time <= entry_time && entry_time < end_time)
|
||||
ret_hashes->push_back(entry.first);
|
||||
}
|
||||
return ret_hashes;
|
||||
}
|
||||
|
||||
std::unique_ptr<SimpleIndex::HashList> SimpleIndex::GetAllHashes() {
|
||||
return GetEntriesBetween(base::Time(), base::Time());
|
||||
}
|
||||
|
||||
int32_t SimpleIndex::GetEntryCount() const {
|
||||
// TODO(pasko): return a meaningful initial estimate before initialized.
|
||||
return entries_set_.size();
|
||||
}
|
||||
|
||||
uint64_t SimpleIndex::GetCacheSize() const {
|
||||
DCHECK(initialized_);
|
||||
return cache_size_;
|
||||
}
|
||||
|
||||
uint64_t SimpleIndex::GetCacheSizeBetween(base::Time initial_time,
|
||||
base::Time end_time) const {
|
||||
DCHECK_EQ(true, initialized_);
|
||||
|
||||
if (!initial_time.is_null())
|
||||
initial_time -= EntryMetadata::GetLowerEpsilonForTimeComparisons();
|
||||
if (end_time.is_null())
|
||||
end_time = base::Time::Max();
|
||||
else
|
||||
end_time += EntryMetadata::GetUpperEpsilonForTimeComparisons();
|
||||
|
||||
DCHECK(end_time >= initial_time);
|
||||
uint64_t size = 0;
|
||||
for (const auto& entry : entries_set_) {
|
||||
const EntryMetadata& metadata = entry.second;
|
||||
base::Time entry_time = metadata.GetLastUsedTime();
|
||||
if (initial_time <= entry_time && entry_time < end_time)
|
||||
size += metadata.GetEntrySize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t SimpleIndex::EstimateMemoryUsage() const {
|
||||
return base::trace_event::EstimateMemoryUsage(entries_set_) +
|
||||
base::trace_event::EstimateMemoryUsage(removed_entries_);
|
||||
}
|
||||
|
||||
void SimpleIndex::Insert(uint64_t entry_hash) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
// Upon insert we don't know yet the size of the entry.
|
||||
// It will be updated later when the SimpleEntryImpl finishes opening or
|
||||
// creating the new entry, and then UpdateEntrySize will be called.
|
||||
InsertInEntrySet(entry_hash, EntryMetadata(base::Time::Now(), 0u),
|
||||
&entries_set_);
|
||||
if (!initialized_)
|
||||
removed_entries_.erase(entry_hash);
|
||||
PostponeWritingToDisk();
|
||||
}
|
||||
|
||||
void SimpleIndex::Remove(uint64_t entry_hash) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
EntrySet::iterator it = entries_set_.find(entry_hash);
|
||||
if (it != entries_set_.end()) {
|
||||
UpdateEntryIteratorSize(&it, 0u);
|
||||
entries_set_.erase(it);
|
||||
}
|
||||
|
||||
if (!initialized_)
|
||||
removed_entries_.insert(entry_hash);
|
||||
PostponeWritingToDisk();
|
||||
}
|
||||
|
||||
bool SimpleIndex::Has(uint64_t hash) const {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
// If not initialized, always return true, forcing it to go to the disk.
|
||||
return !initialized_ || entries_set_.count(hash) > 0;
|
||||
}
|
||||
|
||||
uint8_t SimpleIndex::GetEntryInMemoryData(uint64_t entry_hash) const {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
EntrySet::const_iterator it = entries_set_.find(entry_hash);
|
||||
if (it == entries_set_.end())
|
||||
return 0;
|
||||
return it->second.GetInMemoryData();
|
||||
}
|
||||
|
||||
void SimpleIndex::SetEntryInMemoryData(uint64_t entry_hash, uint8_t value) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
EntrySet::iterator it = entries_set_.find(entry_hash);
|
||||
if (it == entries_set_.end())
|
||||
return;
|
||||
return it->second.SetInMemoryData(value);
|
||||
}
|
||||
|
||||
bool SimpleIndex::UseIfExists(uint64_t entry_hash) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
// Always update the last used time, even if it is during initialization.
|
||||
// It will be merged later.
|
||||
EntrySet::iterator it = entries_set_.find(entry_hash);
|
||||
if (it == entries_set_.end())
|
||||
// If not initialized, always return true, forcing it to go to the disk.
|
||||
return !initialized_;
|
||||
it->second.SetLastUsedTime(base::Time::Now());
|
||||
PostponeWritingToDisk();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SimpleIndex::StartEvictionIfNeeded() {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
if (eviction_in_progress_ || cache_size_ <= high_watermark_)
|
||||
return;
|
||||
// Take all live key hashes from the index and sort them by time.
|
||||
eviction_in_progress_ = true;
|
||||
eviction_start_time_ = base::TimeTicks::Now();
|
||||
SIMPLE_CACHE_UMA(
|
||||
MEMORY_KB, "Eviction.CacheSizeOnStart2", cache_type_,
|
||||
static_cast<base::HistogramBase::Sample>(cache_size_ / kBytesInKb));
|
||||
SIMPLE_CACHE_UMA(
|
||||
MEMORY_KB, "Eviction.MaxCacheSizeOnStart2", cache_type_,
|
||||
static_cast<base::HistogramBase::Sample>(max_size_ / kBytesInKb));
|
||||
|
||||
// Flatten for sorting.
|
||||
std::vector<std::pair<uint64_t, const EntrySet::value_type*>> entries;
|
||||
entries.reserve(entries_set_.size());
|
||||
uint32_t now = (base::Time::Now() - base::Time::UnixEpoch()).InSeconds();
|
||||
bool use_size = base::FeatureList::IsEnabled(kSimpleCacheEvictionWithSize);
|
||||
for (EntrySet::const_iterator i = entries_set_.begin();
|
||||
i != entries_set_.end(); ++i) {
|
||||
uint64_t sort_value = now - i->second.RawTimeForSorting();
|
||||
if (use_size) {
|
||||
// Will not overflow since we're multiplying two 32-bit values and storing
|
||||
// them in a 64-bit variable.
|
||||
sort_value *= i->second.GetEntrySize() + kEstimatedEntryOverhead;
|
||||
}
|
||||
// Subtract so we don't need a custom comparator.
|
||||
entries.emplace_back(std::numeric_limits<uint64_t>::max() - sort_value,
|
||||
&*i);
|
||||
}
|
||||
|
||||
uint64_t evicted_so_far_size = 0;
|
||||
const uint64_t amount_to_evict = cache_size_ - low_watermark_;
|
||||
std::vector<uint64_t> entry_hashes;
|
||||
std::sort(entries.begin(), entries.end());
|
||||
for (const auto& score_metadata_pair : entries) {
|
||||
if (evicted_so_far_size >= amount_to_evict)
|
||||
break;
|
||||
evicted_so_far_size += score_metadata_pair.second->second.GetEntrySize();
|
||||
entry_hashes.push_back(score_metadata_pair.second->first);
|
||||
}
|
||||
|
||||
SIMPLE_CACHE_UMA(COUNTS_1M,
|
||||
"Eviction.EntryCount", cache_type_, entry_hashes.size());
|
||||
SIMPLE_CACHE_UMA(TIMES,
|
||||
"Eviction.TimeToSelectEntries", cache_type_,
|
||||
base::TimeTicks::Now() - eviction_start_time_);
|
||||
SIMPLE_CACHE_UMA(
|
||||
MEMORY_KB, "Eviction.SizeOfEvicted2", cache_type_,
|
||||
static_cast<base::HistogramBase::Sample>(
|
||||
evicted_so_far_size / kBytesInKb));
|
||||
|
||||
delegate_->DoomEntries(&entry_hashes, base::Bind(&SimpleIndex::EvictionDone,
|
||||
AsWeakPtr()));
|
||||
}
|
||||
|
||||
bool SimpleIndex::UpdateEntrySize(uint64_t entry_hash,
|
||||
base::StrictNumeric<uint32_t> entry_size) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
EntrySet::iterator it = entries_set_.find(entry_hash);
|
||||
if (it == entries_set_.end())
|
||||
return false;
|
||||
|
||||
UpdateEntryIteratorSize(&it, entry_size);
|
||||
PostponeWritingToDisk();
|
||||
StartEvictionIfNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SimpleIndex::EvictionDone(int result) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
|
||||
// Ignore the result of eviction. We did our best.
|
||||
eviction_in_progress_ = false;
|
||||
SIMPLE_CACHE_UMA(BOOLEAN, "Eviction.Result", cache_type_, result == net::OK);
|
||||
SIMPLE_CACHE_UMA(TIMES,
|
||||
"Eviction.TimeToDone", cache_type_,
|
||||
base::TimeTicks::Now() - eviction_start_time_);
|
||||
SIMPLE_CACHE_UMA(
|
||||
MEMORY_KB, "Eviction.SizeWhenDone2", cache_type_,
|
||||
static_cast<base::HistogramBase::Sample>(cache_size_ / kBytesInKb));
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleIndex::InsertInEntrySet(
|
||||
uint64_t entry_hash,
|
||||
const disk_cache::EntryMetadata& entry_metadata,
|
||||
EntrySet* entry_set) {
|
||||
DCHECK(entry_set);
|
||||
entry_set->insert(std::make_pair(entry_hash, entry_metadata));
|
||||
}
|
||||
|
||||
void SimpleIndex::InsertEntryForTesting(uint64_t entry_hash,
|
||||
const EntryMetadata& entry_metadata) {
|
||||
DCHECK(entries_set_.find(entry_hash) == entries_set_.end());
|
||||
InsertInEntrySet(entry_hash, entry_metadata, &entries_set_);
|
||||
cache_size_ += entry_metadata.GetEntrySize();
|
||||
}
|
||||
|
||||
void SimpleIndex::PostponeWritingToDisk() {
|
||||
if (!initialized_)
|
||||
return;
|
||||
const int delay = app_on_background_ ? kWriteToDiskOnBackgroundDelayMSecs
|
||||
: kWriteToDiskDelayMSecs;
|
||||
// If the timer is already active, Start() will just Reset it, postponing it.
|
||||
write_to_disk_timer_.Start(
|
||||
FROM_HERE, base::TimeDelta::FromMilliseconds(delay), write_to_disk_cb_);
|
||||
}
|
||||
|
||||
void SimpleIndex::UpdateEntryIteratorSize(
|
||||
EntrySet::iterator* it,
|
||||
base::StrictNumeric<uint32_t> entry_size) {
|
||||
// Update the total cache size with the new entry size.
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
DCHECK_GE(cache_size_, (*it)->second.GetEntrySize());
|
||||
cache_size_ -= (*it)->second.GetEntrySize();
|
||||
(*it)->second.SetEntrySize(entry_size);
|
||||
// We use GetEntrySize to get consistent rounding.
|
||||
cache_size_ += (*it)->second.GetEntrySize();
|
||||
}
|
||||
|
||||
void SimpleIndex::MergeInitializingSet(
|
||||
std::unique_ptr<SimpleIndexLoadResult> load_result) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
|
||||
EntrySet* index_file_entries = &load_result->entries;
|
||||
|
||||
for (std::unordered_set<uint64_t>::const_iterator it =
|
||||
removed_entries_.begin();
|
||||
it != removed_entries_.end(); ++it) {
|
||||
index_file_entries->erase(*it);
|
||||
}
|
||||
removed_entries_.clear();
|
||||
|
||||
for (EntrySet::const_iterator it = entries_set_.begin();
|
||||
it != entries_set_.end(); ++it) {
|
||||
const uint64_t entry_hash = it->first;
|
||||
std::pair<EntrySet::iterator, bool> insert_result =
|
||||
index_file_entries->insert(EntrySet::value_type(entry_hash,
|
||||
EntryMetadata()));
|
||||
EntrySet::iterator& possibly_inserted_entry = insert_result.first;
|
||||
possibly_inserted_entry->second = it->second;
|
||||
}
|
||||
|
||||
uint64_t merged_cache_size = 0;
|
||||
for (EntrySet::iterator it = index_file_entries->begin();
|
||||
it != index_file_entries->end(); ++it) {
|
||||
merged_cache_size += it->second.GetEntrySize();
|
||||
}
|
||||
|
||||
entries_set_.swap(*index_file_entries);
|
||||
cache_size_ = merged_cache_size;
|
||||
initialized_ = true;
|
||||
init_method_ = load_result->init_method;
|
||||
|
||||
// The actual IO is asynchronous, so calling WriteToDisk() shouldn't slow the
|
||||
// merge down much.
|
||||
if (load_result->flush_required)
|
||||
WriteToDisk(INDEX_WRITE_REASON_STARTUP_MERGE);
|
||||
|
||||
SIMPLE_CACHE_UMA(CUSTOM_COUNTS,
|
||||
"IndexInitializationWaiters", cache_type_,
|
||||
to_run_when_initialized_.size(), 0, 100, 20);
|
||||
SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "IndexNumEntriesOnInit", cache_type_,
|
||||
entries_set_.size(), 0, 100000, 50);
|
||||
SIMPLE_CACHE_UMA(
|
||||
MEMORY_KB, "CacheSizeOnInit", cache_type_,
|
||||
static_cast<base::HistogramBase::Sample>(cache_size_ / kBytesInKb));
|
||||
SIMPLE_CACHE_UMA(
|
||||
MEMORY_KB, "MaxCacheSizeOnInit", cache_type_,
|
||||
static_cast<base::HistogramBase::Sample>(max_size_ / kBytesInKb));
|
||||
if (max_size_ > 0) {
|
||||
SIMPLE_CACHE_UMA(PERCENTAGE, "PercentFullOnInit", cache_type_,
|
||||
static_cast<base::HistogramBase::Sample>(
|
||||
(cache_size_ * 100) / max_size_));
|
||||
}
|
||||
|
||||
// Run all callbacks waiting for the index to come up.
|
||||
for (CallbackList::iterator it = to_run_when_initialized_.begin(),
|
||||
end = to_run_when_initialized_.end(); it != end; ++it) {
|
||||
io_thread_->PostTask(FROM_HERE, base::Bind((*it), net::OK));
|
||||
}
|
||||
to_run_when_initialized_.clear();
|
||||
}
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
void SimpleIndex::OnApplicationStateChange(
|
||||
base::android::ApplicationState state) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
// For more info about android activities, see:
|
||||
// developer.android.com/training/basics/activity-lifecycle/pausing.html
|
||||
if (state == base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES) {
|
||||
app_on_background_ = false;
|
||||
} else if (state ==
|
||||
base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES) {
|
||||
app_on_background_ = true;
|
||||
WriteToDisk(INDEX_WRITE_REASON_ANDROID_STOPPED);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void SimpleIndex::WriteToDisk(IndexWriteToDiskReason reason) {
|
||||
DCHECK(io_thread_checker_.CalledOnValidThread());
|
||||
if (!initialized_)
|
||||
return;
|
||||
SIMPLE_CACHE_UMA(CUSTOM_COUNTS,
|
||||
"IndexNumEntriesOnWrite", cache_type_,
|
||||
entries_set_.size(), 0, 100000, 50);
|
||||
const base::TimeTicks start = base::TimeTicks::Now();
|
||||
if (!last_write_to_disk_.is_null()) {
|
||||
if (app_on_background_) {
|
||||
SIMPLE_CACHE_UMA(MEDIUM_TIMES,
|
||||
"IndexWriteInterval.Background", cache_type_,
|
||||
start - last_write_to_disk_);
|
||||
} else {
|
||||
SIMPLE_CACHE_UMA(MEDIUM_TIMES,
|
||||
"IndexWriteInterval.Foreground", cache_type_,
|
||||
start - last_write_to_disk_);
|
||||
}
|
||||
}
|
||||
last_write_to_disk_ = start;
|
||||
|
||||
base::Closure after_write;
|
||||
if (cleanup_tracker_) {
|
||||
// Make anyone synchronizing with our cleanup wait for the index to be
|
||||
// written back.
|
||||
after_write = base::Bind([](scoped_refptr<BackendCleanupTracker>) {},
|
||||
cleanup_tracker_);
|
||||
}
|
||||
|
||||
index_file_->WriteToDisk(reason, entries_set_, cache_size_, start,
|
||||
app_on_background_, after_write);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
268
net/disk_cache/simple/simple_index.h
Normal file
268
net/disk_cache/simple/simple_index.h
Normal file
@@ -0,0 +1,268 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/timer/timer.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/completion_callback.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#include "base/android/application_status_listener.h"
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
class Pickle;
|
||||
class PickleIterator;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class BackendCleanupTracker;
|
||||
class SimpleIndexDelegate;
|
||||
class SimpleIndexFile;
|
||||
struct SimpleIndexLoadResult;
|
||||
|
||||
NET_EXPORT_PRIVATE extern const base::Feature kSimpleCacheEvictionWithSize;
|
||||
|
||||
class NET_EXPORT_PRIVATE EntryMetadata {
|
||||
public:
|
||||
EntryMetadata();
|
||||
EntryMetadata(base::Time last_used_time,
|
||||
base::StrictNumeric<uint32_t> entry_size);
|
||||
|
||||
base::Time GetLastUsedTime() const;
|
||||
void SetLastUsedTime(const base::Time& last_used_time);
|
||||
|
||||
uint32_t RawTimeForSorting() const {
|
||||
return last_used_time_seconds_since_epoch_;
|
||||
}
|
||||
|
||||
uint32_t GetEntrySize() const;
|
||||
void SetEntrySize(base::StrictNumeric<uint32_t> entry_size);
|
||||
|
||||
uint8_t GetInMemoryData() const { return in_memory_data_; }
|
||||
void SetInMemoryData(uint8_t val) { in_memory_data_ = val; }
|
||||
|
||||
// Serialize the data into the provided pickle.
|
||||
void Serialize(base::Pickle* pickle) const;
|
||||
bool Deserialize(base::PickleIterator* it, bool has_entry_in_memory_data);
|
||||
|
||||
static base::TimeDelta GetLowerEpsilonForTimeComparisons() {
|
||||
return base::TimeDelta::FromSeconds(1);
|
||||
}
|
||||
static base::TimeDelta GetUpperEpsilonForTimeComparisons() {
|
||||
return base::TimeDelta();
|
||||
}
|
||||
|
||||
static const int kOnDiskSizeBytes = 16;
|
||||
|
||||
private:
|
||||
friend class SimpleIndexFileTest;
|
||||
|
||||
// There are tens of thousands of instances of EntryMetadata in memory, so the
|
||||
// size of each entry matters. Even when the values used to set these members
|
||||
// are originally calculated as >32-bit types, the actual necessary size for
|
||||
// each shouldn't exceed 32 bits, so we use 32-bit types here.
|
||||
uint32_t last_used_time_seconds_since_epoch_;
|
||||
uint32_t entry_size_256b_chunks_ : 24; // in 256-byte blocks, rounded up.
|
||||
uint32_t in_memory_data_ : 8;
|
||||
};
|
||||
static_assert(sizeof(EntryMetadata) == 8, "incorrect metadata size");
|
||||
|
||||
// This class is not Thread-safe.
|
||||
class NET_EXPORT_PRIVATE SimpleIndex
|
||||
: public base::SupportsWeakPtr<SimpleIndex> {
|
||||
public:
|
||||
// Used in histograms. Please only add entries at the end.
|
||||
enum IndexInitMethod {
|
||||
INITIALIZE_METHOD_RECOVERED = 0,
|
||||
INITIALIZE_METHOD_LOADED = 1,
|
||||
INITIALIZE_METHOD_NEWCACHE = 2,
|
||||
INITIALIZE_METHOD_MAX = 3,
|
||||
};
|
||||
// Used in histograms. Please only add entries at the end.
|
||||
enum IndexWriteToDiskReason {
|
||||
INDEX_WRITE_REASON_SHUTDOWN = 0,
|
||||
INDEX_WRITE_REASON_STARTUP_MERGE = 1,
|
||||
INDEX_WRITE_REASON_IDLE = 2,
|
||||
INDEX_WRITE_REASON_ANDROID_STOPPED = 3,
|
||||
INDEX_WRITE_REASON_MAX = 4,
|
||||
};
|
||||
|
||||
typedef std::vector<uint64_t> HashList;
|
||||
|
||||
SimpleIndex(const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker,
|
||||
SimpleIndexDelegate* delegate,
|
||||
net::CacheType cache_type,
|
||||
std::unique_ptr<SimpleIndexFile> simple_index_file);
|
||||
|
||||
virtual ~SimpleIndex();
|
||||
|
||||
void Initialize(base::Time cache_mtime);
|
||||
|
||||
void SetMaxSize(uint64_t max_bytes);
|
||||
uint64_t max_size() const { return max_size_; }
|
||||
|
||||
void Insert(uint64_t entry_hash);
|
||||
void Remove(uint64_t entry_hash);
|
||||
|
||||
// Check whether the index has the entry given the hash of its key.
|
||||
bool Has(uint64_t entry_hash) const;
|
||||
|
||||
// Update the last used time of the entry with the given key and return true
|
||||
// iff the entry exist in the index.
|
||||
bool UseIfExists(uint64_t entry_hash);
|
||||
|
||||
uint8_t GetEntryInMemoryData(uint64_t entry_hash) const;
|
||||
void SetEntryInMemoryData(uint64_t entry_hash, uint8_t value);
|
||||
|
||||
void WriteToDisk(IndexWriteToDiskReason reason);
|
||||
|
||||
// Update the size (in bytes) of an entry, in the metadata stored in the
|
||||
// index. This should be the total disk-file size including all streams of the
|
||||
// entry.
|
||||
bool UpdateEntrySize(uint64_t entry_hash,
|
||||
base::StrictNumeric<uint32_t> entry_size);
|
||||
|
||||
using EntrySet = std::unordered_map<uint64_t, EntryMetadata>;
|
||||
|
||||
static void InsertInEntrySet(uint64_t entry_hash,
|
||||
const EntryMetadata& entry_metadata,
|
||||
EntrySet* entry_set);
|
||||
|
||||
// For use in tests only. Updates cache_size_, but will not start evictions
|
||||
// or adjust index writing time. Requires entry to not already be in the set.
|
||||
void InsertEntryForTesting(uint64_t entry_hash,
|
||||
const EntryMetadata& entry_metadata);
|
||||
|
||||
// Executes the |callback| when the index is ready. Allows multiple callbacks.
|
||||
int ExecuteWhenReady(const net::CompletionCallback& callback);
|
||||
|
||||
// Returns entries from the index that have last accessed time matching the
|
||||
// range between |initial_time| and |end_time| where open intervals are
|
||||
// possible according to the definition given in |DoomEntriesBetween()| in the
|
||||
// disk cache backend interface.
|
||||
std::unique_ptr<HashList> GetEntriesBetween(const base::Time initial_time,
|
||||
const base::Time end_time);
|
||||
|
||||
// Returns the list of all entries key hash.
|
||||
std::unique_ptr<HashList> GetAllHashes();
|
||||
|
||||
// Returns number of indexed entries.
|
||||
int32_t GetEntryCount() const;
|
||||
|
||||
// Returns the size of the entire cache in bytes. Can only be called after the
|
||||
// index has been initialized.
|
||||
uint64_t GetCacheSize() const;
|
||||
|
||||
// Returns the size of the cache entries accessed between |initial_time| and
|
||||
// |end_time| in bytes. Can only be called after the index has been
|
||||
// initialized.
|
||||
uint64_t GetCacheSizeBetween(const base::Time initial_time,
|
||||
const base::Time end_time) const;
|
||||
|
||||
// Returns whether the index has been initialized yet.
|
||||
bool initialized() const { return initialized_; }
|
||||
|
||||
IndexInitMethod init_method() const { return init_method_; }
|
||||
|
||||
// Returns the estimate of dynamically allocated memory in bytes.
|
||||
size_t EstimateMemoryUsage() const;
|
||||
|
||||
private:
|
||||
friend class SimpleIndexTest;
|
||||
FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, IndexSizeCorrectOnMerge);
|
||||
FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWriteQueued);
|
||||
FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWriteExecuted);
|
||||
FRIEND_TEST_ALL_PREFIXES(SimpleIndexTest, DiskWritePostponed);
|
||||
|
||||
void StartEvictionIfNeeded();
|
||||
void EvictionDone(int result);
|
||||
|
||||
void PostponeWritingToDisk();
|
||||
|
||||
void UpdateEntryIteratorSize(EntrySet::iterator* it,
|
||||
base::StrictNumeric<uint32_t> entry_size);
|
||||
|
||||
// Must run on IO Thread.
|
||||
void MergeInitializingSet(std::unique_ptr<SimpleIndexLoadResult> load_result);
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
void OnApplicationStateChange(base::android::ApplicationState state);
|
||||
|
||||
std::unique_ptr<base::android::ApplicationStatusListener>
|
||||
app_status_listener_;
|
||||
#endif
|
||||
|
||||
scoped_refptr<BackendCleanupTracker> cleanup_tracker_;
|
||||
|
||||
// The owner of |this| must ensure the |delegate_| outlives |this|.
|
||||
SimpleIndexDelegate* delegate_;
|
||||
|
||||
EntrySet entries_set_;
|
||||
|
||||
const net::CacheType cache_type_;
|
||||
uint64_t cache_size_; // Total cache storage size in bytes.
|
||||
uint64_t max_size_;
|
||||
uint64_t high_watermark_;
|
||||
uint64_t low_watermark_;
|
||||
bool eviction_in_progress_;
|
||||
base::TimeTicks eviction_start_time_;
|
||||
|
||||
// This stores all the entry_hash of entries that are removed during
|
||||
// initialization.
|
||||
std::unordered_set<uint64_t> removed_entries_;
|
||||
bool initialized_;
|
||||
IndexInitMethod init_method_;
|
||||
|
||||
std::unique_ptr<SimpleIndexFile> index_file_;
|
||||
|
||||
scoped_refptr<base::SingleThreadTaskRunner> io_thread_;
|
||||
|
||||
// All nonstatic SimpleEntryImpl methods should always be called on the IO
|
||||
// thread, in all cases. |io_thread_checker_| documents and enforces this.
|
||||
base::ThreadChecker io_thread_checker_;
|
||||
|
||||
// Timestamp of the last time we wrote the index to disk.
|
||||
// PostponeWritingToDisk() may give up postponing and allow the write if it
|
||||
// has been a while since last time we wrote.
|
||||
base::TimeTicks last_write_to_disk_;
|
||||
|
||||
base::OneShotTimer write_to_disk_timer_;
|
||||
base::Closure write_to_disk_cb_;
|
||||
|
||||
typedef std::list<net::CompletionCallback> CallbackList;
|
||||
CallbackList to_run_when_initialized_;
|
||||
|
||||
// Set to true when the app is on the background. When the app is in the
|
||||
// background we can write the index much more frequently, to insure fresh
|
||||
// index on next startup.
|
||||
bool app_on_background_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_H_
|
||||
30
net/disk_cache/simple/simple_index_delegate.h
Normal file
30
net/disk_cache/simple/simple_index_delegate.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_DELEGATE_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_DELEGATE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "net/base/completion_callback.h"
|
||||
#include "net/base/net_export.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
class NET_EXPORT_PRIVATE SimpleIndexDelegate {
|
||||
public:
|
||||
virtual ~SimpleIndexDelegate() {}
|
||||
|
||||
// Dooms all entries in |entries|, calling |callback| with the result
|
||||
// asynchronously. |entries| is mutated in an undefined way by this call,
|
||||
// for efficiency.
|
||||
virtual void DoomEntries(std::vector<uint64_t>* entry_hashes,
|
||||
const net::CompletionCallback& callback) = 0;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_DELEGATE_H_
|
||||
605
net/disk_cache/simple/simple_index_file.cc
Normal file
605
net/disk_cache/simple/simple_index_file.cc
Normal file
@@ -0,0 +1,605 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_index_file.h"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
#include "base/hash.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/pickle.h"
|
||||
#include "base/single_thread_task_runner.h"
|
||||
#include "base/task_runner_util.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "net/disk_cache/simple/simple_backend_version.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
#include "net/disk_cache/simple/simple_histogram_macros.h"
|
||||
#include "net/disk_cache/simple/simple_index.h"
|
||||
#include "net/disk_cache/simple/simple_synchronous_entry.h"
|
||||
#include "net/disk_cache/simple/simple_util.h"
|
||||
|
||||
using base::File;
|
||||
|
||||
namespace disk_cache {
|
||||
namespace {
|
||||
|
||||
const int kEntryFilesHashLength = 16;
|
||||
const int kEntryFilesSuffixLength = 2;
|
||||
|
||||
// Limit on how big a file we are willing to work with, to avoid crashes
|
||||
// when its corrupt.
|
||||
const int kMaxEntriesInIndex = 1000000;
|
||||
|
||||
// Here 8 comes from the key size.
|
||||
const int64_t kMaxIndexFileSizeBytes =
|
||||
kMaxEntriesInIndex * (8 + EntryMetadata::kOnDiskSizeBytes);
|
||||
|
||||
uint32_t CalculatePickleCRC(const base::Pickle& pickle) {
|
||||
return simple_util::Crc32(pickle.payload(), pickle.payload_size());
|
||||
}
|
||||
|
||||
// Used in histograms. Please only add new values at the end.
|
||||
enum IndexFileState {
|
||||
INDEX_STATE_CORRUPT = 0,
|
||||
INDEX_STATE_STALE = 1,
|
||||
INDEX_STATE_FRESH = 2,
|
||||
INDEX_STATE_FRESH_CONCURRENT_UPDATES = 3,
|
||||
INDEX_STATE_MAX = 4,
|
||||
};
|
||||
|
||||
enum StaleIndexQuality {
|
||||
STALE_INDEX_OK = 0,
|
||||
STALE_INDEX_MISSED_ENTRIES = 1,
|
||||
STALE_INDEX_EXTRA_ENTRIES = 2,
|
||||
STALE_INDEX_BOTH_MISSED_AND_EXTRA_ENTRIES = 3,
|
||||
STALE_INDEX_MAX = 4,
|
||||
};
|
||||
|
||||
void UmaRecordIndexFileState(IndexFileState state, net::CacheType cache_type) {
|
||||
SIMPLE_CACHE_UMA(ENUMERATION,
|
||||
"IndexFileStateOnLoad", cache_type, state, INDEX_STATE_MAX);
|
||||
}
|
||||
|
||||
void UmaRecordIndexInitMethod(SimpleIndex::IndexInitMethod method,
|
||||
net::CacheType cache_type) {
|
||||
SIMPLE_CACHE_UMA(ENUMERATION, "IndexInitializeMethod", cache_type, method,
|
||||
SimpleIndex::INITIALIZE_METHOD_MAX);
|
||||
}
|
||||
|
||||
void UmaRecordIndexWriteReason(SimpleIndex::IndexWriteToDiskReason reason,
|
||||
net::CacheType cache_type) {
|
||||
SIMPLE_CACHE_UMA(ENUMERATION, "IndexWriteReason", cache_type, reason,
|
||||
SimpleIndex::INDEX_WRITE_REASON_MAX);
|
||||
}
|
||||
|
||||
void UmaRecordIndexWriteReasonAtLoad(SimpleIndex::IndexWriteToDiskReason reason,
|
||||
net::CacheType cache_type) {
|
||||
SIMPLE_CACHE_UMA(ENUMERATION, "IndexWriteReasonAtLoad", cache_type, reason,
|
||||
SimpleIndex::INDEX_WRITE_REASON_MAX);
|
||||
}
|
||||
|
||||
void UmaRecordStaleIndexQuality(int missed_entry_count,
|
||||
int extra_entry_count,
|
||||
net::CacheType cache_type) {
|
||||
SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "StaleIndexMissedEntryCount", cache_type,
|
||||
missed_entry_count, 1, 100, 5);
|
||||
SIMPLE_CACHE_UMA(CUSTOM_COUNTS, "StaleIndexExtraEntryCount", cache_type,
|
||||
extra_entry_count, 1, 100, 5);
|
||||
|
||||
StaleIndexQuality quality;
|
||||
if (missed_entry_count > 0 && extra_entry_count > 0)
|
||||
quality = STALE_INDEX_BOTH_MISSED_AND_EXTRA_ENTRIES;
|
||||
else if (missed_entry_count > 0)
|
||||
quality = STALE_INDEX_MISSED_ENTRIES;
|
||||
else if (extra_entry_count > 0)
|
||||
quality = STALE_INDEX_EXTRA_ENTRIES;
|
||||
else
|
||||
quality = STALE_INDEX_OK;
|
||||
SIMPLE_CACHE_UMA(ENUMERATION, "StaleIndexQuality", cache_type, quality,
|
||||
STALE_INDEX_MAX);
|
||||
}
|
||||
|
||||
bool WritePickleFile(base::Pickle* pickle, const base::FilePath& file_name) {
|
||||
File file(
|
||||
file_name,
|
||||
File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE | File::FLAG_SHARE_DELETE);
|
||||
if (!file.IsValid())
|
||||
return false;
|
||||
|
||||
int bytes_written =
|
||||
file.Write(0, static_cast<const char*>(pickle->data()), pickle->size());
|
||||
if (bytes_written != base::checked_cast<int>(pickle->size())) {
|
||||
simple_util::SimpleCacheDeleteFile(file_name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called for each cache directory traversal iteration.
|
||||
void ProcessEntryFile(SimpleIndex::EntrySet* entries,
|
||||
const base::FilePath& file_path,
|
||||
base::Time last_accessed,
|
||||
base::Time last_modified,
|
||||
int64_t size) {
|
||||
static const size_t kEntryFilesLength =
|
||||
kEntryFilesHashLength + kEntryFilesSuffixLength;
|
||||
// Converting to std::string is OK since we never use UTF8 wide chars in our
|
||||
// file names.
|
||||
const base::FilePath::StringType base_name = file_path.BaseName().value();
|
||||
const std::string file_name(base_name.begin(), base_name.end());
|
||||
if (file_name.size() != kEntryFilesLength)
|
||||
return;
|
||||
const base::StringPiece hash_string(
|
||||
file_name.begin(), file_name.begin() + kEntryFilesHashLength);
|
||||
uint64_t hash_key = 0;
|
||||
if (!simple_util::GetEntryHashKeyFromHexString(hash_string, &hash_key)) {
|
||||
LOG(WARNING) << "Invalid entry hash key filename while restoring index from"
|
||||
<< " disk: " << file_name;
|
||||
return;
|
||||
}
|
||||
|
||||
base::Time last_used_time;
|
||||
#if defined(OS_POSIX)
|
||||
// For POSIX systems, a last access time is available. However, it's not
|
||||
// guaranteed to be more accurate than mtime. It is no worse though.
|
||||
last_used_time = last_accessed;
|
||||
#endif
|
||||
if (last_used_time.is_null())
|
||||
last_used_time = last_modified;
|
||||
|
||||
SimpleIndex::EntrySet::iterator it = entries->find(hash_key);
|
||||
base::CheckedNumeric<uint32_t> total_entry_size = size;
|
||||
|
||||
// Sometimes we see entry sizes here which are nonsense. We can't use them
|
||||
// as-is, as they simply won't fit the type. The options that come to mind
|
||||
// are:
|
||||
// 1) Ignore the file.
|
||||
// 2) Make something up.
|
||||
// 3) Delete the files for the hash.
|
||||
// ("crash the browser" isn't considered a serious alternative).
|
||||
//
|
||||
// The problem with doing (1) is that we are recovering the index here, so if
|
||||
// we don't include the info on the file here, we may completely lose track of
|
||||
// the entry and never clean the file up.
|
||||
//
|
||||
// (2) is actually mostly fine: we may trigger eviction too soon or too late,
|
||||
// but we can't really do better since we can't trust the size. If the entry
|
||||
// is never opened, it will eventually get evicted. If it is opened, we will
|
||||
// re-check the file size, and if it's nonsense delete it there, and if it's
|
||||
// fine we will fix up the index via a UpdateDataFromEntryStat to have the
|
||||
// correct size.
|
||||
//
|
||||
// (3) does the best thing except when the wrong size is some weird interim
|
||||
// thing just on directory listing (in which case it may evict an entry
|
||||
// prematurely). It's a little harder to think about since it involves
|
||||
// mutating the disk while there are other mutations going on, however,
|
||||
// while (2) is single-threaded.
|
||||
//
|
||||
// Hence this picks (2).
|
||||
|
||||
const int kPlaceHolderSizeWhenInvalid = 32768;
|
||||
if (!total_entry_size.IsValid()) {
|
||||
LOG(WARNING) << "Invalid file size while restoring index from disk: "
|
||||
<< size << " on file:" << file_name;
|
||||
}
|
||||
|
||||
if (it == entries->end()) {
|
||||
SimpleIndex::InsertInEntrySet(
|
||||
hash_key,
|
||||
EntryMetadata(last_used_time, total_entry_size.ValueOrDefault(
|
||||
kPlaceHolderSizeWhenInvalid)),
|
||||
entries);
|
||||
} else {
|
||||
// Summing up the total size of the entry through all the *_[0-1] files
|
||||
total_entry_size += it->second.GetEntrySize();
|
||||
it->second.SetEntrySize(
|
||||
total_entry_size.ValueOrDefault(kPlaceHolderSizeWhenInvalid));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SimpleIndexLoadResult::SimpleIndexLoadResult()
|
||||
: did_load(false),
|
||||
index_write_reason(SimpleIndex::INDEX_WRITE_REASON_MAX),
|
||||
flush_required(false) {}
|
||||
|
||||
SimpleIndexLoadResult::~SimpleIndexLoadResult() = default;
|
||||
|
||||
void SimpleIndexLoadResult::Reset() {
|
||||
did_load = false;
|
||||
index_write_reason = SimpleIndex::INDEX_WRITE_REASON_MAX;
|
||||
flush_required = false;
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
// static
|
||||
const char SimpleIndexFile::kIndexFileName[] = "the-real-index";
|
||||
// static
|
||||
const char SimpleIndexFile::kIndexDirectory[] = "index-dir";
|
||||
// static
|
||||
const char SimpleIndexFile::kTempIndexFileName[] = "temp-index";
|
||||
|
||||
SimpleIndexFile::IndexMetadata::IndexMetadata()
|
||||
: magic_number_(kSimpleIndexMagicNumber),
|
||||
version_(kSimpleVersion),
|
||||
reason_(SimpleIndex::INDEX_WRITE_REASON_MAX),
|
||||
entry_count_(0),
|
||||
cache_size_(0) {}
|
||||
|
||||
SimpleIndexFile::IndexMetadata::IndexMetadata(
|
||||
SimpleIndex::IndexWriteToDiskReason reason,
|
||||
uint64_t entry_count,
|
||||
uint64_t cache_size)
|
||||
: magic_number_(kSimpleIndexMagicNumber),
|
||||
version_(kSimpleVersion),
|
||||
reason_(reason),
|
||||
entry_count_(entry_count),
|
||||
cache_size_(cache_size) {}
|
||||
|
||||
void SimpleIndexFile::IndexMetadata::Serialize(base::Pickle* pickle) const {
|
||||
DCHECK(pickle);
|
||||
pickle->WriteUInt64(magic_number_);
|
||||
pickle->WriteUInt32(version_);
|
||||
pickle->WriteUInt64(entry_count_);
|
||||
pickle->WriteUInt64(cache_size_);
|
||||
pickle->WriteUInt32(static_cast<uint32_t>(reason_));
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleIndexFile::SerializeFinalData(base::Time cache_modified,
|
||||
base::Pickle* pickle) {
|
||||
pickle->WriteInt64(cache_modified.ToInternalValue());
|
||||
SimpleIndexFile::PickleHeader* header_p = pickle->headerT<PickleHeader>();
|
||||
header_p->crc = CalculatePickleCRC(*pickle);
|
||||
}
|
||||
|
||||
bool SimpleIndexFile::IndexMetadata::Deserialize(base::PickleIterator* it) {
|
||||
DCHECK(it);
|
||||
|
||||
bool v6_format_index_read_results =
|
||||
it->ReadUInt64(&magic_number_) && it->ReadUInt32(&version_) &&
|
||||
it->ReadUInt64(&entry_count_) && it->ReadUInt64(&cache_size_);
|
||||
if (!v6_format_index_read_results)
|
||||
return false;
|
||||
if (version_ >= 7) {
|
||||
uint32_t tmp_reason;
|
||||
if (!it->ReadUInt32(&tmp_reason))
|
||||
return false;
|
||||
reason_ = static_cast<SimpleIndex::IndexWriteToDiskReason>(tmp_reason);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SimpleIndexFile::SyncWriteToDisk(net::CacheType cache_type,
|
||||
const base::FilePath& cache_directory,
|
||||
const base::FilePath& index_filename,
|
||||
const base::FilePath& temp_index_filename,
|
||||
std::unique_ptr<base::Pickle> pickle,
|
||||
const base::TimeTicks& start_time,
|
||||
bool app_on_background) {
|
||||
DCHECK_EQ(index_filename.DirName().value(),
|
||||
temp_index_filename.DirName().value());
|
||||
base::FilePath index_file_directory = temp_index_filename.DirName();
|
||||
if (!base::DirectoryExists(index_file_directory) &&
|
||||
!base::CreateDirectory(index_file_directory)) {
|
||||
LOG(ERROR) << "Could not create a directory to hold the index file";
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a chance that the index containing all the necessary data about
|
||||
// newly created entries will appear to be stale. This can happen if on-disk
|
||||
// part of a Create operation does not fit into the time budget for the index
|
||||
// flush delay. This simple approach will be reconsidered if it does not allow
|
||||
// for maintaining freshness.
|
||||
base::Time cache_dir_mtime;
|
||||
if (!simple_util::GetMTime(cache_directory, &cache_dir_mtime)) {
|
||||
LOG(ERROR) << "Could obtain information about cache age";
|
||||
return;
|
||||
}
|
||||
SerializeFinalData(cache_dir_mtime, pickle.get());
|
||||
if (!WritePickleFile(pickle.get(), temp_index_filename)) {
|
||||
LOG(ERROR) << "Failed to write the temporary index file";
|
||||
return;
|
||||
}
|
||||
|
||||
// Atomically rename the temporary index file to become the real one.
|
||||
if (!base::ReplaceFile(temp_index_filename, index_filename, NULL))
|
||||
return;
|
||||
|
||||
if (app_on_background) {
|
||||
SIMPLE_CACHE_UMA(TIMES,
|
||||
"IndexWriteToDiskTime.Background", cache_type,
|
||||
(base::TimeTicks::Now() - start_time));
|
||||
} else {
|
||||
SIMPLE_CACHE_UMA(TIMES,
|
||||
"IndexWriteToDiskTime.Foreground", cache_type,
|
||||
(base::TimeTicks::Now() - start_time));
|
||||
}
|
||||
}
|
||||
|
||||
bool SimpleIndexFile::IndexMetadata::CheckIndexMetadata() {
|
||||
if (entry_count_ > kMaxEntriesInIndex ||
|
||||
magic_number_ != kSimpleIndexMagicNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static_assert(kSimpleVersion == 8, "index metadata reader out of date");
|
||||
// No |reason_| is saved in the version 6 file format.
|
||||
if (version_ == 6)
|
||||
return reason_ == SimpleIndex::INDEX_WRITE_REASON_MAX;
|
||||
return (version_ == 7 || version_ == 8) &&
|
||||
reason_ < SimpleIndex::INDEX_WRITE_REASON_MAX;
|
||||
}
|
||||
|
||||
SimpleIndexFile::SimpleIndexFile(
|
||||
const scoped_refptr<base::SequencedTaskRunner>& cache_runner,
|
||||
const scoped_refptr<base::TaskRunner>& worker_pool,
|
||||
net::CacheType cache_type,
|
||||
const base::FilePath& cache_directory)
|
||||
: cache_runner_(cache_runner),
|
||||
worker_pool_(worker_pool),
|
||||
cache_type_(cache_type),
|
||||
cache_directory_(cache_directory),
|
||||
index_file_(cache_directory_.AppendASCII(kIndexDirectory)
|
||||
.AppendASCII(kIndexFileName)),
|
||||
temp_index_file_(cache_directory_.AppendASCII(kIndexDirectory)
|
||||
.AppendASCII(kTempIndexFileName)) {}
|
||||
|
||||
SimpleIndexFile::~SimpleIndexFile() = default;
|
||||
|
||||
void SimpleIndexFile::LoadIndexEntries(base::Time cache_last_modified,
|
||||
const base::Closure& callback,
|
||||
SimpleIndexLoadResult* out_result) {
|
||||
base::Closure task = base::Bind(&SimpleIndexFile::SyncLoadIndexEntries,
|
||||
cache_type_,
|
||||
cache_last_modified, cache_directory_,
|
||||
index_file_, out_result);
|
||||
worker_pool_->PostTaskAndReply(FROM_HERE, task, callback);
|
||||
}
|
||||
|
||||
void SimpleIndexFile::WriteToDisk(SimpleIndex::IndexWriteToDiskReason reason,
|
||||
const SimpleIndex::EntrySet& entry_set,
|
||||
uint64_t cache_size,
|
||||
const base::TimeTicks& start,
|
||||
bool app_on_background,
|
||||
const base::Closure& callback) {
|
||||
UmaRecordIndexWriteReason(reason, cache_type_);
|
||||
IndexMetadata index_metadata(reason, entry_set.size(), cache_size);
|
||||
std::unique_ptr<base::Pickle> pickle = Serialize(index_metadata, entry_set);
|
||||
base::Closure task =
|
||||
base::Bind(&SimpleIndexFile::SyncWriteToDisk,
|
||||
cache_type_, cache_directory_, index_file_, temp_index_file_,
|
||||
base::Passed(&pickle), start, app_on_background);
|
||||
if (callback.is_null())
|
||||
cache_runner_->PostTask(FROM_HERE, task);
|
||||
else
|
||||
cache_runner_->PostTaskAndReply(FROM_HERE, task, callback);
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleIndexFile::SyncLoadIndexEntries(
|
||||
net::CacheType cache_type,
|
||||
base::Time cache_last_modified,
|
||||
const base::FilePath& cache_directory,
|
||||
const base::FilePath& index_file_path,
|
||||
SimpleIndexLoadResult* out_result) {
|
||||
// Load the index and find its age.
|
||||
base::Time last_cache_seen_by_index;
|
||||
SyncLoadFromDisk(index_file_path, &last_cache_seen_by_index, out_result);
|
||||
|
||||
// Consider the index loaded if it is fresh.
|
||||
const bool index_file_existed = base::PathExists(index_file_path);
|
||||
if (!out_result->did_load) {
|
||||
if (index_file_existed)
|
||||
UmaRecordIndexFileState(INDEX_STATE_CORRUPT, cache_type);
|
||||
} else {
|
||||
if (cache_last_modified <= last_cache_seen_by_index) {
|
||||
if (out_result->index_write_reason !=
|
||||
SimpleIndex::INDEX_WRITE_REASON_MAX) {
|
||||
UmaRecordIndexWriteReasonAtLoad(out_result->index_write_reason,
|
||||
cache_type);
|
||||
}
|
||||
base::Time latest_dir_mtime;
|
||||
simple_util::GetMTime(cache_directory, &latest_dir_mtime);
|
||||
if (LegacyIsIndexFileStale(latest_dir_mtime, index_file_path)) {
|
||||
UmaRecordIndexFileState(INDEX_STATE_FRESH_CONCURRENT_UPDATES,
|
||||
cache_type);
|
||||
} else {
|
||||
UmaRecordIndexFileState(INDEX_STATE_FRESH, cache_type);
|
||||
}
|
||||
out_result->init_method = SimpleIndex::INITIALIZE_METHOD_LOADED;
|
||||
UmaRecordIndexInitMethod(out_result->init_method, cache_type);
|
||||
return;
|
||||
}
|
||||
UmaRecordIndexFileState(INDEX_STATE_STALE, cache_type);
|
||||
}
|
||||
|
||||
// Reconstruct the index by scanning the disk for entries.
|
||||
SimpleIndex::EntrySet entries_from_stale_index;
|
||||
entries_from_stale_index.swap(out_result->entries);
|
||||
const base::TimeTicks start = base::TimeTicks::Now();
|
||||
SyncRestoreFromDisk(cache_directory, index_file_path, out_result);
|
||||
SIMPLE_CACHE_UMA(MEDIUM_TIMES, "IndexRestoreTime", cache_type,
|
||||
base::TimeTicks::Now() - start);
|
||||
SIMPLE_CACHE_UMA(COUNTS_1M, "IndexEntriesRestored", cache_type,
|
||||
out_result->entries.size());
|
||||
if (index_file_existed) {
|
||||
out_result->init_method = SimpleIndex::INITIALIZE_METHOD_RECOVERED;
|
||||
|
||||
int missed_entry_count = 0;
|
||||
for (const auto& i : out_result->entries) {
|
||||
if (entries_from_stale_index.count(i.first) == 0)
|
||||
++missed_entry_count;
|
||||
}
|
||||
int extra_entry_count = 0;
|
||||
for (const auto& i : entries_from_stale_index) {
|
||||
if (out_result->entries.count(i.first) == 0)
|
||||
++extra_entry_count;
|
||||
}
|
||||
UmaRecordStaleIndexQuality(missed_entry_count, extra_entry_count,
|
||||
cache_type);
|
||||
} else {
|
||||
out_result->init_method = SimpleIndex::INITIALIZE_METHOD_NEWCACHE;
|
||||
SIMPLE_CACHE_UMA(COUNTS_1M,
|
||||
"IndexCreatedEntryCount", cache_type,
|
||||
out_result->entries.size());
|
||||
}
|
||||
UmaRecordIndexInitMethod(out_result->init_method, cache_type);
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleIndexFile::SyncLoadFromDisk(const base::FilePath& index_filename,
|
||||
base::Time* out_last_cache_seen_by_index,
|
||||
SimpleIndexLoadResult* out_result) {
|
||||
out_result->Reset();
|
||||
|
||||
File file(index_filename, File::FLAG_OPEN | File::FLAG_READ |
|
||||
File::FLAG_SHARE_DELETE |
|
||||
File::FLAG_SEQUENTIAL_SCAN);
|
||||
if (!file.IsValid())
|
||||
return;
|
||||
|
||||
// Sanity-check the length. We don't want to crash trying to read some corrupt
|
||||
// 10GiB file or such.
|
||||
int64_t file_length = file.GetLength();
|
||||
if (file_length < 0 || file_length > kMaxIndexFileSizeBytes) {
|
||||
simple_util::SimpleCacheDeleteFile(index_filename);
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to preallocate in one chunk, so we don't induce fragmentation
|
||||
// reallocating a growing buffer.
|
||||
auto buffer = std::make_unique<char[]>(file_length);
|
||||
|
||||
int read = file.Read(0, buffer.get(), file_length);
|
||||
if (read < file_length) {
|
||||
simple_util::SimpleCacheDeleteFile(index_filename);
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleIndexFile::Deserialize(buffer.get(), read, out_last_cache_seen_by_index,
|
||||
out_result);
|
||||
|
||||
if (!out_result->did_load)
|
||||
simple_util::SimpleCacheDeleteFile(index_filename);
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<base::Pickle> SimpleIndexFile::Serialize(
|
||||
const SimpleIndexFile::IndexMetadata& index_metadata,
|
||||
const SimpleIndex::EntrySet& entries) {
|
||||
std::unique_ptr<base::Pickle> pickle(
|
||||
new base::Pickle(sizeof(SimpleIndexFile::PickleHeader)));
|
||||
|
||||
index_metadata.Serialize(pickle.get());
|
||||
for (SimpleIndex::EntrySet::const_iterator it = entries.begin();
|
||||
it != entries.end(); ++it) {
|
||||
pickle->WriteUInt64(it->first);
|
||||
it->second.Serialize(pickle.get());
|
||||
}
|
||||
return pickle;
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleIndexFile::Deserialize(const char* data, int data_len,
|
||||
base::Time* out_cache_last_modified,
|
||||
SimpleIndexLoadResult* out_result) {
|
||||
DCHECK(data);
|
||||
|
||||
out_result->Reset();
|
||||
SimpleIndex::EntrySet* entries = &out_result->entries;
|
||||
|
||||
base::Pickle pickle(data, data_len);
|
||||
if (!pickle.data()) {
|
||||
LOG(WARNING) << "Corrupt Simple Index File.";
|
||||
return;
|
||||
}
|
||||
|
||||
base::PickleIterator pickle_it(pickle);
|
||||
SimpleIndexFile::PickleHeader* header_p =
|
||||
pickle.headerT<SimpleIndexFile::PickleHeader>();
|
||||
const uint32_t crc_read = header_p->crc;
|
||||
const uint32_t crc_calculated = CalculatePickleCRC(pickle);
|
||||
|
||||
if (crc_read != crc_calculated) {
|
||||
LOG(WARNING) << "Invalid CRC in Simple Index file.";
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleIndexFile::IndexMetadata index_metadata;
|
||||
if (!index_metadata.Deserialize(&pickle_it)) {
|
||||
LOG(ERROR) << "Invalid index_metadata on Simple Cache Index.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!index_metadata.CheckIndexMetadata()) {
|
||||
LOG(ERROR) << "Invalid index_metadata on Simple Cache Index.";
|
||||
return;
|
||||
}
|
||||
|
||||
entries->reserve(index_metadata.entry_count() + kExtraSizeForMerge);
|
||||
while (entries->size() < index_metadata.entry_count()) {
|
||||
uint64_t hash_key;
|
||||
EntryMetadata entry_metadata;
|
||||
if (!pickle_it.ReadUInt64(&hash_key) ||
|
||||
!entry_metadata.Deserialize(
|
||||
&pickle_it, index_metadata.has_entry_in_memory_data())) {
|
||||
LOG(WARNING) << "Invalid EntryMetadata in Simple Index file.";
|
||||
entries->clear();
|
||||
return;
|
||||
}
|
||||
SimpleIndex::InsertInEntrySet(hash_key, entry_metadata, entries);
|
||||
}
|
||||
|
||||
int64_t cache_last_modified;
|
||||
if (!pickle_it.ReadInt64(&cache_last_modified)) {
|
||||
entries->clear();
|
||||
return;
|
||||
}
|
||||
DCHECK(out_cache_last_modified);
|
||||
*out_cache_last_modified = base::Time::FromInternalValue(cache_last_modified);
|
||||
|
||||
out_result->index_write_reason = index_metadata.reason();
|
||||
out_result->did_load = true;
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleIndexFile::SyncRestoreFromDisk(
|
||||
const base::FilePath& cache_directory,
|
||||
const base::FilePath& index_file_path,
|
||||
SimpleIndexLoadResult* out_result) {
|
||||
VLOG(1) << "Simple Cache Index is being restored from disk.";
|
||||
simple_util::SimpleCacheDeleteFile(index_file_path);
|
||||
out_result->Reset();
|
||||
SimpleIndex::EntrySet* entries = &out_result->entries;
|
||||
|
||||
const bool did_succeed = TraverseCacheDirectory(
|
||||
cache_directory, base::Bind(&ProcessEntryFile, entries));
|
||||
if (!did_succeed) {
|
||||
LOG(ERROR) << "Could not reconstruct index from disk";
|
||||
return;
|
||||
}
|
||||
out_result->did_load = true;
|
||||
// When we restore from disk we write the merged index file to disk right
|
||||
// away, this might save us from having to restore again next time.
|
||||
out_result->flush_required = true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool SimpleIndexFile::LegacyIsIndexFileStale(
|
||||
base::Time cache_last_modified,
|
||||
const base::FilePath& index_file_path) {
|
||||
base::Time index_mtime;
|
||||
if (!simple_util::GetMTime(index_file_path, &index_mtime))
|
||||
return true;
|
||||
return index_mtime < cache_last_modified;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
207
net/disk_cache/simple/simple_index_file.h
Normal file
207
net/disk_cache/simple/simple_index_file.h
Normal file
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_FILE_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_FILE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/pickle.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/simple/simple_index.h"
|
||||
|
||||
namespace base {
|
||||
class SequencedTaskRunner;
|
||||
class TaskRunner;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
const uint64_t kSimpleIndexMagicNumber = UINT64_C(0x656e74657220796f);
|
||||
|
||||
struct NET_EXPORT_PRIVATE SimpleIndexLoadResult {
|
||||
SimpleIndexLoadResult();
|
||||
~SimpleIndexLoadResult();
|
||||
void Reset();
|
||||
|
||||
bool did_load;
|
||||
SimpleIndex::EntrySet entries;
|
||||
SimpleIndex::IndexWriteToDiskReason index_write_reason;
|
||||
SimpleIndex::IndexInitMethod init_method;
|
||||
bool flush_required;
|
||||
};
|
||||
|
||||
// Simple Index File format is a pickle of IndexMetadata and EntryMetadata
|
||||
// objects. The file format is as follows: one instance of |IndexMetadata|
|
||||
// followed by |EntryMetadata| repeated |entry_count| times. To learn more about
|
||||
// the format see |SimpleIndexFile::Serialize()| and
|
||||
// |SimpleIndexFile::LoadFromDisk()|.
|
||||
//
|
||||
// The non-static methods must run on the IO thread. All the real
|
||||
// work is done in the static methods, which are run on the cache thread
|
||||
// or in worker threads. Synchronization between methods is the
|
||||
// responsibility of the caller.
|
||||
class NET_EXPORT_PRIVATE SimpleIndexFile {
|
||||
public:
|
||||
class NET_EXPORT_PRIVATE IndexMetadata {
|
||||
public:
|
||||
IndexMetadata();
|
||||
IndexMetadata(SimpleIndex::IndexWriteToDiskReason reason,
|
||||
uint64_t entry_count,
|
||||
uint64_t cache_size);
|
||||
|
||||
virtual void Serialize(base::Pickle* pickle) const;
|
||||
bool Deserialize(base::PickleIterator* it);
|
||||
|
||||
bool CheckIndexMetadata();
|
||||
|
||||
SimpleIndex::IndexWriteToDiskReason reason() const { return reason_; }
|
||||
uint64_t entry_count() const { return entry_count_; }
|
||||
bool has_entry_in_memory_data() const { return version_ >= 8; }
|
||||
|
||||
private:
|
||||
FRIEND_TEST_ALL_PREFIXES(IndexMetadataTest, Basics);
|
||||
FRIEND_TEST_ALL_PREFIXES(IndexMetadataTest, Serialize);
|
||||
FRIEND_TEST_ALL_PREFIXES(IndexMetadataTest, ReadV6Format);
|
||||
FRIEND_TEST_ALL_PREFIXES(SimpleIndexFileTest, ReadV7Format);
|
||||
friend class V6IndexMetadataForTest;
|
||||
friend class V7IndexMetadataForTest;
|
||||
|
||||
uint64_t magic_number_;
|
||||
uint32_t version_;
|
||||
SimpleIndex::IndexWriteToDiskReason reason_;
|
||||
uint64_t entry_count_;
|
||||
uint64_t cache_size_; // Total cache storage size in bytes.
|
||||
};
|
||||
|
||||
SimpleIndexFile(const scoped_refptr<base::SequencedTaskRunner>& cache_runner,
|
||||
const scoped_refptr<base::TaskRunner>& worker_pool,
|
||||
net::CacheType cache_type,
|
||||
const base::FilePath& cache_directory);
|
||||
virtual ~SimpleIndexFile();
|
||||
|
||||
// Gets index entries based on current disk context. On error it may leave
|
||||
// |out_result.did_load| untouched, but still return partial and consistent
|
||||
// results in |out_result.entries|.
|
||||
virtual void LoadIndexEntries(base::Time cache_last_modified,
|
||||
const base::Closure& callback,
|
||||
SimpleIndexLoadResult* out_result);
|
||||
|
||||
// Writes the specified set of entries to disk.
|
||||
virtual void WriteToDisk(SimpleIndex::IndexWriteToDiskReason reason,
|
||||
const SimpleIndex::EntrySet& entry_set,
|
||||
uint64_t cache_size,
|
||||
const base::TimeTicks& start,
|
||||
bool app_on_background,
|
||||
const base::Closure& callback);
|
||||
|
||||
private:
|
||||
friend class WrappedSimpleIndexFile;
|
||||
|
||||
// Used for cache directory traversal.
|
||||
using EntryFileCallback = base::Callback<void(const base::FilePath&,
|
||||
base::Time last_accessed,
|
||||
base::Time last_modified,
|
||||
int64_t size)>;
|
||||
|
||||
// When loading the entries from disk, add this many extra hash buckets to
|
||||
// prevent reallocation on the IO thread when merging in new live entries.
|
||||
static const int kExtraSizeForMerge = 512;
|
||||
|
||||
// Synchronous (IO performing) implementation of LoadIndexEntries.
|
||||
static void SyncLoadIndexEntries(net::CacheType cache_type,
|
||||
base::Time cache_last_modified,
|
||||
const base::FilePath& cache_directory,
|
||||
const base::FilePath& index_file_path,
|
||||
SimpleIndexLoadResult* out_result);
|
||||
|
||||
// Load the index file from disk returning an EntrySet.
|
||||
static void SyncLoadFromDisk(const base::FilePath& index_filename,
|
||||
base::Time* out_last_cache_seen_by_index,
|
||||
SimpleIndexLoadResult* out_result);
|
||||
|
||||
// Returns a scoped_ptr for a newly allocated base::Pickle containing the
|
||||
// serialized
|
||||
// data to be written to a file. Note: the pickle is not in a consistent state
|
||||
// immediately after calling this menthod, one needs to call
|
||||
// SerializeFinalData to make it ready to write to a file.
|
||||
static std::unique_ptr<base::Pickle> Serialize(
|
||||
const SimpleIndexFile::IndexMetadata& index_metadata,
|
||||
const SimpleIndex::EntrySet& entries);
|
||||
|
||||
// Appends cache modification time data to the serialized format. This is
|
||||
// performed on a thread accessing the disk. It is not combined with the main
|
||||
// serialization path to avoid extra thread hops or copying the pickle to the
|
||||
// worker thread.
|
||||
static void SerializeFinalData(base::Time cache_modified,
|
||||
base::Pickle* pickle);
|
||||
|
||||
// Given the contents of an index file |data| of length |data_len|, returns
|
||||
// the corresponding EntrySet. Returns NULL on error.
|
||||
static void Deserialize(const char* data, int data_len,
|
||||
base::Time* out_cache_last_modified,
|
||||
SimpleIndexLoadResult* out_result);
|
||||
|
||||
// Implemented either in simple_index_file_posix.cc or
|
||||
// simple_index_file_win.cc. base::FileEnumerator turned out to be very
|
||||
// expensive in terms of memory usage therefore it's used only on non-POSIX
|
||||
// environments for convenience (for now). Returns whether the traversal
|
||||
// succeeded.
|
||||
static bool TraverseCacheDirectory(
|
||||
const base::FilePath& cache_path,
|
||||
const EntryFileCallback& entry_file_callback);
|
||||
|
||||
// Writes the index file to disk atomically.
|
||||
static void SyncWriteToDisk(net::CacheType cache_type,
|
||||
const base::FilePath& cache_directory,
|
||||
const base::FilePath& index_filename,
|
||||
const base::FilePath& temp_index_filename,
|
||||
std::unique_ptr<base::Pickle> pickle,
|
||||
const base::TimeTicks& start_time,
|
||||
bool app_on_background);
|
||||
|
||||
// Scan the index directory for entries, returning an EntrySet of all entries
|
||||
// found.
|
||||
static void SyncRestoreFromDisk(const base::FilePath& cache_directory,
|
||||
const base::FilePath& index_file_path,
|
||||
SimpleIndexLoadResult* out_result);
|
||||
|
||||
// Determines if an index file is stale relative to the time of last
|
||||
// modification of the cache directory. Obsolete, used only for a histogram to
|
||||
// compare with the new method.
|
||||
// TODO(pasko): remove this method after getting enough data.
|
||||
static bool LegacyIsIndexFileStale(base::Time cache_last_modified,
|
||||
const base::FilePath& index_file_path);
|
||||
|
||||
struct PickleHeader : public base::Pickle::Header {
|
||||
uint32_t crc;
|
||||
};
|
||||
|
||||
const scoped_refptr<base::SequencedTaskRunner> cache_runner_;
|
||||
const scoped_refptr<base::TaskRunner> worker_pool_;
|
||||
const net::CacheType cache_type_;
|
||||
const base::FilePath cache_directory_;
|
||||
const base::FilePath index_file_;
|
||||
const base::FilePath temp_index_file_;
|
||||
|
||||
static const char kIndexDirectory[];
|
||||
static const char kIndexFileName[];
|
||||
static const char kTempIndexFileName[];
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SimpleIndexFile);
|
||||
};
|
||||
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_INDEX_FILE_H_
|
||||
72
net/disk_cache/simple/simple_index_file_posix.cc
Normal file
72
net/disk_cache/simple/simple_index_file_posix.cc
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_index_file.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace disk_cache {
|
||||
namespace {
|
||||
|
||||
struct DirCloser {
|
||||
void operator()(DIR* dir) { closedir(dir); }
|
||||
};
|
||||
|
||||
typedef std::unique_ptr<DIR, DirCloser> ScopedDir;
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
bool SimpleIndexFile::TraverseCacheDirectory(
|
||||
const base::FilePath& cache_path,
|
||||
const EntryFileCallback& entry_file_callback) {
|
||||
const ScopedDir dir(opendir(cache_path.value().c_str()));
|
||||
if (!dir) {
|
||||
PLOG(ERROR) << "opendir " << cache_path.value();
|
||||
return false;
|
||||
}
|
||||
while (true) {
|
||||
// errno must be set to 0 before every readdir() call to detect errors.
|
||||
errno = 0;
|
||||
dirent* entry = readdir(dir.get());
|
||||
if (!entry) {
|
||||
// Some implementations of readdir() (particularly older versions of
|
||||
// Android Bionic) may leave errno set to EINTR even after they handle
|
||||
// this case internally. It's safe to ignore EINTR in that case.
|
||||
if (errno && errno != EINTR) {
|
||||
PLOG(ERROR) << "readdir " << cache_path.value();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string file_name(entry->d_name);
|
||||
if (file_name == "." || file_name == "..")
|
||||
continue;
|
||||
const base::FilePath file_path = cache_path.Append(
|
||||
base::FilePath(file_name));
|
||||
base::File::Info file_info;
|
||||
if (!base::GetFileInfo(file_path, &file_info)) {
|
||||
LOG(ERROR) << "Could not get file info for " << file_path.value();
|
||||
continue;
|
||||
}
|
||||
|
||||
entry_file_callback.Run(file_path, file_info.last_accessed,
|
||||
file_info.last_modified, file_info.size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
35
net/disk_cache/simple/simple_index_file_win.cc
Normal file
35
net/disk_cache/simple/simple_index_file_win.cc
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_index_file.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_path.h"
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
// static
|
||||
bool SimpleIndexFile::TraverseCacheDirectory(
|
||||
const base::FilePath& cache_path,
|
||||
const EntryFileCallback& entry_file_callback) {
|
||||
const base::FilePath current_directory(FILE_PATH_LITERAL("."));
|
||||
const base::FilePath parent_directory(FILE_PATH_LITERAL(".."));
|
||||
const base::FilePath::StringType file_pattern = FILE_PATH_LITERAL("*");
|
||||
base::FileEnumerator enumerator(
|
||||
cache_path, false /* recursive */, base::FileEnumerator::FILES,
|
||||
file_pattern);
|
||||
for (base::FilePath file_path = enumerator.Next(); !file_path.empty();
|
||||
file_path = enumerator.Next()) {
|
||||
if (file_path == current_directory || file_path == parent_directory)
|
||||
continue;
|
||||
base::FileEnumerator::FileInfo info = enumerator.GetInfo();
|
||||
entry_file_callback.Run(file_path, base::Time(), info.GetLastModifiedTime(),
|
||||
info.GetSize());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
60
net/disk_cache/simple/simple_net_log_parameters.cc
Normal file
60
net/disk_cache/simple/simple_net_log_parameters.cc
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_net_log_parameters.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "base/format_macros.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/values.h"
|
||||
#include "net/base/net_errors.h"
|
||||
#include "net/disk_cache/simple/simple_entry_impl.h"
|
||||
#include "net/log/net_log_capture_mode.h"
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<base::Value> NetLogSimpleEntryConstructionCallback(
|
||||
const disk_cache::SimpleEntryImpl* entry,
|
||||
net::NetLogCaptureMode capture_mode) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
dict->SetString("entry_hash",
|
||||
base::StringPrintf("%#016" PRIx64, entry->entry_hash()));
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
std::unique_ptr<base::Value> NetLogSimpleEntryCreationCallback(
|
||||
const disk_cache::SimpleEntryImpl* entry,
|
||||
int net_error,
|
||||
net::NetLogCaptureMode /* capture_mode */) {
|
||||
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
|
||||
dict->SetInteger("net_error", net_error);
|
||||
if (net_error == net::OK)
|
||||
dict->SetString("key", entry->key());
|
||||
return std::move(dict);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogSimpleEntryConstructionCallback(
|
||||
const SimpleEntryImpl* entry) {
|
||||
DCHECK(entry);
|
||||
return base::Bind(&NetLogSimpleEntryConstructionCallback,
|
||||
base::Unretained(entry));
|
||||
}
|
||||
|
||||
net::NetLogParametersCallback CreateNetLogSimpleEntryCreationCallback(
|
||||
const SimpleEntryImpl* entry,
|
||||
int net_error) {
|
||||
DCHECK(entry);
|
||||
return base::Bind(&NetLogSimpleEntryCreationCallback, base::Unretained(entry),
|
||||
net_error);
|
||||
}
|
||||
|
||||
} // namespace disk_cache
|
||||
32
net/disk_cache/simple/simple_net_log_parameters.h
Normal file
32
net/disk_cache/simple/simple_net_log_parameters.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_NET_LOG_PARAMETERS_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_NET_LOG_PARAMETERS_H_
|
||||
|
||||
#include "net/log/net_log_parameters_callback.h"
|
||||
|
||||
// This file augments the functions in net/disk_cache/net_log_parameters.h to
|
||||
// include ones that deal with specifics of the Simple Cache backend.
|
||||
namespace disk_cache {
|
||||
|
||||
class SimpleEntryImpl;
|
||||
|
||||
// Creates a NetLog callback that returns parameters for the construction of a
|
||||
// SimpleEntryImpl. Contains the entry's hash. |entry| can't be NULL and must
|
||||
// outlive the returned callback.
|
||||
net::NetLogParametersCallback CreateNetLogSimpleEntryConstructionCallback(
|
||||
const SimpleEntryImpl* entry);
|
||||
|
||||
// Creates a NetLog callback that returns parameters for the result of calling
|
||||
// |CreateEntry| or |OpenEntry| on a SimpleEntryImpl. Contains the |net_error|
|
||||
// and, if successful, the entry's key. |entry| can't be NULL and must outlive
|
||||
// the returned callback.
|
||||
net::NetLogParametersCallback CreateNetLogSimpleEntryCreationCallback(
|
||||
const SimpleEntryImpl* entry,
|
||||
int net_error);
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_NET_LOG_PARAMETERS_H_
|
||||
1795
net/disk_cache/simple/simple_synchronous_entry.cc
Normal file
1795
net/disk_cache/simple/simple_synchronous_entry.cc
Normal file
File diff suppressed because it is too large
Load Diff
463
net/disk_cache/simple/simple_synchronous_entry.h
Normal file
463
net/disk_cache/simple/simple_synchronous_entry.h
Normal file
@@ -0,0 +1,463 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_SYNCHRONOUS_ENTRY_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_SYNCHRONOUS_ENTRY_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/strings/string_piece_forward.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/base/cache_type.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
#include "net/disk_cache/simple/simple_file_tracker.h"
|
||||
|
||||
namespace net {
|
||||
class GrowableIOBuffer;
|
||||
class IOBuffer;
|
||||
}
|
||||
|
||||
FORWARD_DECLARE_TEST(DiskCacheBackendTest, SimpleCacheEnumerationLongKeys);
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
NET_EXPORT_PRIVATE extern const base::Feature kSimpleCachePrefetchExperiment;
|
||||
NET_EXPORT_PRIVATE extern const char kSimplePrefetchBytesParam[];
|
||||
|
||||
// Returns how large a file would get prefetched on reading the entry.
|
||||
// If the experiment is disabled, returns 0.
|
||||
NET_EXPORT_PRIVATE int GetSimpleCachePrefetchSize();
|
||||
|
||||
class SimpleSynchronousEntry;
|
||||
|
||||
// This class handles the passing of data about the entry between
|
||||
// SimpleEntryImplementation and SimpleSynchronousEntry and the computation of
|
||||
// file offsets based on the data size for all streams.
|
||||
class NET_EXPORT_PRIVATE SimpleEntryStat {
|
||||
public:
|
||||
SimpleEntryStat(base::Time last_used,
|
||||
base::Time last_modified,
|
||||
const int32_t data_size[],
|
||||
const int32_t sparse_data_size);
|
||||
|
||||
int GetOffsetInFile(size_t key_length, int offset, int stream_index) const;
|
||||
int GetEOFOffsetInFile(size_t key_length, int stream_index) const;
|
||||
int GetLastEOFOffsetInFile(size_t key_length, int file_index) const;
|
||||
int64_t GetFileSize(size_t key_length, int file_index) const;
|
||||
|
||||
base::Time last_used() const { return last_used_; }
|
||||
base::Time last_modified() const { return last_modified_; }
|
||||
void set_last_used(base::Time last_used) { last_used_ = last_used; }
|
||||
void set_last_modified(base::Time last_modified) {
|
||||
last_modified_ = last_modified;
|
||||
}
|
||||
|
||||
int32_t data_size(int stream_index) const { return data_size_[stream_index]; }
|
||||
void set_data_size(int stream_index, int data_size) {
|
||||
data_size_[stream_index] = data_size;
|
||||
}
|
||||
|
||||
int32_t sparse_data_size() const { return sparse_data_size_; }
|
||||
void set_sparse_data_size(int32_t sparse_data_size) {
|
||||
sparse_data_size_ = sparse_data_size;
|
||||
}
|
||||
|
||||
private:
|
||||
base::Time last_used_;
|
||||
base::Time last_modified_;
|
||||
int32_t data_size_[kSimpleEntryStreamCount];
|
||||
int32_t sparse_data_size_;
|
||||
};
|
||||
|
||||
struct SimpleStreamPrefetchData {
|
||||
SimpleStreamPrefetchData();
|
||||
~SimpleStreamPrefetchData();
|
||||
|
||||
scoped_refptr<net::GrowableIOBuffer> data;
|
||||
uint32_t stream_crc32;
|
||||
};
|
||||
|
||||
struct SimpleEntryCreationResults {
|
||||
explicit SimpleEntryCreationResults(SimpleEntryStat entry_stat);
|
||||
~SimpleEntryCreationResults();
|
||||
|
||||
SimpleSynchronousEntry* sync_entry;
|
||||
|
||||
// Expectation is that [0] will always be filled in, but [1] might not be.
|
||||
SimpleStreamPrefetchData stream_prefetch_data[2];
|
||||
|
||||
SimpleEntryStat entry_stat;
|
||||
int result;
|
||||
};
|
||||
|
||||
// Worker thread interface to the very simple cache. This interface is not
|
||||
// thread safe, and callers must ensure that it is only ever accessed from
|
||||
// a single thread between synchronization points.
|
||||
class SimpleSynchronousEntry {
|
||||
public:
|
||||
struct CRCRecord {
|
||||
CRCRecord();
|
||||
CRCRecord(int index_p, bool has_crc32_p, uint32_t data_crc32_p);
|
||||
|
||||
int index;
|
||||
bool has_crc32;
|
||||
uint32_t data_crc32;
|
||||
};
|
||||
|
||||
struct CRCRequest {
|
||||
CRCRequest()
|
||||
: data_crc32(0),
|
||||
request_verify(false),
|
||||
performed_verify(false),
|
||||
verify_ok(false) {}
|
||||
|
||||
// Initial CRC, to be updated with CRC of block.
|
||||
uint32_t data_crc32;
|
||||
|
||||
// If true, CRC should be verified if at end of stream.
|
||||
bool request_verify;
|
||||
|
||||
// If true, CRC was actually checked.
|
||||
bool performed_verify;
|
||||
bool verify_ok;
|
||||
};
|
||||
|
||||
struct EntryOperationData {
|
||||
EntryOperationData(int index_p, int offset_p, int buf_len_p);
|
||||
EntryOperationData(int index_p,
|
||||
int offset_p,
|
||||
int buf_len_p,
|
||||
bool truncate_p,
|
||||
bool doomed_p);
|
||||
EntryOperationData(int64_t sparse_offset_p, int buf_len_p);
|
||||
|
||||
int index;
|
||||
int offset;
|
||||
int64_t sparse_offset;
|
||||
int buf_len;
|
||||
bool truncate;
|
||||
bool doomed;
|
||||
};
|
||||
|
||||
// Opens a disk cache entry on disk. The |key| parameter is optional, if empty
|
||||
// the operation may be slower. The |entry_hash| parameter is required.
|
||||
// |had_index| is provided only for histograms.
|
||||
// |time_enqueued| is when this operation was added to the I/O thread pool,
|
||||
// and is provided only for histograms.
|
||||
static void OpenEntry(net::CacheType cache_type,
|
||||
const base::FilePath& path,
|
||||
const std::string& key,
|
||||
uint64_t entry_hash,
|
||||
bool had_index,
|
||||
const base::TimeTicks& time_enqueued,
|
||||
SimpleFileTracker* file_tracker,
|
||||
SimpleEntryCreationResults* out_results);
|
||||
|
||||
static void CreateEntry(net::CacheType cache_type,
|
||||
const base::FilePath& path,
|
||||
const std::string& key,
|
||||
uint64_t entry_hash,
|
||||
bool had_index,
|
||||
const base::TimeTicks& time_enqueued,
|
||||
SimpleFileTracker* file_tracker,
|
||||
SimpleEntryCreationResults* out_results);
|
||||
|
||||
// Deletes an entry from the file system without affecting the state of the
|
||||
// corresponding instance, if any (allowing operations to continue to be
|
||||
// executed through that instance). Returns a net error code.
|
||||
static int DoomEntry(const base::FilePath& path, uint64_t entry_hash);
|
||||
|
||||
// Like |DoomEntry()| above, except that it truncates the entry files rather
|
||||
// than deleting them. Used when dooming entries after the backend has
|
||||
// shutdown. See implementation of |SimpleEntryImpl::DoomEntryInternal()| for
|
||||
// more.
|
||||
static int TruncateEntryFiles(const base::FilePath& path,
|
||||
uint64_t entry_hash);
|
||||
|
||||
// Like |DoomEntry()| above. Deletes all entries corresponding to the
|
||||
// |key_hashes|. Succeeds only when all entries are deleted. Returns a net
|
||||
// error code.
|
||||
static int DoomEntrySet(const std::vector<uint64_t>* key_hashes,
|
||||
const base::FilePath& path);
|
||||
|
||||
// N.B. ReadData(), WriteData(), CheckEOFRecord(), ReadSparseData(),
|
||||
// WriteSparseData() and Close() may block on IO.
|
||||
//
|
||||
// All of these methods will put the //net return value into |*out_result|.
|
||||
|
||||
// |crc_request| can be nullptr here, to denote that no CRC computation is
|
||||
// requested.
|
||||
void ReadData(const EntryOperationData& in_entry_op,
|
||||
CRCRequest* crc_request,
|
||||
SimpleEntryStat* entry_stat,
|
||||
net::IOBuffer* out_buf,
|
||||
int* out_result);
|
||||
void WriteData(const EntryOperationData& in_entry_op,
|
||||
net::IOBuffer* in_buf,
|
||||
SimpleEntryStat* out_entry_stat,
|
||||
int* out_result);
|
||||
int CheckEOFRecord(base::File* file,
|
||||
int stream_index,
|
||||
const SimpleEntryStat& entry_stat,
|
||||
uint32_t expected_crc32);
|
||||
|
||||
void ReadSparseData(const EntryOperationData& in_entry_op,
|
||||
net::IOBuffer* out_buf,
|
||||
base::Time* out_last_used,
|
||||
int* out_result);
|
||||
void WriteSparseData(const EntryOperationData& in_entry_op,
|
||||
net::IOBuffer* in_buf,
|
||||
uint64_t max_sparse_data_size,
|
||||
SimpleEntryStat* out_entry_stat,
|
||||
int* out_result);
|
||||
void GetAvailableRange(const EntryOperationData& in_entry_op,
|
||||
int64_t* out_start,
|
||||
int* out_result);
|
||||
|
||||
// Close all streams, and add write EOF records to streams indicated by the
|
||||
// CRCRecord entries in |crc32s_to_write|.
|
||||
void Close(const SimpleEntryStat& entry_stat,
|
||||
std::unique_ptr<std::vector<CRCRecord>> crc32s_to_write,
|
||||
net::GrowableIOBuffer* stream_0_data);
|
||||
|
||||
const base::FilePath& path() const { return path_; }
|
||||
std::string key() const { return key_; }
|
||||
const SimpleFileTracker::EntryFileKey& entry_file_key() const {
|
||||
return entry_file_key_;
|
||||
}
|
||||
|
||||
private:
|
||||
FRIEND_TEST_ALL_PREFIXES(::DiskCacheBackendTest,
|
||||
SimpleCacheEnumerationLongKeys);
|
||||
friend class SimpleFileTrackerTest;
|
||||
|
||||
enum CreateEntryResult {
|
||||
CREATE_ENTRY_SUCCESS = 0,
|
||||
CREATE_ENTRY_PLATFORM_FILE_ERROR = 1,
|
||||
CREATE_ENTRY_CANT_WRITE_HEADER = 2,
|
||||
CREATE_ENTRY_CANT_WRITE_KEY = 3,
|
||||
CREATE_ENTRY_MAX = 4,
|
||||
};
|
||||
|
||||
enum FileRequired {
|
||||
FILE_NOT_REQUIRED,
|
||||
FILE_REQUIRED
|
||||
};
|
||||
|
||||
struct SparseRange {
|
||||
int64_t offset;
|
||||
int64_t length;
|
||||
uint32_t data_crc32;
|
||||
int64_t file_offset;
|
||||
|
||||
bool operator<(const SparseRange& other) const {
|
||||
return offset < other.offset;
|
||||
}
|
||||
};
|
||||
|
||||
// When opening an entry without knowing the key, the header must be read
|
||||
// without knowing the size of the key. This is how much to read initially, to
|
||||
// make it likely the entire key is read.
|
||||
static const size_t kInitialHeaderRead = 64 * 1024;
|
||||
|
||||
NET_EXPORT_PRIVATE SimpleSynchronousEntry(
|
||||
net::CacheType cache_type,
|
||||
const base::FilePath& path,
|
||||
const std::string& key,
|
||||
uint64_t entry_hash,
|
||||
bool had_index,
|
||||
SimpleFileTracker* simple_file_tracker);
|
||||
|
||||
// Like Entry, the SimpleSynchronousEntry self releases when Close() is
|
||||
// called.
|
||||
NET_EXPORT_PRIVATE ~SimpleSynchronousEntry();
|
||||
|
||||
// Tries to open one of the cache entry files. Succeeds if the open succeeds
|
||||
// or if the file was not found and is allowed to be omitted if the
|
||||
// corresponding stream is empty.
|
||||
bool MaybeOpenFile(int file_index,
|
||||
base::File::Error* out_error);
|
||||
// Creates one of the cache entry files if necessary. If the file is allowed
|
||||
// to be omitted if the corresponding stream is empty, and if |file_required|
|
||||
// is FILE_NOT_REQUIRED, then the file is not created; otherwise, it is.
|
||||
bool MaybeCreateFile(int file_index,
|
||||
FileRequired file_required,
|
||||
base::File::Error* out_error);
|
||||
bool OpenFiles(SimpleEntryStat* out_entry_stat);
|
||||
bool CreateFiles(SimpleEntryStat* out_entry_stat);
|
||||
void CloseFile(int index);
|
||||
void CloseFiles();
|
||||
|
||||
// Read the header and key at the beginning of the file, and validate that
|
||||
// they are correct. If this entry was opened with a key, the key is checked
|
||||
// for a match. If not, then the |key_| member is set based on the value in
|
||||
// this header. Records histograms if any check is failed.
|
||||
bool CheckHeaderAndKey(base::File* file, int file_index);
|
||||
|
||||
// Returns a net error, i.e. net::OK on success.
|
||||
int InitializeForOpen(SimpleEntryStat* out_entry_stat,
|
||||
SimpleStreamPrefetchData stream_prefetch_data[2]);
|
||||
|
||||
// Writes the header and key to a newly-created stream file. |index| is the
|
||||
// index of the stream. Returns true on success; returns false and sets
|
||||
// |*out_result| on failure.
|
||||
bool InitializeCreatedFile(int index, CreateEntryResult* out_result);
|
||||
|
||||
// Returns a net error, including net::OK on success and net::FILE_EXISTS
|
||||
// when the entry already exists.
|
||||
int InitializeForCreate(SimpleEntryStat* out_entry_stat);
|
||||
|
||||
// Allocates and fills a buffer with stream 0 data in |stream_0_data|, then
|
||||
// checks its crc32. May also optionally read in |stream_1_data| and its
|
||||
// crc, but might decide not to.
|
||||
int ReadAndValidateStream0AndMaybe1(
|
||||
int file_size,
|
||||
SimpleEntryStat* out_entry_stat,
|
||||
SimpleStreamPrefetchData stream_prefetch_data[2]);
|
||||
|
||||
// Reads the EOF record located at |file_offset| in file |file_index|,
|
||||
// with |file_0_prefetch| potentially having prefetched file 0 content.
|
||||
// Puts the result into |*eof_record| and sanity-checks it.
|
||||
// Returns net status, and records any failures to UMA.
|
||||
int GetEOFRecordData(base::File* file,
|
||||
base::StringPiece file_0_prefetch,
|
||||
int file_index,
|
||||
int file_offset,
|
||||
SimpleFileEOF* eof_record);
|
||||
|
||||
// Reads either from |file_0_prefetch| or |file|.
|
||||
// Range-checks all the in-memory reads.
|
||||
bool ReadFromFileOrPrefetched(base::File* file,
|
||||
base::StringPiece file_0_prefetch,
|
||||
int file_index,
|
||||
int offset,
|
||||
int size,
|
||||
char* dest);
|
||||
|
||||
// Extracts out the payload of stream |stream_index|, reading either from
|
||||
// |file_0_prefetch|, if available, or |file|. |entry_stat| will be used to
|
||||
// determine file layout, though |extra_size| additional bytes will be read
|
||||
// past the stream payload end.
|
||||
//
|
||||
// |*stream_data| will be pointed to a fresh buffer with the results,
|
||||
// and |*out_crc32| will get the checksum, which will be verified against
|
||||
// |eof_record|.
|
||||
int PreReadStreamPayload(base::File* file,
|
||||
base::StringPiece file_0_prefetch,
|
||||
int stream_index,
|
||||
int extra_size,
|
||||
const SimpleEntryStat& entry_stat,
|
||||
const SimpleFileEOF& eof_record,
|
||||
SimpleStreamPrefetchData* out);
|
||||
|
||||
void Doom() const;
|
||||
|
||||
// Opens the sparse data file and scans it if it exists.
|
||||
bool OpenSparseFileIfExists(int32_t* out_sparse_data_size);
|
||||
|
||||
// Creates and initializes the sparse data file.
|
||||
bool CreateSparseFile();
|
||||
|
||||
// Closes the sparse data file.
|
||||
void CloseSparseFile();
|
||||
|
||||
// Writes the header to the (newly-created) sparse file.
|
||||
bool InitializeSparseFile(base::File* file);
|
||||
|
||||
// Removes all but the header of the sparse file.
|
||||
bool TruncateSparseFile(base::File* sparse_file);
|
||||
|
||||
// Scans the existing ranges in the sparse file. Populates |sparse_ranges_|
|
||||
// and sets |*out_sparse_data_size| to the total size of all the ranges (not
|
||||
// including headers).
|
||||
bool ScanSparseFile(base::File* sparse_file, int32_t* out_sparse_data_size);
|
||||
|
||||
// Reads from a single sparse range. If asked to read the entire range, also
|
||||
// verifies the CRC32.
|
||||
bool ReadSparseRange(base::File* sparse_file,
|
||||
const SparseRange* range,
|
||||
int offset,
|
||||
int len,
|
||||
char* buf);
|
||||
|
||||
// Writes to a single (existing) sparse range. If asked to write the entire
|
||||
// range, also updates the CRC32; otherwise, invalidates it.
|
||||
bool WriteSparseRange(base::File* sparse_file,
|
||||
SparseRange* range,
|
||||
int offset,
|
||||
int len,
|
||||
const char* buf);
|
||||
|
||||
// Appends a new sparse range to the sparse data file.
|
||||
bool AppendSparseRange(base::File* sparse_file,
|
||||
int64_t offset,
|
||||
int len,
|
||||
const char* buf);
|
||||
|
||||
static bool DeleteFileForEntryHash(const base::FilePath& path,
|
||||
uint64_t entry_hash,
|
||||
int file_index);
|
||||
static bool DeleteFilesForEntryHash(const base::FilePath& path,
|
||||
uint64_t entry_hash);
|
||||
static bool TruncateFilesForEntryHash(const base::FilePath& path,
|
||||
uint64_t entry_hash);
|
||||
|
||||
void RecordSyncCreateResult(CreateEntryResult result, bool had_index);
|
||||
|
||||
base::FilePath GetFilenameFromFileIndex(int file_index);
|
||||
|
||||
bool sparse_file_open() const { return sparse_file_open_; }
|
||||
|
||||
const net::CacheType cache_type_;
|
||||
const base::FilePath path_;
|
||||
SimpleFileTracker::EntryFileKey entry_file_key_;
|
||||
const bool had_index_;
|
||||
std::string key_;
|
||||
|
||||
bool have_open_files_;
|
||||
bool initialized_;
|
||||
|
||||
// Normally false. This is set to true when an entry is opened without
|
||||
// checking the file headers. Any subsequent read will perform the check
|
||||
// before completing.
|
||||
bool header_and_key_check_needed_[kSimpleEntryNormalFileCount] = {
|
||||
false,
|
||||
};
|
||||
|
||||
SimpleFileTracker* file_tracker_;
|
||||
|
||||
// True if the corresponding stream is empty and therefore no on-disk file
|
||||
// was created to store it.
|
||||
bool empty_file_omitted_[kSimpleEntryNormalFileCount];
|
||||
|
||||
typedef std::map<int64_t, SparseRange> SparseRangeOffsetMap;
|
||||
typedef SparseRangeOffsetMap::iterator SparseRangeIterator;
|
||||
SparseRangeOffsetMap sparse_ranges_;
|
||||
bool sparse_file_open_;
|
||||
|
||||
// Offset of the end of the sparse file (where the next sparse range will be
|
||||
// written).
|
||||
int64_t sparse_tail_offset_;
|
||||
|
||||
// True if the entry was created, or false if it was opened. Used to log
|
||||
// SimpleCache.*.EntryCreatedWithStream2Omitted only for created entries.
|
||||
bool files_created_;
|
||||
};
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_SYNCHRONOUS_ENTRY_H_
|
||||
135
net/disk_cache/simple/simple_test_util.cc
Normal file
135
net/disk_cache/simple/simple_test_util.cc
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_test_util.h"
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "net/base/hash_value.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
#include "net/disk_cache/simple/simple_util.h"
|
||||
|
||||
namespace disk_cache {
|
||||
namespace simple_util {
|
||||
|
||||
using base::File;
|
||||
using base::FilePath;
|
||||
|
||||
bool CreateCorruptFileForTests(const std::string& key,
|
||||
const FilePath& cache_path) {
|
||||
FilePath entry_file_path = cache_path.AppendASCII(
|
||||
disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0));
|
||||
int flags = File::FLAG_CREATE_ALWAYS | File::FLAG_WRITE;
|
||||
File entry_file(entry_file_path, flags);
|
||||
|
||||
if (!entry_file.IsValid())
|
||||
return false;
|
||||
|
||||
return entry_file.Write(0, "dummy", 1) == 1;
|
||||
}
|
||||
|
||||
bool RemoveKeySHA256FromEntry(const std::string& key,
|
||||
const FilePath& cache_path) {
|
||||
FilePath entry_file_path = cache_path.AppendASCII(
|
||||
disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0));
|
||||
int flags = File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE;
|
||||
File entry_file(entry_file_path, flags);
|
||||
if (!entry_file.IsValid())
|
||||
return false;
|
||||
int64_t file_length = entry_file.GetLength();
|
||||
SimpleFileEOF eof_record;
|
||||
if (file_length < static_cast<int64_t>(sizeof(eof_record)))
|
||||
return false;
|
||||
if (entry_file.Read(file_length - sizeof(eof_record),
|
||||
reinterpret_cast<char*>(&eof_record),
|
||||
sizeof(eof_record)) != sizeof(eof_record)) {
|
||||
return false;
|
||||
}
|
||||
if (eof_record.final_magic_number != disk_cache::kSimpleFinalMagicNumber ||
|
||||
(eof_record.flags & SimpleFileEOF::FLAG_HAS_KEY_SHA256) !=
|
||||
SimpleFileEOF::FLAG_HAS_KEY_SHA256) {
|
||||
return false;
|
||||
}
|
||||
// Remove the key SHA256 flag, and rewrite the header on top of the
|
||||
// SHA256. Truncate the file afterwards, and we have an identical entry
|
||||
// lacking a key SHA256.
|
||||
eof_record.flags &= ~SimpleFileEOF::FLAG_HAS_KEY_SHA256;
|
||||
if (entry_file.Write(
|
||||
file_length - sizeof(eof_record) - sizeof(net::SHA256HashValue),
|
||||
reinterpret_cast<char*>(&eof_record),
|
||||
sizeof(eof_record)) != sizeof(eof_record)) {
|
||||
return false;
|
||||
}
|
||||
if (!entry_file.SetLength(file_length - sizeof(net::SHA256HashValue))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CorruptKeySHA256FromEntry(const std::string& key,
|
||||
const base::FilePath& cache_path) {
|
||||
FilePath entry_file_path = cache_path.AppendASCII(
|
||||
disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0));
|
||||
int flags = File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE;
|
||||
File entry_file(entry_file_path, flags);
|
||||
if (!entry_file.IsValid())
|
||||
return false;
|
||||
int64_t file_length = entry_file.GetLength();
|
||||
SimpleFileEOF eof_record;
|
||||
if (file_length < static_cast<int64_t>(sizeof(eof_record)))
|
||||
return false;
|
||||
if (entry_file.Read(file_length - sizeof(eof_record),
|
||||
reinterpret_cast<char*>(&eof_record),
|
||||
sizeof(eof_record)) != sizeof(eof_record)) {
|
||||
return false;
|
||||
}
|
||||
if (eof_record.final_magic_number != disk_cache::kSimpleFinalMagicNumber ||
|
||||
(eof_record.flags & SimpleFileEOF::FLAG_HAS_KEY_SHA256) !=
|
||||
SimpleFileEOF::FLAG_HAS_KEY_SHA256) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char corrupt_data[] = "corrupt data";
|
||||
static_assert(sizeof(corrupt_data) <= sizeof(net::SHA256HashValue),
|
||||
"corrupt data should not be larger than a SHA-256");
|
||||
if (entry_file.Write(
|
||||
file_length - sizeof(eof_record) - sizeof(net::SHA256HashValue),
|
||||
corrupt_data, sizeof(corrupt_data)) != sizeof(corrupt_data)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CorruptStream0LengthFromEntry(const std::string& key,
|
||||
const base::FilePath& cache_path) {
|
||||
FilePath entry_file_path = cache_path.AppendASCII(
|
||||
disk_cache::simple_util::GetFilenameFromKeyAndFileIndex(key, 0));
|
||||
int flags = File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE;
|
||||
File entry_file(entry_file_path, flags);
|
||||
if (!entry_file.IsValid())
|
||||
return false;
|
||||
int64_t file_length = entry_file.GetLength();
|
||||
SimpleFileEOF eof_record;
|
||||
if (file_length < static_cast<int64_t>(sizeof(eof_record)))
|
||||
return false;
|
||||
if (entry_file.Read(file_length - sizeof(eof_record),
|
||||
reinterpret_cast<char*>(&eof_record),
|
||||
sizeof(eof_record)) != sizeof(eof_record)) {
|
||||
return false;
|
||||
}
|
||||
if (eof_record.final_magic_number != disk_cache::kSimpleFinalMagicNumber)
|
||||
return false;
|
||||
|
||||
// Set the stream size to a clearly invalidly large value.
|
||||
eof_record.stream_size = std::numeric_limits<uint32_t>::max() - 50;
|
||||
if (entry_file.Write(file_length - sizeof(eof_record),
|
||||
reinterpret_cast<char*>(&eof_record),
|
||||
sizeof(eof_record)) != sizeof(eof_record)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace simple_util
|
||||
} // namespace disk_cache
|
||||
61
net/disk_cache/simple/simple_test_util.h
Normal file
61
net/disk_cache/simple/simple_test_util.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_TEST_UTIL_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_TEST_UTIL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/callback.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
namespace simple_util {
|
||||
|
||||
// Immutable array with compile-time bound-checking.
|
||||
template <typename T, size_t Size>
|
||||
class ImmutableArray {
|
||||
public:
|
||||
static const size_t size = Size;
|
||||
|
||||
ImmutableArray(const base::Callback<T (size_t index)>& initializer) {
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
data_[i] = initializer.Run(i);
|
||||
}
|
||||
|
||||
template <size_t Index>
|
||||
const T& at() const {
|
||||
static_assert(Index < size, "array out of bounds");
|
||||
return data_[Index];
|
||||
}
|
||||
|
||||
private:
|
||||
T data_[size];
|
||||
};
|
||||
|
||||
// Creates a corrupt file to be used in tests.
|
||||
bool CreateCorruptFileForTests(const std::string& key,
|
||||
const base::FilePath& cache_path);
|
||||
|
||||
// Removes the key SHA256 from an entry.
|
||||
bool RemoveKeySHA256FromEntry(const std::string& key,
|
||||
const base::FilePath& cache_path);
|
||||
|
||||
// Modifies the key SHA256 from an entry so that it is corrupt.
|
||||
bool CorruptKeySHA256FromEntry(const std::string& key,
|
||||
const base::FilePath& cache_path);
|
||||
|
||||
// Modifies the stream 0 length field from an entry so it is invalid.
|
||||
bool CorruptStream0LengthFromEntry(const std::string& key,
|
||||
const base::FilePath& cache_path);
|
||||
|
||||
} // namespace simple_util
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_TEST_UTIL_H_
|
||||
127
net/disk_cache/simple/simple_util.cc
Normal file
127
net/disk_cache/simple/simple_util.cc
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_util.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/format_macros.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/sha1.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "base/time/time.h"
|
||||
#include "net/disk_cache/simple/simple_entry_format.h"
|
||||
#include "third_party/zlib/zlib.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Size of the uint64_t hash_key number in Hex format in a string.
|
||||
const size_t kEntryHashKeyAsHexStringSize = 2 * sizeof(uint64_t);
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace simple_util {
|
||||
|
||||
std::string ConvertEntryHashKeyToHexString(uint64_t hash_key) {
|
||||
const std::string hash_key_str = base::StringPrintf("%016" PRIx64, hash_key);
|
||||
DCHECK_EQ(kEntryHashKeyAsHexStringSize, hash_key_str.size());
|
||||
return hash_key_str;
|
||||
}
|
||||
|
||||
std::string GetEntryHashKeyAsHexString(const std::string& key) {
|
||||
std::string hash_key_str =
|
||||
ConvertEntryHashKeyToHexString(GetEntryHashKey(key));
|
||||
DCHECK_EQ(kEntryHashKeyAsHexStringSize, hash_key_str.size());
|
||||
return hash_key_str;
|
||||
}
|
||||
|
||||
bool GetEntryHashKeyFromHexString(const base::StringPiece& hash_key,
|
||||
uint64_t* hash_key_out) {
|
||||
if (hash_key.size() != kEntryHashKeyAsHexStringSize) {
|
||||
return false;
|
||||
}
|
||||
return base::HexStringToUInt64(hash_key, hash_key_out);
|
||||
}
|
||||
|
||||
uint64_t GetEntryHashKey(const std::string& key) {
|
||||
union {
|
||||
unsigned char sha_hash[base::kSHA1Length];
|
||||
uint64_t key_hash;
|
||||
} u;
|
||||
base::SHA1HashBytes(reinterpret_cast<const unsigned char*>(key.data()),
|
||||
key.size(), u.sha_hash);
|
||||
return u.key_hash;
|
||||
}
|
||||
|
||||
std::string GetFilenameFromEntryFileKeyAndFileIndex(
|
||||
const SimpleFileTracker::EntryFileKey& key,
|
||||
int file_index) {
|
||||
// TODO(morlovich): create a todelete_..._ file for a non-zero
|
||||
// doom_generation.
|
||||
DCHECK_EQ(0u, key.doom_generation);
|
||||
return base::StringPrintf("%016" PRIx64 "_%1d", key.entry_hash, file_index);
|
||||
}
|
||||
|
||||
std::string GetSparseFilenameFromEntryFileKey(
|
||||
const SimpleFileTracker::EntryFileKey& key) {
|
||||
// TODO(morlovich): create a todelete_..._ file for a non-zero
|
||||
// doom_generation.
|
||||
DCHECK_EQ(0u, key.doom_generation);
|
||||
return base::StringPrintf("%016" PRIx64 "_s", key.entry_hash);
|
||||
}
|
||||
|
||||
std::string GetFilenameFromKeyAndFileIndex(const std::string& key,
|
||||
int file_index) {
|
||||
return GetEntryHashKeyAsHexString(key) +
|
||||
base::StringPrintf("_%1d", file_index);
|
||||
}
|
||||
|
||||
size_t GetHeaderSize(size_t key_length) {
|
||||
return sizeof(SimpleFileHeader) + key_length;
|
||||
}
|
||||
|
||||
int32_t GetDataSizeFromFileSize(size_t key_length, int64_t file_size) {
|
||||
int64_t data_size =
|
||||
file_size - key_length - sizeof(SimpleFileHeader) - sizeof(SimpleFileEOF);
|
||||
return base::checked_cast<int32_t>(data_size);
|
||||
}
|
||||
|
||||
int64_t GetFileSizeFromDataSize(size_t key_length, int32_t data_size) {
|
||||
return data_size + key_length + sizeof(SimpleFileHeader) +
|
||||
sizeof(SimpleFileEOF);
|
||||
}
|
||||
|
||||
int GetFileIndexFromStreamIndex(int stream_index) {
|
||||
return (stream_index == 2) ? 1 : 0;
|
||||
}
|
||||
|
||||
bool GetMTime(const base::FilePath& path, base::Time* out_mtime) {
|
||||
DCHECK(out_mtime);
|
||||
base::File::Info file_info;
|
||||
if (!base::GetFileInfo(path, &file_info))
|
||||
return false;
|
||||
*out_mtime = file_info.last_modified;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t Crc32(const char* data, int length) {
|
||||
uint32_t empty_crc = crc32(0, Z_NULL, 0);
|
||||
if (length == 0)
|
||||
return empty_crc;
|
||||
return crc32(empty_crc, reinterpret_cast<const Bytef*>(data), length);
|
||||
}
|
||||
|
||||
uint32_t IncrementalCrc32(uint32_t previous_crc, const char* data, int length) {
|
||||
return crc32(previous_crc, reinterpret_cast<const Bytef*>(data), length);
|
||||
}
|
||||
|
||||
} // namespace simple_util
|
||||
|
||||
} // namespace disk_cache
|
||||
96
net/disk_cache/simple/simple_util.h
Normal file
96
net/disk_cache/simple/simple_util.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef NET_DISK_CACHE_SIMPLE_SIMPLE_UTIL_H_
|
||||
#define NET_DISK_CACHE_SIMPLE_SIMPLE_UTIL_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "net/base/net_export.h"
|
||||
#include "net/disk_cache/simple/simple_file_tracker.h"
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
class Time;
|
||||
}
|
||||
|
||||
namespace disk_cache {
|
||||
|
||||
namespace simple_util {
|
||||
|
||||
NET_EXPORT_PRIVATE std::string ConvertEntryHashKeyToHexString(
|
||||
uint64_t hash_key);
|
||||
|
||||
// |key| is the regular cache key, such as an URL.
|
||||
// Returns the Hex ascii representation of the uint64_t hash_key.
|
||||
NET_EXPORT_PRIVATE std::string GetEntryHashKeyAsHexString(
|
||||
const std::string& key);
|
||||
|
||||
// |key| is the regular HTTP Cache key, which is a URL.
|
||||
// Returns the hash of the key as uint64_t.
|
||||
NET_EXPORT_PRIVATE uint64_t GetEntryHashKey(const std::string& key);
|
||||
|
||||
// Parses the |hash_key| string into a uint64_t buffer.
|
||||
// |hash_key| string must be of the form: FFFFFFFFFFFFFFFF .
|
||||
NET_EXPORT_PRIVATE bool GetEntryHashKeyFromHexString(
|
||||
const base::StringPiece& hash_key,
|
||||
uint64_t* hash_key_out);
|
||||
|
||||
// Given a |key| for a (potential) entry in the simple backend and the |index|
|
||||
// of a stream on that entry, returns the filename in which that stream would be
|
||||
// stored.
|
||||
NET_EXPORT_PRIVATE std::string GetFilenameFromKeyAndFileIndex(
|
||||
const std::string& key,
|
||||
int file_index);
|
||||
|
||||
// Same as |GetFilenameFromKeyAndIndex| above, but using a numeric key.
|
||||
NET_EXPORT_PRIVATE std::string GetFilenameFromEntryFileKeyAndFileIndex(
|
||||
const SimpleFileTracker::EntryFileKey& key,
|
||||
int file_index);
|
||||
|
||||
// Given a |key| for an entry, returns the name of the sparse data file.
|
||||
NET_EXPORT_PRIVATE std::string GetSparseFilenameFromEntryFileKey(
|
||||
const SimpleFileTracker::EntryFileKey& key);
|
||||
|
||||
// Given the size of a key, the size in bytes of the header at the beginning
|
||||
// of a simple cache file.
|
||||
size_t GetHeaderSize(size_t key_length);
|
||||
|
||||
// Given the size of a file holding a stream in the simple backend and the key
|
||||
// to an entry, returns the number of bytes in the stream.
|
||||
NET_EXPORT_PRIVATE int32_t GetDataSizeFromFileSize(size_t key_length,
|
||||
int64_t file_size);
|
||||
|
||||
// Given the size of a stream in the simple backend and the key to an entry,
|
||||
// returns the number of bytes in the file.
|
||||
NET_EXPORT_PRIVATE int64_t GetFileSizeFromDataSize(size_t key_length,
|
||||
int32_t data_size);
|
||||
|
||||
// Given the stream index, returns the number of the file the stream is stored
|
||||
// in.
|
||||
NET_EXPORT_PRIVATE int GetFileIndexFromStreamIndex(int stream_index);
|
||||
|
||||
// Fills |out_time| with the time the file last modified time. Unlike the
|
||||
// functions in file.h, the time resolution is milliseconds.
|
||||
NET_EXPORT_PRIVATE bool GetMTime(const base::FilePath& path,
|
||||
base::Time* out_mtime);
|
||||
|
||||
// Deletes a file, insuring POSIX semantics. Provided that all open handles to
|
||||
// this file were opened with File::FLAG_SHARE_DELETE, it is possible to delete
|
||||
// an open file and continue to use that file. After deleting an open file, it
|
||||
// is possible to immediately create a new file with the same name.
|
||||
NET_EXPORT_PRIVATE bool SimpleCacheDeleteFile(const base::FilePath& path);
|
||||
|
||||
uint32_t Crc32(const char* data, int length);
|
||||
|
||||
uint32_t IncrementalCrc32(uint32_t previous_crc, const char* data, int length);
|
||||
|
||||
} // namespace simple_util
|
||||
|
||||
} // namespace disk_cache
|
||||
|
||||
#endif // NET_DISK_CACHE_SIMPLE_SIMPLE_UTIL_H_
|
||||
17
net/disk_cache/simple/simple_util_posix.cc
Normal file
17
net/disk_cache/simple/simple_util_posix.cc
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "net/disk_cache/simple/simple_util.h"
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
|
||||
namespace disk_cache {
|
||||
namespace simple_util {
|
||||
|
||||
bool SimpleCacheDeleteFile(const base::FilePath& path) {
|
||||
return base::DeleteFile(path, false);
|
||||
}
|
||||
|
||||
} // namespace simple_util
|
||||
} // namespace disk_cache
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user