913 lines
36 KiB
C++
913 lines
36 KiB
C++
// Copyright 2013 The Chromium Authors
|
|
// 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>
|
|
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/task/sequenced_task_runner.h"
|
|
#include "base/task/thread_pool.h"
|
|
#include "build/build_config.h"
|
|
|
|
#if BUILDFLAG(IS_POSIX)
|
|
#include <sys/resource.h>
|
|
#endif
|
|
|
|
#include "base/files/file_util.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/lazy_instance.h"
|
|
#include "base/location.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/metrics/field_trial.h"
|
|
#include "base/metrics/field_trial_params.h"
|
|
#include "base/metrics/histogram_functions.h"
|
|
#include "base/metrics/histogram_macros.h"
|
|
#include "base/system/sys_info.h"
|
|
#include "base/task/thread_pool/thread_pool_instance.h"
|
|
#include "base/time/time.h"
|
|
#include "build/build_config.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/base/prioritized_task_runner.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_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::FilePath;
|
|
using base::Time;
|
|
|
|
namespace disk_cache {
|
|
|
|
namespace {
|
|
|
|
// Maximum fraction of the cache that one entry can consume.
|
|
const int kMaxFileRatio = 8;
|
|
|
|
// Native code entries can be large. Rather than increasing the overall cache
|
|
// size, allow an individual entry to occupy up to half of the cache.
|
|
const int kMaxNativeCodeFileRatio = 2;
|
|
|
|
// Overrides the above.
|
|
const int64_t kMinFileSizeLimit = 5 * 1024 * 1024;
|
|
|
|
// 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.
|
|
SimpleCacheConsistencyResult FileStructureConsistent(
|
|
BackendFileOperations* file_operations,
|
|
const base::FilePath& path) {
|
|
if (!file_operations->PathExists(path) &&
|
|
!file_operations->CreateDirectory(path)) {
|
|
LOG(ERROR) << "Failed to create directory: " << path.LossyDisplayName();
|
|
return SimpleCacheConsistencyResult::kCreateDirectoryFailed;
|
|
}
|
|
return disk_cache::UpgradeSimpleCacheOnDisk(file_operations, path);
|
|
}
|
|
|
|
// A context used by a BarrierCompletionCallback to track state.
|
|
struct BarrierContext {
|
|
explicit BarrierContext(net::CompletionOnceCallback final_callback,
|
|
int expected)
|
|
: final_callback_(std::move(final_callback)), expected(expected) {}
|
|
|
|
net::CompletionOnceCallback final_callback_;
|
|
const int expected;
|
|
int count = 0;
|
|
bool had_error = false;
|
|
};
|
|
|
|
void BarrierCompletionCallbackImpl(
|
|
BarrierContext* context,
|
|
int result) {
|
|
DCHECK_GT(context->expected, context->count);
|
|
if (context->had_error)
|
|
return;
|
|
if (result != net::OK) {
|
|
context->had_error = true;
|
|
std::move(context->final_callback_).Run(result);
|
|
return;
|
|
}
|
|
++context->count;
|
|
if (context->count == context->expected)
|
|
std::move(context->final_callback_).Run(net::OK);
|
|
}
|
|
|
|
// A barrier completion callback is a repeatable callback 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.
|
|
base::RepeatingCallback<void(int)> MakeBarrierCompletionCallback(
|
|
int count,
|
|
net::CompletionOnceCallback final_callback) {
|
|
BarrierContext* context =
|
|
new BarrierContext(std::move(final_callback), count);
|
|
return base::BindRepeating(&BarrierCompletionCallbackImpl,
|
|
base::Owned(context));
|
|
}
|
|
|
|
// A short bindable thunk that ensures a completion callback is always called
|
|
// after running an operation asynchronously. Checks for backend liveness first.
|
|
void RunOperationAndCallback(
|
|
base::WeakPtr<SimpleBackendImpl> backend,
|
|
base::OnceCallback<net::Error(net::CompletionOnceCallback)> operation,
|
|
net::CompletionOnceCallback operation_callback) {
|
|
if (!backend)
|
|
return;
|
|
|
|
auto split_callback = base::SplitOnceCallback(std::move(operation_callback));
|
|
const int operation_result =
|
|
std::move(operation).Run(std::move(split_callback.first));
|
|
if (operation_result != net::ERR_IO_PENDING && split_callback.second)
|
|
std::move(split_callback.second).Run(operation_result);
|
|
}
|
|
|
|
// Same but for things that work with EntryResult.
|
|
void RunEntryResultOperationAndCallback(
|
|
base::WeakPtr<SimpleBackendImpl> backend,
|
|
base::OnceCallback<EntryResult(EntryResultCallback)> operation,
|
|
EntryResultCallback operation_callback) {
|
|
if (!backend)
|
|
return;
|
|
|
|
auto split_callback = base::SplitOnceCallback(std::move(operation_callback));
|
|
EntryResult operation_result =
|
|
std::move(operation).Run(std::move(split_callback.first));
|
|
if (operation_result.net_error() != net::ERR_IO_PENDING &&
|
|
split_callback.second) {
|
|
std::move(split_callback.second).Run(std::move(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);
|
|
}
|
|
}
|
|
|
|
SimpleEntryImpl::OperationsMode CacheTypeToOperationsMode(net::CacheType type) {
|
|
return (type == net::DISK_CACHE || type == net::GENERATED_BYTE_CODE_CACHE ||
|
|
type == net::GENERATED_NATIVE_CODE_CACHE ||
|
|
type == net::GENERATED_WEBUI_BYTE_CODE_CACHE)
|
|
? SimpleEntryImpl::OPTIMISTIC_OPERATIONS
|
|
: SimpleEntryImpl::NON_OPTIMISTIC_OPERATIONS;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
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) {
|
|
return base::WrapUnique(new ActiveEntryProxy(entry_hash, backend));
|
|
}
|
|
|
|
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(
|
|
scoped_refptr<BackendFileOperationsFactory> file_operations_factory,
|
|
const FilePath& path,
|
|
scoped_refptr<BackendCleanupTracker> cleanup_tracker,
|
|
SimpleFileTracker* file_tracker,
|
|
int64_t max_bytes,
|
|
net::CacheType cache_type,
|
|
net::NetLog* net_log)
|
|
: Backend(cache_type),
|
|
file_operations_factory_(
|
|
file_operations_factory
|
|
? std::move(file_operations_factory)
|
|
: base::MakeRefCounted<TrivialFileOperationsFactory>()),
|
|
cleanup_tracker_(std::move(cleanup_tracker)),
|
|
file_tracker_(file_tracker ? file_tracker
|
|
: g_simple_file_tracker.Pointer()),
|
|
path_(path),
|
|
orig_max_size_(max_bytes),
|
|
entry_operations_mode_(CacheTypeToOperationsMode(cache_type)),
|
|
post_doom_waiting_(
|
|
base::MakeRefCounted<SimplePostDoomWaiterTable>(cache_type)),
|
|
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;
|
|
}
|
|
|
|
SimpleBackendImpl::~SimpleBackendImpl() {
|
|
// Write the index out if there is a pending write from a
|
|
// previous operation.
|
|
if (index_->HasPendingWrite())
|
|
index_->WriteToDisk(SimpleIndex::INDEX_WRITE_REASON_SHUTDOWN);
|
|
}
|
|
|
|
void SimpleBackendImpl::SetTaskRunnerForTesting(
|
|
scoped_refptr<base::SequencedTaskRunner> task_runner) {
|
|
prioritized_task_runner_ =
|
|
base::MakeRefCounted<net::PrioritizedTaskRunner>(kWorkerPoolTaskTraits);
|
|
prioritized_task_runner_->SetTaskRunnerForTesting( // IN-TEST
|
|
std::move(task_runner));
|
|
}
|
|
|
|
void SimpleBackendImpl::Init(CompletionOnceCallback completion_callback) {
|
|
auto index_task_runner = base::ThreadPool::CreateSequencedTaskRunner(
|
|
{base::MayBlock(), base::WithBaseSyncPrimitives(),
|
|
base::TaskPriority::USER_BLOCKING,
|
|
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
|
|
|
|
prioritized_task_runner_ =
|
|
base::MakeRefCounted<net::PrioritizedTaskRunner>(kWorkerPoolTaskTraits);
|
|
|
|
index_ = std::make_unique<SimpleIndex>(
|
|
base::SequencedTaskRunner::GetCurrentDefault(), cleanup_tracker_.get(),
|
|
this, GetCacheType(),
|
|
std::make_unique<SimpleIndexFile>(
|
|
index_task_runner, file_operations_factory_, GetCacheType(), path_));
|
|
index_->ExecuteWhenReady(
|
|
base::BindOnce(&RecordIndexLoad, GetCacheType(), base::TimeTicks::Now()));
|
|
|
|
auto file_operations = file_operations_factory_->Create(index_task_runner);
|
|
index_task_runner->PostTaskAndReplyWithResult(
|
|
FROM_HERE,
|
|
base::BindOnce(&SimpleBackendImpl::InitCacheStructureOnDisk,
|
|
std::move(file_operations), path_, orig_max_size_,
|
|
GetCacheType()),
|
|
base::BindOnce(&SimpleBackendImpl::InitializeIndex, AsWeakPtr(),
|
|
std::move(completion_callback)));
|
|
}
|
|
|
|
bool SimpleBackendImpl::SetMaxSize(int64_t max_bytes) {
|
|
if (max_bytes < 0)
|
|
return false;
|
|
orig_max_size_ = max_bytes;
|
|
index_->SetMaxSize(max_bytes);
|
|
return true;
|
|
}
|
|
|
|
int64_t SimpleBackendImpl::MaxFileSize() const {
|
|
uint64_t file_size_ratio = GetCacheType() == net::GENERATED_NATIVE_CODE_CACHE
|
|
? kMaxNativeCodeFileRatio
|
|
: kMaxFileRatio;
|
|
return std::max(
|
|
base::saturated_cast<int64_t>(index_->max_size() / file_size_ratio),
|
|
kMinFileSizeLimit);
|
|
}
|
|
|
|
scoped_refptr<SimplePostDoomWaiterTable> SimpleBackendImpl::OnDoomStart(
|
|
uint64_t entry_hash) {
|
|
post_doom_waiting_->OnDoomStart(entry_hash);
|
|
return post_doom_waiting_;
|
|
}
|
|
|
|
void SimpleBackendImpl::DoomEntries(std::vector<uint64_t>* entry_hashes,
|
|
net::CompletionOnceCallback callback) {
|
|
auto mass_doom_entry_hashes = std::make_unique<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::DeleteEntrySetFiles 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) &&
|
|
!post_doom_waiting_->Has(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);
|
|
}
|
|
|
|
base::RepeatingCallback<void(int)> barrier_callback =
|
|
MakeBarrierCompletionCallback(to_doom_individually_hashes.size() + 1,
|
|
std::move(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
|
|
// std::move() before mass_doom_entry_hashes.get().
|
|
std::vector<uint64_t>* mass_doom_entry_hashes_ptr =
|
|
mass_doom_entry_hashes.get();
|
|
|
|
// We don't use priorities (i.e., `prioritized_task_runner_`) here because
|
|
// we don't actually have them here (since this is for eviction based on
|
|
// index).
|
|
auto task_runner =
|
|
base::ThreadPool::CreateSequencedTaskRunner(kWorkerPoolTaskTraits);
|
|
task_runner->PostTaskAndReplyWithResult(
|
|
FROM_HERE,
|
|
base::BindOnce(&SimpleSynchronousEntry::DeleteEntrySetFiles,
|
|
mass_doom_entry_hashes_ptr, path_,
|
|
file_operations_factory_->CreateUnbound()),
|
|
base::BindOnce(&SimpleBackendImpl::DoomEntriesComplete, AsWeakPtr(),
|
|
std::move(mass_doom_entry_hashes), barrier_callback));
|
|
}
|
|
|
|
int32_t SimpleBackendImpl::GetEntryCount() const {
|
|
// TODO(pasko): Use directory file count when index is not ready.
|
|
return index_->GetEntryCount();
|
|
}
|
|
|
|
EntryResult SimpleBackendImpl::OpenEntry(const std::string& key,
|
|
net::RequestPriority request_priority,
|
|
EntryResultCallback callback) {
|
|
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
|
|
|
std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
|
|
scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveOrDoomedEntry(
|
|
entry_hash, key, request_priority, &post_doom);
|
|
if (!simple_entry) {
|
|
if (post_doom->empty() &&
|
|
entry_operations_mode_ == SimpleEntryImpl::OPTIMISTIC_OPERATIONS) {
|
|
// The entry is doomed, and no other backend operations are queued for the
|
|
// entry, thus the open must fail and it's safe to return synchronously.
|
|
net::NetLogWithSource log_for_entry(net::NetLogWithSource::Make(
|
|
net_log_, net::NetLogSourceType::DISK_CACHE_ENTRY));
|
|
log_for_entry.AddEvent(
|
|
net::NetLogEventType::SIMPLE_CACHE_ENTRY_OPEN_CALL);
|
|
log_for_entry.AddEventWithNetErrorCode(
|
|
net::NetLogEventType::SIMPLE_CACHE_ENTRY_OPEN_END, net::ERR_FAILED);
|
|
return EntryResult::MakeError(net::ERR_FAILED);
|
|
}
|
|
|
|
base::OnceCallback<EntryResult(EntryResultCallback)> operation =
|
|
base::BindOnce(&SimpleBackendImpl::OpenEntry, base::Unretained(this),
|
|
key, request_priority);
|
|
post_doom->emplace_back(base::BindOnce(&RunEntryResultOperationAndCallback,
|
|
AsWeakPtr(), std::move(operation),
|
|
std::move(callback)));
|
|
return EntryResult::MakeError(net::ERR_IO_PENDING);
|
|
}
|
|
return simple_entry->OpenEntry(std::move(callback));
|
|
}
|
|
|
|
EntryResult SimpleBackendImpl::CreateEntry(
|
|
const std::string& key,
|
|
net::RequestPriority request_priority,
|
|
EntryResultCallback callback) {
|
|
DCHECK_LT(0u, key.size());
|
|
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
|
|
|
std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
|
|
scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveOrDoomedEntry(
|
|
entry_hash, key, request_priority, &post_doom);
|
|
|
|
// If couldn't grab an entry object due to pending doom, see if circumstances
|
|
// are right for an optimistic create.
|
|
if (!simple_entry) {
|
|
simple_entry = MaybeOptimisticCreateForPostDoom(
|
|
entry_hash, key, request_priority, post_doom);
|
|
}
|
|
|
|
// If that doesn't work either, retry this once doom is done.
|
|
if (!simple_entry) {
|
|
base::OnceCallback<EntryResult(EntryResultCallback)> operation =
|
|
base::BindOnce(&SimpleBackendImpl::CreateEntry, base::Unretained(this),
|
|
key, request_priority);
|
|
post_doom->emplace_back(base::BindOnce(&RunEntryResultOperationAndCallback,
|
|
AsWeakPtr(), std::move(operation),
|
|
std::move(callback)));
|
|
return EntryResult::MakeError(net::ERR_IO_PENDING);
|
|
}
|
|
|
|
return simple_entry->CreateEntry(std::move(callback));
|
|
}
|
|
|
|
EntryResult SimpleBackendImpl::OpenOrCreateEntry(
|
|
const std::string& key,
|
|
net::RequestPriority request_priority,
|
|
EntryResultCallback callback) {
|
|
DCHECK_LT(0u, key.size());
|
|
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
|
|
|
std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
|
|
scoped_refptr<SimpleEntryImpl> simple_entry = CreateOrFindActiveOrDoomedEntry(
|
|
entry_hash, key, request_priority, &post_doom);
|
|
|
|
// If couldn't grab an entry object due to pending doom, see if circumstances
|
|
// are right for an optimistic create.
|
|
if (!simple_entry) {
|
|
simple_entry = MaybeOptimisticCreateForPostDoom(
|
|
entry_hash, key, request_priority, post_doom);
|
|
if (simple_entry) {
|
|
return simple_entry->CreateEntry(std::move(callback));
|
|
} else {
|
|
// If that doesn't work either, retry this once doom is done.
|
|
base::OnceCallback<EntryResult(EntryResultCallback)> operation =
|
|
base::BindOnce(&SimpleBackendImpl::OpenOrCreateEntry,
|
|
base::Unretained(this), key, request_priority);
|
|
post_doom->emplace_back(
|
|
base::BindOnce(&RunEntryResultOperationAndCallback, AsWeakPtr(),
|
|
std::move(operation), std::move(callback)));
|
|
return EntryResult::MakeError(net::ERR_IO_PENDING);
|
|
}
|
|
}
|
|
|
|
return simple_entry->OpenOrCreateEntry(std::move(callback));
|
|
}
|
|
|
|
scoped_refptr<SimpleEntryImpl>
|
|
SimpleBackendImpl::MaybeOptimisticCreateForPostDoom(
|
|
uint64_t entry_hash,
|
|
const std::string& key,
|
|
net::RequestPriority request_priority,
|
|
std::vector<SimplePostDoomWaiter>* post_doom) {
|
|
scoped_refptr<SimpleEntryImpl> 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 = base::MakeRefCounted<SimpleEntryImpl>(
|
|
GetCacheType(), path_, cleanup_tracker_.get(), entry_hash,
|
|
entry_operations_mode_, this, file_tracker_, file_operations_factory_,
|
|
net_log_, GetNewEntryPriority(request_priority));
|
|
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->emplace_back(base::BindOnce(
|
|
&SimpleEntryImpl::NotifyDoomBeforeCreateComplete, simple_entry));
|
|
DCHECK(insert_result.second);
|
|
}
|
|
|
|
return simple_entry;
|
|
}
|
|
|
|
net::Error SimpleBackendImpl::DoomEntry(const std::string& key,
|
|
net::RequestPriority priority,
|
|
CompletionOnceCallback callback) {
|
|
const uint64_t entry_hash = simple_util::GetEntryHashKey(key);
|
|
|
|
std::vector<SimplePostDoomWaiter>* post_doom = nullptr;
|
|
scoped_refptr<SimpleEntryImpl> simple_entry =
|
|
CreateOrFindActiveOrDoomedEntry(entry_hash, key, priority, &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.
|
|
base::OnceCallback<net::Error(CompletionOnceCallback)> operation =
|
|
base::BindOnce(&SimpleBackendImpl::DoomEntry, base::Unretained(this),
|
|
key, priority);
|
|
post_doom->emplace_back(base::BindOnce(&RunOperationAndCallback,
|
|
AsWeakPtr(), std::move(operation),
|
|
std::move(callback)));
|
|
return net::ERR_IO_PENDING;
|
|
}
|
|
|
|
return simple_entry->DoomEntry(std::move(callback));
|
|
}
|
|
|
|
net::Error SimpleBackendImpl::DoomAllEntries(CompletionOnceCallback callback) {
|
|
return DoomEntriesBetween(Time(), Time(), std::move(callback));
|
|
}
|
|
|
|
net::Error SimpleBackendImpl::DoomEntriesBetween(
|
|
const Time initial_time,
|
|
const Time end_time,
|
|
CompletionOnceCallback callback) {
|
|
index_->ExecuteWhenReady(base::BindOnce(&SimpleBackendImpl::IndexReadyForDoom,
|
|
AsWeakPtr(), initial_time, end_time,
|
|
std::move(callback)));
|
|
return net::ERR_IO_PENDING;
|
|
}
|
|
|
|
net::Error SimpleBackendImpl::DoomEntriesSince(
|
|
const Time initial_time,
|
|
CompletionOnceCallback callback) {
|
|
return DoomEntriesBetween(initial_time, Time(), std::move(callback));
|
|
}
|
|
|
|
int64_t SimpleBackendImpl::CalculateSizeOfAllEntries(
|
|
Int64CompletionOnceCallback callback) {
|
|
index_->ExecuteWhenReady(
|
|
base::BindOnce(&SimpleBackendImpl::IndexReadyForSizeCalculation,
|
|
AsWeakPtr(), std::move(callback)));
|
|
return net::ERR_IO_PENDING;
|
|
}
|
|
|
|
int64_t SimpleBackendImpl::CalculateSizeOfEntriesBetween(
|
|
base::Time initial_time,
|
|
base::Time end_time,
|
|
Int64CompletionOnceCallback callback) {
|
|
index_->ExecuteWhenReady(
|
|
base::BindOnce(&SimpleBackendImpl::IndexReadyForSizeBetweenCalculation,
|
|
AsWeakPtr(), initial_time, end_time, std::move(callback)));
|
|
return net::ERR_IO_PENDING;
|
|
}
|
|
|
|
class SimpleBackendImpl::SimpleIterator final : public Iterator {
|
|
public:
|
|
explicit SimpleIterator(base::WeakPtr<SimpleBackendImpl> backend)
|
|
: backend_(backend) {}
|
|
|
|
// From Backend::Iterator:
|
|
EntryResult OpenNextEntry(EntryResultCallback callback) override {
|
|
if (!backend_)
|
|
return EntryResult::MakeError(net::ERR_FAILED);
|
|
CompletionOnceCallback open_next_entry_impl =
|
|
base::BindOnce(&SimpleIterator::OpenNextEntryImpl,
|
|
weak_factory_.GetWeakPtr(), std::move(callback));
|
|
backend_->index_->ExecuteWhenReady(std::move(open_next_entry_impl));
|
|
return EntryResult::MakeError(net::ERR_IO_PENDING);
|
|
}
|
|
|
|
void OpenNextEntryImpl(EntryResultCallback callback,
|
|
int index_initialization_error_code) {
|
|
if (!backend_) {
|
|
std::move(callback).Run(EntryResult::MakeError(net::ERR_FAILED));
|
|
return;
|
|
}
|
|
if (index_initialization_error_code != net::OK) {
|
|
std::move(callback).Run(EntryResult::MakeError(
|
|
static_cast<net::Error>(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)) {
|
|
auto split_callback = base::SplitOnceCallback(std::move(callback));
|
|
callback = std::move(split_callback.first);
|
|
EntryResultCallback continue_iteration = base::BindOnce(
|
|
&SimpleIterator::CheckIterationReturnValue,
|
|
weak_factory_.GetWeakPtr(), std::move(split_callback.second));
|
|
EntryResult open_result = backend_->OpenEntryFromHash(
|
|
entry_hash, std::move(continue_iteration));
|
|
if (open_result.net_error() == net::ERR_IO_PENDING)
|
|
return;
|
|
if (open_result.net_error() != net::ERR_FAILED) {
|
|
std::move(callback).Run(std::move(open_result));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
std::move(callback).Run(EntryResult::MakeError(net::ERR_FAILED));
|
|
}
|
|
|
|
void CheckIterationReturnValue(EntryResultCallback callback,
|
|
EntryResult result) {
|
|
if (result.net_error() == net::ERR_FAILED) {
|
|
OpenNextEntry(std::move(callback));
|
|
return;
|
|
}
|
|
std::move(callback).Run(std::move(result));
|
|
}
|
|
|
|
private:
|
|
base::WeakPtr<SimpleBackendImpl> backend_;
|
|
std::unique_ptr<std::vector<uint64_t>> hashes_to_enumerate_;
|
|
base::WeakPtrFactory<SimpleIterator> weak_factory_{this};
|
|
};
|
|
|
|
std::unique_ptr<Backend::Iterator> SimpleBackendImpl::CreateIterator() {
|
|
return std::make_unique<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));
|
|
}
|
|
|
|
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(CompletionOnceCallback callback,
|
|
const DiskStatResult& result) {
|
|
if (result.net_error == net::OK) {
|
|
index_->SetMaxSize(result.max_size);
|
|
#if BUILDFLAG(IS_ANDROID)
|
|
if (app_status_listener_)
|
|
index_->set_app_status_listener(app_status_listener_);
|
|
#endif
|
|
index_->Initialize(result.cache_dir_mtime);
|
|
}
|
|
std::move(callback).Run(result.net_error);
|
|
}
|
|
|
|
void SimpleBackendImpl::IndexReadyForDoom(Time initial_time,
|
|
Time end_time,
|
|
CompletionOnceCallback callback,
|
|
int result) {
|
|
if (result != net::OK) {
|
|
std::move(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(), std::move(callback));
|
|
}
|
|
|
|
void SimpleBackendImpl::IndexReadyForSizeCalculation(
|
|
Int64CompletionOnceCallback callback,
|
|
int result) {
|
|
int64_t rv = result == net::OK ? index_->GetCacheSize() : result;
|
|
std::move(callback).Run(rv);
|
|
}
|
|
|
|
void SimpleBackendImpl::IndexReadyForSizeBetweenCalculation(
|
|
base::Time initial_time,
|
|
base::Time end_time,
|
|
Int64CompletionOnceCallback callback,
|
|
int result) {
|
|
int64_t rv = result == net::OK
|
|
? index_->GetCacheSizeBetween(initial_time, end_time)
|
|
: result;
|
|
std::move(callback).Run(rv);
|
|
}
|
|
|
|
// static
|
|
SimpleBackendImpl::DiskStatResult SimpleBackendImpl::InitCacheStructureOnDisk(
|
|
std::unique_ptr<BackendFileOperations> file_operations,
|
|
const base::FilePath& path,
|
|
uint64_t suggested_max_size,
|
|
net::CacheType cache_type) {
|
|
DiskStatResult result;
|
|
result.max_size = suggested_max_size;
|
|
result.net_error = net::OK;
|
|
SimpleCacheConsistencyResult consistency =
|
|
FileStructureConsistent(file_operations.get(), path);
|
|
SIMPLE_CACHE_UMA(ENUMERATION, "ConsistencyResult", cache_type, consistency);
|
|
|
|
// If the cache structure is inconsistent make a single attempt at
|
|
// recovering it. Previously there were bugs that could cause a partially
|
|
// written fake index file to be left in an otherwise empty cache. In
|
|
// that case we can delete the index files and start over. Also, some
|
|
// consistency failures may leave an empty directory directly and we can
|
|
// retry those cases as well.
|
|
if (consistency != SimpleCacheConsistencyResult::kOK) {
|
|
bool deleted_files = disk_cache::DeleteIndexFilesIfCacheIsEmpty(path);
|
|
SIMPLE_CACHE_UMA(BOOLEAN, "DidDeleteIndexFilesAfterFailedConsistency",
|
|
cache_type, deleted_files);
|
|
if (base::IsDirectoryEmpty(path)) {
|
|
SimpleCacheConsistencyResult orig_consistency = consistency;
|
|
consistency = FileStructureConsistent(file_operations.get(), path);
|
|
SIMPLE_CACHE_UMA(ENUMERATION, "RetryConsistencyResult", cache_type,
|
|
consistency);
|
|
if (consistency == SimpleCacheConsistencyResult::kOK) {
|
|
SIMPLE_CACHE_UMA(ENUMERATION,
|
|
"OriginalConsistencyResultBeforeSuccessfulRetry",
|
|
cache_type, orig_consistency);
|
|
}
|
|
}
|
|
if (deleted_files) {
|
|
SIMPLE_CACHE_UMA(ENUMERATION, "ConsistencyResultAfterIndexFilesDeleted",
|
|
cache_type, consistency);
|
|
}
|
|
}
|
|
|
|
if (consistency != SimpleCacheConsistencyResult::kOK) {
|
|
LOG(ERROR) << "Simple Cache Backend: wrong file structure on disk: "
|
|
<< static_cast<int>(consistency)
|
|
<< " path: " << path.LossyDisplayName();
|
|
result.net_error = net::ERR_FAILED;
|
|
} else {
|
|
absl::optional<base::File::Info> file_info =
|
|
file_operations->GetFileInfo(path);
|
|
if (!file_info.has_value()) {
|
|
// Something deleted the directory between when we set it up and the
|
|
// mstat; this is not uncommon on some test fixtures which erase their
|
|
// tempdir while some worker threads may still be running.
|
|
LOG(ERROR) << "Simple Cache Backend: cache directory inaccessible right "
|
|
"after creation; path: "
|
|
<< path.LossyDisplayName();
|
|
result.net_error = net::ERR_FAILED;
|
|
} else {
|
|
result.cache_dir_mtime = file_info->last_modified;
|
|
if (!result.max_size) {
|
|
int64_t available = base::SysInfo::AmountOfFreeDiskSpace(path);
|
|
result.max_size = disk_cache::PreferredCacheSize(available, cache_type);
|
|
DCHECK(result.max_size);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
scoped_refptr<SimpleEntryImpl>
|
|
SimpleBackendImpl::CreateOrFindActiveOrDoomedEntry(
|
|
const uint64_t entry_hash,
|
|
const std::string& key,
|
|
net::RequestPriority request_priority,
|
|
std::vector<SimplePostDoomWaiter>** post_doom) {
|
|
DCHECK_EQ(entry_hash, simple_util::GetEntryHashKey(key));
|
|
|
|
// If there is a doom pending, we would want to serialize after it.
|
|
*post_doom = post_doom_waiting_->Find(entry_hash);
|
|
if (*post_doom)
|
|
return nullptr;
|
|
|
|
std::pair<EntryMap::iterator, bool> insert_result =
|
|
active_entries_.insert(EntryMap::value_type(entry_hash, nullptr));
|
|
EntryMap::iterator& it = insert_result.first;
|
|
const bool did_insert = insert_result.second;
|
|
if (did_insert) {
|
|
SimpleEntryImpl* entry = it->second = new SimpleEntryImpl(
|
|
GetCacheType(), path_, cleanup_tracker_.get(), entry_hash,
|
|
entry_operations_mode_, this, file_tracker_, file_operations_factory_,
|
|
net_log_, GetNewEntryPriority(request_priority));
|
|
entry->SetKey(key);
|
|
entry->SetActiveEntryProxy(ActiveEntryProxy::Create(entry_hash, this));
|
|
}
|
|
// TODO(jkarlin): In case of recycling a half-closed entry, we might want to
|
|
// update its priority.
|
|
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(post_doom_waiting_->Has(entry_hash));
|
|
// Re-run ourselves to handle the now-pending doom.
|
|
return CreateOrFindActiveOrDoomedEntry(entry_hash, key, request_priority,
|
|
post_doom);
|
|
}
|
|
return base::WrapRefCounted(it->second);
|
|
}
|
|
|
|
EntryResult SimpleBackendImpl::OpenEntryFromHash(uint64_t entry_hash,
|
|
EntryResultCallback callback) {
|
|
std::vector<SimplePostDoomWaiter>* post_doom =
|
|
post_doom_waiting_->Find(entry_hash);
|
|
if (post_doom) {
|
|
base::OnceCallback<EntryResult(EntryResultCallback)> operation =
|
|
base::BindOnce(&SimpleBackendImpl::OpenEntryFromHash,
|
|
base::Unretained(this), entry_hash);
|
|
// TODO(https://crbug.com/1019682) The cancellation behavior looks wrong.
|
|
post_doom->emplace_back(base::BindOnce(&RunEntryResultOperationAndCallback,
|
|
AsWeakPtr(), std::move(operation),
|
|
std::move(callback)));
|
|
return EntryResult::MakeError(net::ERR_IO_PENDING);
|
|
}
|
|
|
|
auto has_active = active_entries_.find(entry_hash);
|
|
if (has_active != active_entries_.end()) {
|
|
return OpenEntry(has_active->second->key(), net::HIGHEST,
|
|
std::move(callback));
|
|
}
|
|
|
|
auto simple_entry = base::MakeRefCounted<SimpleEntryImpl>(
|
|
GetCacheType(), path_, cleanup_tracker_.get(), entry_hash,
|
|
entry_operations_mode_, this, file_tracker_, file_operations_factory_,
|
|
net_log_, GetNewEntryPriority(net::HIGHEST));
|
|
EntryResultCallback backend_callback =
|
|
base::BindOnce(&SimpleBackendImpl::OnEntryOpenedFromHash, AsWeakPtr(),
|
|
entry_hash, simple_entry, std::move(callback));
|
|
return simple_entry->OpenEntry(std::move(backend_callback));
|
|
}
|
|
|
|
net::Error SimpleBackendImpl::DoomEntryFromHash(
|
|
uint64_t entry_hash,
|
|
CompletionOnceCallback callback) {
|
|
std::vector<SimplePostDoomWaiter>* post_doom =
|
|
post_doom_waiting_->Find(entry_hash);
|
|
if (post_doom) {
|
|
base::OnceCallback<net::Error(CompletionOnceCallback)> operation =
|
|
base::BindOnce(&SimpleBackendImpl::DoomEntryFromHash,
|
|
base::Unretained(this), entry_hash);
|
|
post_doom->emplace_back(base::BindOnce(&RunOperationAndCallback,
|
|
AsWeakPtr(), std::move(operation),
|
|
std::move(callback)));
|
|
return net::ERR_IO_PENDING;
|
|
}
|
|
|
|
auto active_it = active_entries_.find(entry_hash);
|
|
if (active_it != active_entries_.end())
|
|
return active_it->second->DoomEntry(std::move(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, std::move(callback));
|
|
return net::ERR_IO_PENDING;
|
|
}
|
|
|
|
void SimpleBackendImpl::OnEntryOpenedFromHash(
|
|
uint64_t hash,
|
|
const scoped_refptr<SimpleEntryImpl>& simple_entry,
|
|
EntryResultCallback callback,
|
|
EntryResult result) {
|
|
if (result.net_error() != net::OK) {
|
|
std::move(callback).Run(std::move(result));
|
|
return;
|
|
}
|
|
|
|
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));
|
|
std::move(callback).Run(std::move(result));
|
|
} 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.
|
|
Entry* entry_from_result = result.ReleaseEntry();
|
|
DCHECK_EQ(entry_from_result, simple_entry.get());
|
|
simple_entry->Close();
|
|
EntryResult reopen_result = it->second->OpenEntry(std::move(callback));
|
|
DCHECK_EQ(reopen_result.net_error(), net::ERR_IO_PENDING);
|
|
}
|
|
}
|
|
|
|
void SimpleBackendImpl::DoomEntriesComplete(
|
|
std::unique_ptr<std::vector<uint64_t>> entry_hashes,
|
|
CompletionOnceCallback callback,
|
|
int result) {
|
|
for (const uint64_t& entry_hash : *entry_hashes)
|
|
post_doom_waiting_->OnDoomComplete(entry_hash);
|
|
std::move(callback).Run(result);
|
|
}
|
|
|
|
uint32_t SimpleBackendImpl::GetNewEntryPriority(
|
|
net::RequestPriority request_priority) {
|
|
// Lower priority is better, so give high network priority the least bump.
|
|
return ((net::RequestPriority::MAXIMUM_PRIORITY - request_priority) * 10000) +
|
|
entry_count_++;
|
|
}
|
|
|
|
} // namespace disk_cache
|