Import chromium-64.0.3282.140

This commit is contained in:
klzgrad
2018-02-02 05:49:39 -05:00
commit 86b64329f6
19589 changed files with 4029211 additions and 0 deletions

4
net/disk_cache/OWNERS Normal file
View File

@@ -0,0 +1,4 @@
morlovich@chromium.org
jkarlin@chromium.org
# COMPONENT: Internals>Network>Cache

View 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

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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_

View 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

View 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_

View 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_

View 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

View 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_

View 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_

View 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

View 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

View 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_

View 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

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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

View 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

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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_

View 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_

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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

View 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

View 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
View 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_

View 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());
}

View 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_

View 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();
}

View 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_

View 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

View 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_

View 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, &current_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, &current_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

View 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_

View 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

View 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_

View File

@@ -0,0 +1,4 @@
gavinp@chromium.org
pasko@chromium.org
# COMPONENT: Internals>Network>Cache>Simple

View 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

View 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_

View 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_

View 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

View 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_

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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(), &params);
auto iter = params.find(experiments[i].param_name);
if (iter == params.end())
continue;
uint32_t param;
if (!base::StringToUint(iter->second, &param))
continue;
experiment.type = experiments[i].experiment_type;
experiment.param = param;
return experiment;
}
return experiment;
}
} // namespace disk_cache

View 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_

View 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

View 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_

View 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_

View 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_

View 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

View 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_

View 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_

View 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

View 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_

View 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

View 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

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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_

View 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

View 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_

View 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