// 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/mem_entry_impl.h" #include "base/bind.h" #include "base/logging.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/mem_backend_impl.h" #include "net/disk_cache/net_log_parameters.h" using base::Time; 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; // Convert global offset to child index. inline int ToChildIndex(int64 offset) { return static_cast<int>(offset >> kMaxSparseEntryBits); } // Convert global offset to offset in child entry. inline int ToChildOffset(int64 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 child MemEntryImpl. Separate // function needed because child entries don't suppport GetKey(). base::Value* NetLogChildEntryCreationCallback( const disk_cache::MemEntryImpl* parent, int child_id, net::NetLog::LogLevel /* log_level */) { base::DictionaryValue* dict = new base::DictionaryValue(); dict->SetString("key", GenerateChildName(parent->GetKey(), child_id)); dict->SetBoolean("created", true); return dict; } } // namespace namespace disk_cache { MemEntryImpl::MemEntryImpl(MemBackendImpl* backend) { doomed_ = false; backend_ = backend; ref_count_ = 0; parent_ = NULL; child_id_ = 0; child_first_pos_ = 0; next_ = NULL; prev_ = NULL; for (int i = 0; i < NUM_STREAMS; i++) data_size_[i] = 0; } // ------------------------------------------------------------------------ bool MemEntryImpl::CreateEntry(const std::string& key, net::NetLog* net_log) { key_ = key; Time current = Time::Now(); last_modified_ = current; last_used_ = current; net_log_ = net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_MEMORY_CACHE_ENTRY); // Must be called after |key_| is set, so GetKey() works. net_log_.BeginEvent( net::NetLog::TYPE_DISK_CACHE_MEM_ENTRY_IMPL, CreateNetLogEntryCreationCallback(this, true)); Open(); backend_->ModifyStorageSize(0, static_cast<int32>(key.size())); return true; } void MemEntryImpl::InternalDoom() { net_log_.AddEvent(net::NetLog::TYPE_ENTRY_DOOM); doomed_ = true; if (!ref_count_) { if (type() == kParentEntry) { // If this is a parent entry, we need to doom all the child entries. if (children_.get()) { EntryMap children; children.swap(*children_); for (EntryMap::iterator i = children.begin(); i != children.end(); ++i) { // Since a pointer to this object is also saved in the map, avoid // dooming it. if (i->second != this) i->second->Doom(); } DCHECK(children_->empty()); } } else { // If this is a child entry, detach it from the parent. parent_->DetachChild(child_id_); } delete this; } } void MemEntryImpl::Open() { // Only a parent entry can be opened. // TODO(hclam): make sure it's correct to not apply the concept of ref // counting to child entry. DCHECK(type() == kParentEntry); ref_count_++; DCHECK_GE(ref_count_, 0); DCHECK(!doomed_); } bool MemEntryImpl::InUse() { if (type() == kParentEntry) { return ref_count_ > 0; } else { // A child entry is always not in use. The consequence is that a child entry // can always be evicted while the associated parent entry is currently in // used (i.e. opened). return false; } } // ------------------------------------------------------------------------ void MemEntryImpl::Doom() { if (doomed_) return; if (type() == kParentEntry) { // Perform internal doom from the backend if this is a parent entry. backend_->InternalDoomEntry(this); } else { // Manually detach from the backend and perform internal doom. backend_->RemoveFromRankingList(this); InternalDoom(); } } void MemEntryImpl::Close() { // Only a parent entry can be closed. DCHECK(type() == kParentEntry); ref_count_--; DCHECK_GE(ref_count_, 0); if (!ref_count_ && doomed_) InternalDoom(); } std::string MemEntryImpl::GetKey() const { // A child entry doesn't have key so this method should not be called. DCHECK(type() == kParentEntry); return key_; } Time MemEntryImpl::GetLastUsed() const { return last_used_; } Time MemEntryImpl::GetLastModified() const { return last_modified_; } int32 MemEntryImpl::GetDataSize(int index) const { if (index < 0 || index >= NUM_STREAMS) return 0; return data_size_[index]; } int MemEntryImpl::ReadData(int index, int offset, IOBuffer* buf, int buf_len, const CompletionCallback& callback) { if (net_log_.IsLoggingAllEvents()) { net_log_.BeginEvent( net::NetLog::TYPE_ENTRY_READ_DATA, CreateNetLogReadWriteDataCallback(index, offset, buf_len, false)); } int result = InternalReadData(index, offset, buf, buf_len); if (net_log_.IsLoggingAllEvents()) { net_log_.EndEvent( net::NetLog::TYPE_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_.IsLoggingAllEvents()) { net_log_.BeginEvent( net::NetLog::TYPE_ENTRY_WRITE_DATA, CreateNetLogReadWriteDataCallback(index, offset, buf_len, truncate)); } int result = InternalWriteData(index, offset, buf, buf_len, truncate); if (net_log_.IsLoggingAllEvents()) { net_log_.EndEvent( net::NetLog::TYPE_ENTRY_WRITE_DATA, CreateNetLogReadWriteCompleteCallback(result)); } return result; } int MemEntryImpl::ReadSparseData(int64 offset, IOBuffer* buf, int buf_len, const CompletionCallback& callback) { if (net_log_.IsLoggingAllEvents()) { net_log_.BeginEvent( net::NetLog::TYPE_SPARSE_READ, CreateNetLogSparseOperationCallback(offset, buf_len)); } int result = InternalReadSparseData(offset, buf, buf_len); if (net_log_.IsLoggingAllEvents()) net_log_.EndEvent(net::NetLog::TYPE_SPARSE_READ); return result; } int MemEntryImpl::WriteSparseData(int64 offset, IOBuffer* buf, int buf_len, const CompletionCallback& callback) { if (net_log_.IsLoggingAllEvents()) { net_log_.BeginEvent( net::NetLog::TYPE_SPARSE_WRITE, CreateNetLogSparseOperationCallback(offset, buf_len)); } int result = InternalWriteSparseData(offset, buf, buf_len); if (net_log_.IsLoggingAllEvents()) net_log_.EndEvent(net::NetLog::TYPE_SPARSE_WRITE); return result; } int MemEntryImpl::GetAvailableRange(int64 offset, int len, int64* start, const CompletionCallback& callback) { if (net_log_.IsLoggingAllEvents()) { net_log_.BeginEvent( net::NetLog::TYPE_SPARSE_GET_RANGE, CreateNetLogSparseOperationCallback(offset, len)); } int result = GetAvailableRange(offset, len, start); if (net_log_.IsLoggingAllEvents()) { net_log_.EndEvent( net::NetLog::TYPE_SPARSE_GET_RANGE, CreateNetLogGetAvailableRangeResultCallback(*start, result)); } return result; } bool MemEntryImpl::CouldBeSparse() const { DCHECK_EQ(kParentEntry, type()); return (children_.get() != NULL); } int MemEntryImpl::ReadyForSparseIO(const CompletionCallback& callback) { return net::OK; } // ------------------------------------------------------------------------ MemEntryImpl::~MemEntryImpl() { for (int i = 0; i < NUM_STREAMS; i++) backend_->ModifyStorageSize(data_size_[i], 0); backend_->ModifyStorageSize(static_cast<int32>(key_.size()), 0); net_log_.EndEvent(net::NetLog::TYPE_DISK_CACHE_MEM_ENTRY_IMPL); } int MemEntryImpl::InternalReadData(int index, int offset, IOBuffer* buf, int buf_len) { DCHECK(type() == kParentEntry || index == kSparseData); if (index < 0 || index >= NUM_STREAMS) return net::ERR_INVALID_ARGUMENT; int entry_size = GetDataSize(index); if (offset >= entry_size || offset < 0 || !buf_len) return 0; if (buf_len < 0) return net::ERR_INVALID_ARGUMENT; if (offset + buf_len > entry_size) buf_len = entry_size - offset; UpdateRank(false); memcpy(buf->data(), &(data_[index])[offset], buf_len); return buf_len; } int MemEntryImpl::InternalWriteData(int index, int offset, IOBuffer* buf, int buf_len, bool truncate) { DCHECK(type() == kParentEntry || index == kSparseData); if (index < 0 || index >= NUM_STREAMS) return net::ERR_INVALID_ARGUMENT; if (offset < 0 || buf_len < 0) 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) { return net::ERR_FAILED; } // Read the size at this point. int entry_size = GetDataSize(index); PrepareTarget(index, offset, buf_len); if (entry_size < offset + buf_len) { backend_->ModifyStorageSize(entry_size, offset + buf_len); data_size_[index] = offset + buf_len; } else if (truncate) { if (entry_size > offset + buf_len) { backend_->ModifyStorageSize(entry_size, offset + buf_len); data_size_[index] = offset + buf_len; } } UpdateRank(true); if (!buf_len) return 0; memcpy(&(data_[index])[offset], buf->data(), buf_len); return buf_len; } int MemEntryImpl::InternalReadSparseData(int64 offset, IOBuffer* buf, int buf_len) { DCHECK(type() == kParentEntry); 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 = OpenChild(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_.IsLoggingAllEvents()) { net_log_.BeginEvent( net::NetLog::TYPE_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_.IsLoggingAllEvents()) { net_log_.EndEventWithNetErrorCode( net::NetLog::TYPE_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); } UpdateRank(false); return io_buf->BytesConsumed(); } int MemEntryImpl::InternalWriteSparseData(int64 offset, IOBuffer* buf, int buf_len) { DCHECK(type() == kParentEntry); 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 = OpenChild(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_.IsLoggingAllEvents()) { net_log_.BeginEvent( net::NetLog::TYPE_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_.IsLoggingAllEvents()) { net_log_.EndEventWithNetErrorCode( net::NetLog::TYPE_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); } UpdateRank(true); return io_buf->BytesConsumed(); } int MemEntryImpl::GetAvailableRange(int64 offset, int len, int64* start) { DCHECK(type() == kParentEntry); 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 = NULL; // Find the first child and record the number of empty bytes. int empty = FindNextChild(offset, len, ¤t_child); if (current_child) { *start = offset + empty; len -= empty; // Counts the number of continuous bytes. int continuous = 0; // This loop scan for continuous bytes. while (len && current_child) { // Number of bytes available in this child. int data_size = current_child->GetDataSize(kSparseData) - ToChildOffset(*start + continuous); if (data_size > len) data_size = len; // We have found more continuous bytes so increment the count. Also // decrement the length we should scan. continuous += data_size; len -= data_size; // If the next child is discontinuous, break the loop. if (FindNextChild(*start + continuous, len, ¤t_child)) break; } return continuous; } *start = offset; return 0; } void MemEntryImpl::PrepareTarget(int index, int offset, int buf_len) { int entry_size = GetDataSize(index); if (entry_size >= offset + buf_len) return; // Not growing the stored data. if (static_cast<int>(data_[index].size()) < offset + buf_len) data_[index].resize(offset + buf_len); if (offset <= entry_size) return; // There is no "hole" on the stored data. // Cleanup the hole not written by the user. The point is to avoid returning // random stuff later on. memset(&(data_[index])[entry_size], 0, offset - entry_size); } void MemEntryImpl::UpdateRank(bool modified) { Time current = Time::Now(); last_used_ = current; if (modified) last_modified_ = current; if (!doomed_) backend_->UpdateRank(this); } bool MemEntryImpl::InitSparseInfo() { DCHECK(type() == kParentEntry); if (!children_.get()) { // 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; } bool MemEntryImpl::InitChildEntry(MemEntryImpl* parent, int child_id, net::NetLog* net_log) { DCHECK(!parent_); DCHECK(!child_id_); net_log_ = net::BoundNetLog::Make(net_log, net::NetLog::SOURCE_MEMORY_CACHE_ENTRY); net_log_.BeginEvent( net::NetLog::TYPE_DISK_CACHE_MEM_ENTRY_IMPL, base::Bind(&NetLogChildEntryCreationCallback, parent, child_id_)); parent_ = parent; child_id_ = child_id; Time current = Time::Now(); last_modified_ = current; last_used_ = current; // Insert this to the backend's ranking list. backend_->InsertIntoRankingList(this); return true; } MemEntryImpl* MemEntryImpl::OpenChild(int64 offset, bool create) { DCHECK(type() == kParentEntry); int index = ToChildIndex(offset); EntryMap::iterator i = children_->find(index); if (i != children_->end()) { return i->second; } else if (create) { MemEntryImpl* child = new MemEntryImpl(backend_); child->InitChildEntry(this, index, net_log_.net_log()); (*children_)[index] = child; return child; } return NULL; } int MemEntryImpl::FindNextChild(int64 offset, int len, MemEntryImpl** child) { DCHECK(child); *child = NULL; 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 = OpenChild(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; } void MemEntryImpl::DetachChild(int child_id) { children_->erase(child_id); } } // namespace disk_cache