blob: 74d284671545825f7821ee963c1e837b43088121 [file] [log] [blame]
// 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/interval.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 MemEntryWriteResult {
MEM_ENTRY_WRITE_RESULT_SUCCESS = 0,
MEM_ENTRY_WRITE_RESULT_INVALID_ARGUMENT = 1,
MEM_ENTRY_WRITE_RESULT_OVER_MAX_ENTRY_SIZE = 2,
MEM_ENTRY_WRITE_RESULT_EXCEEDED_CACHE_STORAGE_SIZE = 3,
MEM_ENTRY_WRITE_RESULT_MAX = 4,
};
void RecordWriteResult(MemEntryWriteResult result) {
UMA_HISTOGRAM_ENUMERATION("MemCache.WriteResult", result,
MEM_ENTRY_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(base::WeakPtr<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(base::WeakPtr<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) {
// !doomed_ implies backend_ != null as ~MemBackendImpl dooms everything.
if (!doomed_)
backend_->OnEntryUpdated(this);
last_used_ = Time::Now();
if (modified_enum == ENTRY_WAS_MODIFIED)
last_modified_ = last_used_;
}
void MemEntryImpl::Doom() {
// !doomed_ implies backend_ != null as ~MemBackendImpl dooms everything.
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_;
if (ref_count_ == 0 && !doomed_) {
// At this point the user is clearly done writing, so make sure there isn't
// wastage due to exponential growth of vector for main data stream.
Compact();
if (children_) {
for (const auto& child_info : *children_) {
if (child_info.second != this)
child_info.second->Compact();
}
}
}
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,
CompletionOnceCallback 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,
CompletionOnceCallback 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,
CompletionOnceCallback 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,
CompletionOnceCallback 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,
CompletionOnceCallback 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);
}
net::Error MemEntryImpl::ReadyForSparseIO(CompletionOnceCallback callback) {
return net::OK;
}
void MemEntryImpl::SetLastUsedTimeForTest(base::Time time) {
last_used_ = time;
}
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(base::WeakPtr<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() {
if (backend_)
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 (!backend_) {
// We have to fail writes after the backend is destroyed since we can't
// ensure we wouldn't use too much memory if it's gone.
RecordWriteResult(MEM_ENTRY_WRITE_RESULT_EXCEEDED_CACHE_STORAGE_SIZE);
return net::ERR_INSUFFICIENT_RESOURCES;
}
if (index < 0 || index >= kNumStreams) {
RecordWriteResult(MEM_ENTRY_WRITE_RESULT_INVALID_ARGUMENT);
return net::ERR_INVALID_ARGUMENT;
}
if (offset < 0 || buf_len < 0) {
RecordWriteResult(MEM_ENTRY_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(MEM_ENTRY_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(MEM_ENTRY_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(MEM_ENTRY_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 =
base::MakeRefCounted<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(), CompletionOnceCallback());
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;
// We can't generally do this without the backend since we need it to create
// child entries.
if (!backend_)
return net::ERR_FAILED;
if (offset < 0 || buf_len < 0)
return net::ERR_INVALID_ARGUMENT;
scoped_refptr<net::DrainableIOBuffer> io_buf =
base::MakeRefCounted<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, CompletionOnceCallback(), 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;
net::Interval<int64_t> requested(offset, offset + len);
// Find the first relevant child, if any --- may have to skip over
// one entry as it may be before the range (consider, for example,
// if the request is for [2048, 10000), while [0, 1024) is a valid range
// for the entry).
EntryMap::const_iterator i = children_->lower_bound(ToChildIndex(offset));
if (i != children_->cend() && !ChildInterval(i).Intersects(requested))
++i;
net::Interval<int64_t> found;
if (i != children_->cend() &&
requested.Intersects(ChildInterval(i), &found)) {
// Found something relevant; now just need to expand this out if next
// children are contiguous and relevant to the request.
while (true) {
++i;
net::Interval<int64_t> relevant_in_next_child;
if (i == children_->cend() ||
!requested.Intersects(ChildInterval(i), &relevant_in_next_child) ||
relevant_in_next_child.min() != found.max()) {
break;
}
found.SpanningUnion(relevant_in_next_child);
}
*start = found.min();
return found.Length();
}
*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);
auto 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;
}
net::Interval<int64_t> MemEntryImpl::ChildInterval(
MemEntryImpl::EntryMap::const_iterator i) {
DCHECK(i != children_->cend());
const MemEntryImpl* child = i->second;
// The valid range in child is [child_first_pos_, DataSize), since the child
// entry ops just use standard disk_cache::Entry API, so DataSize is
// not aware of any hole in the beginning.
int64_t child_responsibility_start = (i->first) * kMaxSparseEntrySize;
return net::Interval<int64_t>(
child_responsibility_start + child->child_first_pos_,
child_responsibility_start + child->GetDataSize(kSparseData));
}
void MemEntryImpl::Compact() {
// Stream 0 should already be fine since it's written out in a single WriteData().
data_[1].shrink_to_fit();
data_[2].shrink_to_fit();
}
} // namespace disk_cache