| // 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 |