| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "storage/browser/blob/blob_storage_context.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/numerics/safe_math.h" |
| #include "base/task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "storage/browser/blob/blob_data_builder.h" |
| #include "storage/browser/blob/blob_data_item.h" |
| #include "storage/browser/blob/blob_data_snapshot.h" |
| #include "storage/browser/blob/shareable_blob_data_item.h" |
| #include "storage/common/data_element.h" |
| #include "url/gurl.h" |
| |
| namespace storage { |
| namespace { |
| using ItemCopyEntry = BlobEntry::ItemCopyEntry; |
| using QuotaAllocationTask = BlobMemoryController::QuotaAllocationTask; |
| |
| bool IsBytes(DataElement::Type type) { |
| return type == DataElement::TYPE_BYTES || |
| type == DataElement::TYPE_BYTES_DESCRIPTION; |
| } |
| |
| void RecordBlobItemSizeStats(const DataElement& input_element) { |
| uint64_t length = input_element.length(); |
| |
| switch (input_element.type()) { |
| case DataElement::TYPE_BYTES: |
| case DataElement::TYPE_BYTES_DESCRIPTION: |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.Bytes", length / 1024); |
| break; |
| case DataElement::TYPE_BLOB: |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.Blob", |
| (length - input_element.offset()) / 1024); |
| break; |
| case DataElement::TYPE_FILE: { |
| bool full_file = (length == std::numeric_limits<uint64_t>::max()); |
| UMA_HISTOGRAM_BOOLEAN("Storage.BlobItemSize.File.Unknown", full_file); |
| if (!full_file) { |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.File", |
| (length - input_element.offset()) / 1024); |
| } |
| break; |
| } |
| case DataElement::TYPE_FILE_FILESYSTEM: { |
| bool full_file = (length == std::numeric_limits<uint64_t>::max()); |
| UMA_HISTOGRAM_BOOLEAN("Storage.BlobItemSize.FileSystem.Unknown", |
| full_file); |
| if (!full_file) { |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.FileSystem", |
| (length - input_element.offset()) / 1024); |
| } |
| break; |
| } |
| case DataElement::TYPE_DISK_CACHE_ENTRY: |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.CacheEntry", |
| (length - input_element.offset()) / 1024); |
| break; |
| case DataElement::TYPE_DATA_PIPE: |
| case DataElement::TYPE_UNKNOWN: |
| NOTREACHED(); |
| break; |
| } |
| } |
| } // namespace |
| |
| BlobStorageContext::BlobFlattener::BlobFlattener( |
| const BlobDataBuilder& input_builder, |
| BlobEntry* output_blob, |
| BlobStorageRegistry* registry) { |
| const std::string& uuid = input_builder.uuid_; |
| std::set<std::string> dependent_blob_uuids; |
| |
| size_t num_files_with_unknown_size = 0; |
| size_t num_building_dependent_blobs = 0; |
| |
| bool found_memory_transport = false; |
| bool found_file_transport = false; |
| |
| base::CheckedNumeric<uint64_t> checked_total_size = 0; |
| base::CheckedNumeric<uint64_t> checked_total_memory_size = 0; |
| base::CheckedNumeric<uint64_t> checked_transport_quota_needed = 0; |
| base::CheckedNumeric<uint64_t> checked_copy_quota_needed = 0; |
| |
| for (scoped_refptr<BlobDataItem> input_item : input_builder.items_) { |
| const DataElement& input_element = input_item->data_element(); |
| DataElement::Type type = input_element.type(); |
| uint64_t length = input_element.length(); |
| |
| RecordBlobItemSizeStats(input_element); |
| |
| if (IsBytes(type)) { |
| DCHECK_NE(0 + DataElement::kUnknownSize, input_element.length()); |
| found_memory_transport = true; |
| if (found_file_transport) { |
| // We cannot have both memory and file transport items. |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| contains_unpopulated_transport_items |= |
| (type == DataElement::TYPE_BYTES_DESCRIPTION); |
| checked_transport_quota_needed += length; |
| checked_total_size += length; |
| scoped_refptr<ShareableBlobDataItem> item = new ShareableBlobDataItem( |
| std::move(input_item), ShareableBlobDataItem::QUOTA_NEEDED); |
| pending_transport_items.push_back(item); |
| transport_items.push_back(item.get()); |
| output_blob->AppendSharedBlobItem(std::move(item)); |
| continue; |
| } |
| if (type == DataElement::TYPE_BLOB) { |
| BlobEntry* ref_entry = registry->GetEntry(input_element.blob_uuid()); |
| |
| if (!ref_entry || input_element.blob_uuid() == uuid) { |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| |
| if (BlobStatusIsError(ref_entry->status())) { |
| status = BlobStatus::ERR_REFERENCED_BLOB_BROKEN; |
| return; |
| } |
| |
| if (ref_entry->total_size() == DataElement::kUnknownSize) { |
| // We can't reference a blob with unknown size. |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| |
| if (dependent_blob_uuids.find(input_element.blob_uuid()) == |
| dependent_blob_uuids.end()) { |
| dependent_blobs.push_back( |
| std::make_pair(input_element.blob_uuid(), ref_entry)); |
| dependent_blob_uuids.insert(input_element.blob_uuid()); |
| if (BlobStatusIsPending(ref_entry->status())) { |
| num_building_dependent_blobs++; |
| } |
| } |
| |
| length = length == DataElement::kUnknownSize ? ref_entry->total_size() |
| : input_element.length(); |
| checked_total_size += length; |
| |
| // If we're referencing the whole blob, then we don't need to slice. |
| if (input_element.offset() == 0 && length == ref_entry->total_size()) { |
| for (const auto& shareable_item : ref_entry->items()) { |
| output_blob->AppendSharedBlobItem(shareable_item); |
| } |
| continue; |
| } |
| |
| // Validate our reference has good offset & length. |
| uint64_t end_byte; |
| if (!base::CheckAdd(input_element.offset(), length) |
| .AssignIfValid(&end_byte) || |
| end_byte > ref_entry->total_size()) { |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| |
| BlobSlice slice(*ref_entry, input_element.offset(), length); |
| |
| if (!slice.copying_memory_size.IsValid() || |
| !slice.total_memory_size.IsValid()) { |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| checked_total_memory_size += slice.total_memory_size; |
| |
| if (slice.first_source_item) { |
| copies.push_back(ItemCopyEntry(slice.first_source_item, |
| slice.first_item_slice_offset, |
| slice.dest_items.front())); |
| pending_copy_items.push_back(slice.dest_items.front()); |
| } |
| if (slice.last_source_item) { |
| copies.push_back( |
| ItemCopyEntry(slice.last_source_item, 0, slice.dest_items.back())); |
| pending_copy_items.push_back(slice.dest_items.back()); |
| } |
| checked_copy_quota_needed += slice.copying_memory_size; |
| |
| for (auto& shareable_item : slice.dest_items) { |
| output_blob->AppendSharedBlobItem(std::move(shareable_item)); |
| } |
| continue; |
| } |
| |
| // If the source item is a temporary file item, then we need to keep track |
| // of that and mark it as needing quota. |
| scoped_refptr<ShareableBlobDataItem> item; |
| if (BlobDataBuilder::IsFutureFileItem(input_element)) { |
| found_file_transport = true; |
| if (found_memory_transport) { |
| // We cannot have both memory and file transport items. |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| contains_unpopulated_transport_items = true; |
| item = new ShareableBlobDataItem(std::move(input_item), |
| ShareableBlobDataItem::QUOTA_NEEDED); |
| pending_transport_items.push_back(item); |
| transport_items.push_back(item.get()); |
| checked_transport_quota_needed += length; |
| } else { |
| item = new ShareableBlobDataItem( |
| std::move(input_item), |
| ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA); |
| } |
| if (length == DataElement::kUnknownSize) |
| num_files_with_unknown_size++; |
| |
| checked_total_size += length; |
| output_blob->AppendSharedBlobItem(std::move(item)); |
| } |
| |
| if (num_files_with_unknown_size > 1 && input_builder.items_.size() > 1) { |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| if (!checked_total_size.IsValid() || !checked_total_memory_size.IsValid() || |
| !checked_transport_quota_needed.IsValid() || |
| !checked_copy_quota_needed.IsValid()) { |
| status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return; |
| } |
| total_size = checked_total_size.ValueOrDie(); |
| total_memory_size = checked_total_memory_size.ValueOrDie(); |
| transport_quota_needed = checked_transport_quota_needed.ValueOrDie(); |
| copy_quota_needed = checked_copy_quota_needed.ValueOrDie(); |
| transport_quota_type = found_file_transport ? TransportQuotaType::FILE |
| : TransportQuotaType::MEMORY; |
| if (transport_quota_needed) { |
| status = BlobStatus::PENDING_QUOTA; |
| } else { |
| status = BlobStatus::PENDING_INTERNALS; |
| } |
| } |
| |
| BlobStorageContext::BlobFlattener::~BlobFlattener() {} |
| |
| BlobStorageContext::BlobSlice::BlobSlice(const BlobEntry& source, |
| uint64_t slice_offset, |
| uint64_t slice_size) { |
| const auto& source_items = source.items(); |
| const auto& offsets = source.offsets(); |
| DCHECK_LE(slice_offset + slice_size, source.total_size()); |
| size_t item_index = |
| std::upper_bound(offsets.begin(), offsets.end(), slice_offset) - |
| offsets.begin(); |
| uint64_t item_offset = |
| item_index == 0 ? slice_offset : slice_offset - offsets[item_index - 1]; |
| size_t num_items = source_items.size(); |
| |
| size_t first_item_index = item_index; |
| |
| // Read starting from 'first_item_index' and 'item_offset'. |
| for (uint64_t total_sliced = 0; |
| item_index < num_items && total_sliced < slice_size; item_index++) { |
| const scoped_refptr<BlobDataItem>& source_item = |
| source_items[item_index]->item(); |
| uint64_t source_length = source_item->length(); |
| DataElement::Type type = source_item->type(); |
| DCHECK_NE(source_length, std::numeric_limits<uint64_t>::max()); |
| DCHECK_NE(source_length, 0ull); |
| |
| uint64_t read_size = |
| std::min(source_length - item_offset, slice_size - total_sliced); |
| total_sliced += read_size; |
| |
| bool reusing_blob_item = (read_size == source_length); |
| UMA_HISTOGRAM_BOOLEAN("Storage.Blob.ReusedItem", reusing_blob_item); |
| if (reusing_blob_item) { |
| // We can share the entire item. |
| dest_items.push_back(source_items[item_index]); |
| if (IsBytes(type)) { |
| total_memory_size += source_length; |
| } |
| continue; |
| } |
| |
| scoped_refptr<BlobDataItem> data_item; |
| ShareableBlobDataItem::State state = |
| ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA; |
| switch (type) { |
| case DataElement::TYPE_BYTES_DESCRIPTION: |
| case DataElement::TYPE_BYTES: { |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.BlobSlice.Bytes", |
| read_size / 1024); |
| if (item_index == first_item_index) { |
| first_item_slice_offset = item_offset; |
| first_source_item = source_items[item_index]; |
| } else { |
| last_source_item = source_items[item_index]; |
| } |
| copying_memory_size += read_size; |
| total_memory_size += read_size; |
| // Since we don't have quota yet for memory, we create temporary items |
| // for this data. When our blob is finished constructing, all dependent |
| // blobs are done, and we have enough memory quota, we'll copy the data |
| // over. |
| std::unique_ptr<DataElement> element(new DataElement()); |
| element->SetToBytesDescription(base::checked_cast<size_t>(read_size)); |
| data_item = new BlobDataItem(std::move(element)); |
| state = ShareableBlobDataItem::QUOTA_NEEDED; |
| break; |
| } |
| case DataElement::TYPE_FILE: { |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.BlobSlice.File", |
| read_size / 1024); |
| std::unique_ptr<DataElement> element(new DataElement()); |
| element->SetToFilePathRange( |
| source_item->path(), source_item->offset() + item_offset, read_size, |
| source_item->expected_modification_time()); |
| data_item = |
| new BlobDataItem(std::move(element), source_item->data_handle_); |
| |
| if (BlobDataBuilder::IsFutureFileItem(source_item->data_element())) { |
| // The source file isn't a real file yet (path is fake), so store the |
| // items we need to copy from later. |
| if (item_index == first_item_index) { |
| first_item_slice_offset = item_offset; |
| first_source_item = source_items[item_index]; |
| } else { |
| last_source_item = source_items[item_index]; |
| } |
| } |
| break; |
| } |
| case DataElement::TYPE_FILE_FILESYSTEM: { |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.BlobSlice.FileSystem", |
| read_size / 1024); |
| std::unique_ptr<DataElement> element(new DataElement()); |
| element->SetToFileSystemUrlRange( |
| source_item->filesystem_url(), source_item->offset() + item_offset, |
| read_size, source_item->expected_modification_time()); |
| data_item = new BlobDataItem(std::move(element)); |
| break; |
| } |
| case DataElement::TYPE_DISK_CACHE_ENTRY: { |
| UMA_HISTOGRAM_COUNTS_1M("Storage.BlobItemSize.BlobSlice.CacheEntry", |
| read_size / 1024); |
| std::unique_ptr<DataElement> element(new DataElement()); |
| element->SetToDiskCacheEntryRange(source_item->offset() + item_offset, |
| read_size); |
| data_item = |
| new BlobDataItem(std::move(element), source_item->data_handle_, |
| source_item->disk_cache_entry(), |
| source_item->disk_cache_stream_index(), |
| source_item->disk_cache_side_stream_index()); |
| break; |
| } |
| case DataElement::TYPE_BLOB: |
| case DataElement::TYPE_DATA_PIPE: |
| case DataElement::TYPE_UNKNOWN: |
| CHECK(false) << "Illegal blob item type: " << type; |
| } |
| dest_items.push_back( |
| new ShareableBlobDataItem(std::move(data_item), state)); |
| item_offset = 0; |
| } |
| } |
| |
| BlobStorageContext::BlobSlice::~BlobSlice() {} |
| |
| BlobStorageContext::BlobStorageContext() |
| : memory_controller_(base::FilePath(), scoped_refptr<base::TaskRunner>()), |
| ptr_factory_(this) {} |
| |
| BlobStorageContext::BlobStorageContext( |
| base::FilePath storage_directory, |
| scoped_refptr<base::TaskRunner> file_runner) |
| : memory_controller_(std::move(storage_directory), std::move(file_runner)), |
| ptr_factory_(this) {} |
| |
| BlobStorageContext::~BlobStorageContext() {} |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromUUID( |
| const std::string& uuid) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| if (!entry) |
| return nullptr; |
| return CreateHandle(uuid, entry); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromPublicURL( |
| const GURL& url) { |
| std::string uuid; |
| BlobEntry* entry = registry_.GetEntryFromURL(url, &uuid); |
| if (!entry) |
| return nullptr; |
| return CreateHandle(uuid, entry); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::AddFinishedBlob( |
| const BlobDataBuilder& external_builder) { |
| TRACE_EVENT0("Blob", "Context::AddFinishedBlob"); |
| return BuildBlob(external_builder, TransportAllowedCallback()); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::AddFinishedBlob( |
| const BlobDataBuilder* builder) { |
| DCHECK(builder); |
| return AddFinishedBlob(*builder); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::AddBrokenBlob( |
| const std::string& uuid, |
| const std::string& content_type, |
| const std::string& content_disposition, |
| BlobStatus reason) { |
| DCHECK(!registry_.HasEntry(uuid)); |
| DCHECK(BlobStatusIsError(reason)); |
| BlobEntry* entry = |
| registry_.CreateEntry(uuid, content_type, content_disposition); |
| entry->set_status(reason); |
| FinishBuilding(entry); |
| return CreateHandle(uuid, entry); |
| } |
| |
| bool BlobStorageContext::RegisterPublicBlobURL(const GURL& blob_url, |
| const std::string& uuid) { |
| if (!registry_.CreateUrlMapping(blob_url, uuid)) |
| return false; |
| IncrementBlobRefCount(uuid); |
| return true; |
| } |
| |
| void BlobStorageContext::RevokePublicBlobURL(const GURL& blob_url) { |
| std::string uuid; |
| if (!registry_.DeleteURLMapping(blob_url, &uuid)) |
| return; |
| DecrementBlobRefCount(uuid); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::AddFutureBlob( |
| const std::string& uuid, |
| const std::string& content_type, |
| const std::string& content_disposition) { |
| DCHECK(!registry_.HasEntry(uuid)); |
| |
| BlobEntry* entry = |
| registry_.CreateEntry(uuid, content_type, content_disposition); |
| entry->set_size(DataElement::kUnknownSize); |
| entry->set_status(BlobStatus::PENDING_CONSTRUCTION); |
| entry->set_building_state(base::MakeUnique<BlobEntry::BuildingState>( |
| false, TransportAllowedCallback(), 0)); |
| return CreateHandle(uuid, entry); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::BuildPreregisteredBlob( |
| const BlobDataBuilder& content, |
| const TransportAllowedCallback& transport_allowed_callback) { |
| BlobEntry* entry = registry_.GetEntry(content.uuid()); |
| DCHECK(entry); |
| DCHECK_EQ(BlobStatus::PENDING_CONSTRUCTION, entry->status()); |
| entry->set_size(0); |
| |
| return BuildBlobInternal(entry, content, transport_allowed_callback); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::BuildBlob( |
| const BlobDataBuilder& content, |
| const TransportAllowedCallback& transport_allowed_callback) { |
| DCHECK(!registry_.HasEntry(content.uuid_)); |
| |
| BlobEntry* entry = registry_.CreateEntry( |
| content.uuid(), content.content_type_, content.content_disposition_); |
| |
| return BuildBlobInternal(entry, content, transport_allowed_callback); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::BuildBlobInternal( |
| BlobEntry* entry, |
| const BlobDataBuilder& content, |
| const TransportAllowedCallback& transport_allowed_callback) { |
| // This flattens all blob references in the transportion content out and |
| // stores the complete item representation in the internal data. |
| BlobFlattener flattener(content, entry, ®istry_); |
| |
| DCHECK(!flattener.contains_unpopulated_transport_items || |
| transport_allowed_callback) |
| << "If we have pending unpopulated content then a callback is required"; |
| |
| DCHECK(flattener.total_size == 0 || |
| flattener.total_size == entry->total_size()); |
| entry->set_size(flattener.total_size); |
| entry->set_status(flattener.status); |
| std::unique_ptr<BlobDataHandle> handle = CreateHandle(content.uuid_, entry); |
| |
| UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.ItemCount", entry->items().size()); |
| UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.TotalSize", |
| flattener.total_memory_size / 1024); |
| |
| uint64_t total_memory_needed = |
| flattener.copy_quota_needed + |
| (flattener.transport_quota_type == TransportQuotaType::MEMORY |
| ? flattener.transport_quota_needed |
| : 0); |
| UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.TotalUnsharedSize", |
| total_memory_needed / 1024); |
| |
| size_t num_building_dependent_blobs = 0; |
| std::vector<std::unique_ptr<BlobDataHandle>> dependent_blobs; |
| // We hold a handle to all blobs we're using. This is important, as our memory |
| // accounting can be delayed until OnEnoughSizeForBlobData is called, and we |
| // only free memory on canceling when we've done this accounting. If a |
| // dependent blob is dereferenced, then we're the last blob holding onto that |
| // data item, and we need to account for that. So we prevent that case by |
| // holding onto all blobs. |
| for (const std::pair<std::string, BlobEntry*>& pending_blob : |
| flattener.dependent_blobs) { |
| dependent_blobs.push_back( |
| CreateHandle(pending_blob.first, pending_blob.second)); |
| if (BlobStatusIsPending(pending_blob.second->status())) { |
| pending_blob.second->building_state_->build_completion_callbacks |
| .push_back(base::Bind(&BlobStorageContext::OnDependentBlobFinished, |
| ptr_factory_.GetWeakPtr(), content.uuid_)); |
| num_building_dependent_blobs++; |
| } |
| } |
| |
| auto previous_building_state = std::move(entry->building_state_); |
| entry->set_building_state(base::MakeUnique<BlobEntry::BuildingState>( |
| !flattener.pending_transport_items.empty(), transport_allowed_callback, |
| num_building_dependent_blobs)); |
| BlobEntry::BuildingState* building_state = entry->building_state_.get(); |
| std::swap(building_state->copies, flattener.copies); |
| std::swap(building_state->dependent_blobs, dependent_blobs); |
| std::swap(building_state->transport_items, flattener.transport_items); |
| if (previous_building_state) { |
| DCHECK(!previous_building_state->transport_items_present); |
| DCHECK(!previous_building_state->transport_allowed_callback); |
| DCHECK(previous_building_state->transport_items.empty()); |
| DCHECK(previous_building_state->dependent_blobs.empty()); |
| DCHECK(previous_building_state->copies.empty()); |
| std::swap(building_state->build_completion_callbacks, |
| previous_building_state->build_completion_callbacks); |
| auto runner = base::ThreadTaskRunnerHandle::Get(); |
| for (const auto& callback : |
| previous_building_state->build_started_callbacks) |
| runner->PostTask(FROM_HERE, base::BindOnce(callback, entry->status())); |
| } |
| |
| // Break ourselves if we have an error. BuildingState must be set first so the |
| // callback is called correctly. |
| if (BlobStatusIsError(flattener.status)) { |
| CancelBuildingBlobInternal(entry, flattener.status); |
| return handle; |
| } |
| |
| // Avoid the state where we might grant only one quota. |
| if (!memory_controller_.CanReserveQuota(flattener.copy_quota_needed + |
| flattener.transport_quota_needed)) { |
| CancelBuildingBlobInternal(entry, BlobStatus::ERR_OUT_OF_MEMORY); |
| return handle; |
| } |
| |
| if (flattener.copy_quota_needed > 0) { |
| // The blob can complete during the execution of |ReserveMemoryQuota|. |
| base::WeakPtr<QuotaAllocationTask> pending_request = |
| memory_controller_.ReserveMemoryQuota( |
| std::move(flattener.pending_copy_items), |
| base::Bind(&BlobStorageContext::OnEnoughSpaceForCopies, |
| ptr_factory_.GetWeakPtr(), content.uuid_)); |
| // Building state will be null if the blob is already finished. |
| if (entry->building_state_) |
| entry->building_state_->copy_quota_request = std::move(pending_request); |
| } |
| |
| if (flattener.transport_quota_needed > 0) { |
| base::WeakPtr<QuotaAllocationTask> pending_request; |
| |
| switch (flattener.transport_quota_type) { |
| case TransportQuotaType::MEMORY: { |
| // The blob can complete during the execution of |ReserveMemoryQuota|. |
| std::vector<BlobMemoryController::FileCreationInfo> empty_files; |
| pending_request = memory_controller_.ReserveMemoryQuota( |
| std::move(flattener.pending_transport_items), |
| base::Bind(&BlobStorageContext::OnEnoughSpaceForTransport, |
| ptr_factory_.GetWeakPtr(), content.uuid_, |
| base::Passed(&empty_files))); |
| break; |
| } |
| case TransportQuotaType::FILE: |
| pending_request = memory_controller_.ReserveFileQuota( |
| std::move(flattener.pending_transport_items), |
| base::Bind(&BlobStorageContext::OnEnoughSpaceForTransport, |
| ptr_factory_.GetWeakPtr(), content.uuid_)); |
| break; |
| } |
| |
| // Building state will be null if the blob is already finished. |
| if (entry->building_state_) { |
| entry->building_state_->transport_quota_request = |
| std::move(pending_request); |
| } |
| } |
| |
| if (entry->CanFinishBuilding()) |
| FinishBuilding(entry); |
| |
| return handle; |
| } |
| |
| void BlobStorageContext::CancelBuildingBlob(const std::string& uuid, |
| BlobStatus reason) { |
| CancelBuildingBlobInternal(registry_.GetEntry(uuid), reason); |
| } |
| |
| void BlobStorageContext::NotifyTransportComplete(const std::string& uuid) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| CHECK(entry) << "There is no blob entry with uuid " << uuid; |
| DCHECK(BlobStatusIsPending(entry->status())); |
| NotifyTransportCompleteInternal(entry); |
| } |
| |
| void BlobStorageContext::IncrementBlobRefCount(const std::string& uuid) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| DCHECK(entry); |
| entry->IncrementRefCount(); |
| } |
| |
| void BlobStorageContext::DecrementBlobRefCount(const std::string& uuid) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| DCHECK(entry); |
| DCHECK_GT(entry->refcount(), 0u); |
| entry->DecrementRefCount(); |
| if (entry->refcount() == 0) { |
| DVLOG(1) << "BlobStorageContext::DecrementBlobRefCount(" << uuid |
| << "): Deleting blob."; |
| ClearAndFreeMemory(entry); |
| registry_.DeleteEntry(uuid); |
| } |
| } |
| |
| std::unique_ptr<BlobDataSnapshot> BlobStorageContext::CreateSnapshot( |
| const std::string& uuid) { |
| std::unique_ptr<BlobDataSnapshot> result; |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| if (entry->status() != BlobStatus::DONE) |
| return result; |
| |
| std::unique_ptr<BlobDataSnapshot> snapshot(new BlobDataSnapshot( |
| uuid, entry->content_type(), entry->content_disposition())); |
| snapshot->items_.reserve(entry->items().size()); |
| for (const auto& shareable_item : entry->items()) { |
| snapshot->items_.push_back(shareable_item->item()); |
| } |
| memory_controller_.NotifyMemoryItemsUsed(entry->items()); |
| return snapshot; |
| } |
| |
| BlobStatus BlobStorageContext::GetBlobStatus(const std::string& uuid) const { |
| const BlobEntry* entry = registry_.GetEntry(uuid); |
| if (!entry) |
| return BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; |
| return entry->status(); |
| } |
| |
| void BlobStorageContext::RunOnConstructionComplete( |
| const std::string& uuid, |
| const BlobStatusCallback& done) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| DCHECK(entry); |
| if (BlobStatusIsPending(entry->status())) { |
| entry->building_state_->build_completion_callbacks.push_back(done); |
| return; |
| } |
| done.Run(entry->status()); |
| } |
| |
| void BlobStorageContext::RunOnConstructionBegin( |
| const std::string& uuid, |
| const BlobStatusCallback& done) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| DCHECK(entry); |
| if (entry->status() == BlobStatus::PENDING_CONSTRUCTION) { |
| entry->building_state_->build_started_callbacks.push_back(done); |
| return; |
| } |
| done.Run(entry->status()); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::CreateHandle( |
| const std::string& uuid, |
| BlobEntry* entry) { |
| return base::WrapUnique(new BlobDataHandle( |
| uuid, entry->content_type_, entry->content_disposition_, entry->size_, |
| this, base::ThreadTaskRunnerHandle::Get().get())); |
| } |
| |
| void BlobStorageContext::NotifyTransportCompleteInternal(BlobEntry* entry) { |
| DCHECK(entry); |
| for (ShareableBlobDataItem* shareable_item : |
| entry->building_state_->transport_items) { |
| DCHECK(shareable_item->state() == ShareableBlobDataItem::QUOTA_GRANTED); |
| shareable_item->set_state(ShareableBlobDataItem::POPULATED_WITH_QUOTA); |
| } |
| entry->set_status(BlobStatus::PENDING_INTERNALS); |
| if (entry->CanFinishBuilding()) |
| FinishBuilding(entry); |
| } |
| |
| void BlobStorageContext::CancelBuildingBlobInternal(BlobEntry* entry, |
| BlobStatus reason) { |
| DCHECK(entry); |
| DCHECK(BlobStatusIsError(reason)); |
| TransportAllowedCallback transport_allowed_callback; |
| if (entry->building_state_ && |
| entry->building_state_->transport_allowed_callback) { |
| transport_allowed_callback = |
| entry->building_state_->transport_allowed_callback; |
| entry->building_state_->transport_allowed_callback.Reset(); |
| } |
| if (entry->building_state_ && |
| entry->status() == BlobStatus::PENDING_CONSTRUCTION) { |
| auto runner = base::ThreadTaskRunnerHandle::Get(); |
| for (const auto& callback : entry->building_state_->build_started_callbacks) |
| runner->PostTask(FROM_HERE, base::BindOnce(callback, reason)); |
| } |
| ClearAndFreeMemory(entry); |
| entry->set_status(reason); |
| if (transport_allowed_callback) { |
| transport_allowed_callback.Run( |
| reason, std::vector<BlobMemoryController::FileCreationInfo>()); |
| } |
| FinishBuilding(entry); |
| } |
| |
| void BlobStorageContext::FinishBuilding(BlobEntry* entry) { |
| DCHECK(entry); |
| |
| BlobStatus status = entry->status_; |
| DCHECK_NE(BlobStatus::DONE, status); |
| |
| bool error = BlobStatusIsError(status); |
| UMA_HISTOGRAM_BOOLEAN("Storage.Blob.Broken", error); |
| if (error) { |
| UMA_HISTOGRAM_ENUMERATION("Storage.Blob.BrokenReason", |
| static_cast<int>(status), |
| (static_cast<int>(BlobStatus::LAST_ERROR) + 1)); |
| } |
| |
| if (BlobStatusIsPending(entry->status_)) { |
| for (const ItemCopyEntry& copy : entry->building_state_->copies) { |
| // Our source item can be a file if it was a slice of an unpopulated file, |
| // or a slice of data that was then paged to disk. |
| size_t dest_size = static_cast<size_t>(copy.dest_item->item()->length()); |
| DataElement::Type dest_type = copy.dest_item->item()->type(); |
| switch (copy.source_item->item()->type()) { |
| case DataElement::TYPE_BYTES: { |
| DCHECK_EQ(dest_type, DataElement::TYPE_BYTES_DESCRIPTION); |
| const char* src_data = |
| copy.source_item->item()->bytes() + copy.source_item_offset; |
| copy.dest_item->item()->item_->SetToBytes(src_data, dest_size); |
| break; |
| } |
| case DataElement::TYPE_FILE: { |
| // If we expected a memory item (and our source was paged to disk) we |
| // free that memory. |
| if (dest_type == DataElement::TYPE_BYTES_DESCRIPTION) |
| copy.dest_item->set_memory_allocation(nullptr); |
| |
| const DataElement& source_element = |
| copy.source_item->item()->data_element(); |
| std::unique_ptr<DataElement> new_element(new DataElement()); |
| new_element->SetToFilePathRange( |
| source_element.path(), |
| source_element.offset() + copy.source_item_offset, dest_size, |
| source_element.expected_modification_time()); |
| scoped_refptr<BlobDataItem> new_item(new BlobDataItem( |
| std::move(new_element), copy.source_item->item()->data_handle_)); |
| copy.dest_item->set_item(std::move(new_item)); |
| break; |
| } |
| case DataElement::TYPE_UNKNOWN: |
| case DataElement::TYPE_BLOB: |
| case DataElement::TYPE_BYTES_DESCRIPTION: |
| case DataElement::TYPE_FILE_FILESYSTEM: |
| case DataElement::TYPE_DISK_CACHE_ENTRY: |
| case DataElement::TYPE_DATA_PIPE: |
| NOTREACHED(); |
| break; |
| } |
| copy.dest_item->set_state(ShareableBlobDataItem::POPULATED_WITH_QUOTA); |
| } |
| |
| entry->set_status(BlobStatus::DONE); |
| } |
| |
| std::vector<BlobStatusCallback> callbacks; |
| if (entry->building_state_.get()) { |
| std::swap(callbacks, entry->building_state_->build_completion_callbacks); |
| entry->building_state_.reset(); |
| } |
| |
| memory_controller_.NotifyMemoryItemsUsed(entry->items()); |
| |
| auto runner = base::ThreadTaskRunnerHandle::Get(); |
| for (const auto& callback : callbacks) |
| runner->PostTask(FROM_HERE, base::Bind(callback, entry->status())); |
| |
| for (const auto& shareable_item : entry->items()) { |
| DCHECK_NE(DataElement::TYPE_BYTES_DESCRIPTION, |
| shareable_item->item()->type()); |
| DCHECK(shareable_item->IsPopulated()) << shareable_item->state(); |
| } |
| } |
| |
| void BlobStorageContext::RequestTransport( |
| BlobEntry* entry, |
| std::vector<BlobMemoryController::FileCreationInfo> files) { |
| BlobEntry::BuildingState* building_state = entry->building_state_.get(); |
| if (building_state->transport_allowed_callback) { |
| base::ResetAndReturn(&building_state->transport_allowed_callback) |
| .Run(BlobStatus::PENDING_TRANSPORT, std::move(files)); |
| return; |
| } |
| DCHECK(files.empty()); |
| NotifyTransportCompleteInternal(entry); |
| } |
| |
| void BlobStorageContext::OnEnoughSpaceForTransport( |
| const std::string& uuid, |
| std::vector<BlobMemoryController::FileCreationInfo> files, |
| bool success) { |
| if (!success) { |
| CancelBuildingBlob(uuid, BlobStatus::ERR_OUT_OF_MEMORY); |
| return; |
| } |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| if (!entry || !entry->building_state_.get()) |
| return; |
| BlobEntry::BuildingState& building_state = *entry->building_state_; |
| DCHECK(!building_state.transport_quota_request); |
| DCHECK(building_state.transport_items_present); |
| |
| entry->set_status(BlobStatus::PENDING_TRANSPORT); |
| RequestTransport(entry, std::move(files)); |
| |
| if (entry->CanFinishBuilding()) |
| FinishBuilding(entry); |
| } |
| |
| void BlobStorageContext::OnEnoughSpaceForCopies(const std::string& uuid, |
| bool success) { |
| if (!success) { |
| CancelBuildingBlob(uuid, BlobStatus::ERR_OUT_OF_MEMORY); |
| return; |
| } |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| if (!entry) |
| return; |
| |
| if (entry->CanFinishBuilding()) |
| FinishBuilding(entry); |
| } |
| |
| void BlobStorageContext::OnDependentBlobFinished( |
| const std::string& owning_blob_uuid, |
| BlobStatus status) { |
| BlobEntry* entry = registry_.GetEntry(owning_blob_uuid); |
| if (!entry || !entry->building_state_) |
| return; |
| |
| if (BlobStatusIsError(status)) { |
| DCHECK_NE(BlobStatus::ERR_BLOB_DEREFERENCED_WHILE_BUILDING, status) |
| << "Referenced blob should never be dereferenced while we " |
| << "are depending on it, as our system holds a handle."; |
| CancelBuildingBlobInternal(entry, BlobStatus::ERR_REFERENCED_BLOB_BROKEN); |
| return; |
| } |
| DCHECK_GT(entry->building_state_->num_building_dependent_blobs, 0u); |
| --entry->building_state_->num_building_dependent_blobs; |
| |
| if (entry->CanFinishBuilding()) |
| FinishBuilding(entry); |
| } |
| |
| void BlobStorageContext::ClearAndFreeMemory(BlobEntry* entry) { |
| if (entry->building_state_) |
| entry->building_state_->CancelRequests(); |
| entry->ClearItems(); |
| entry->ClearOffsets(); |
| entry->set_size(0); |
| } |
| |
| } // namespace storage |