| // 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 <inttypes.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/metrics/histogram_macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/numerics/safe_math.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/memory_dump_manager.h" |
| #include "base/trace_event/trace_event.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.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 "third_party/blink/public/common/blob/blob_utils.h" |
| #include "url/gurl.h" |
| |
| namespace storage { |
| namespace { |
| using ItemCopyEntry = BlobEntry::ItemCopyEntry; |
| using QuotaAllocationTask = BlobMemoryController::QuotaAllocationTask; |
| } // namespace |
| |
| BlobStorageContext::BlobStorageContext() |
| : memory_controller_(base::FilePath(), scoped_refptr<base::TaskRunner>()), |
| ptr_factory_(this) { |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| this, "BlobStorageContext", base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| 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) { |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( |
| this, "BlobStorageContext", base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| BlobStorageContext::~BlobStorageContext() { |
| base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( |
| this); |
| } |
| |
| 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); |
| } |
| |
| void BlobStorageContext::GetBlobDataFromBlobPtr( |
| blink::mojom::BlobPtr blob, |
| base::OnceCallback<void(std::unique_ptr<BlobDataHandle>)> callback) { |
| DCHECK(blob); |
| blink::mojom::Blob* raw_blob = blob.get(); |
| raw_blob->GetInternalUUID(mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| base::BindOnce( |
| [](blink::mojom::BlobPtr, base::WeakPtr<BlobStorageContext> context, |
| base::OnceCallback<void(std::unique_ptr<BlobDataHandle>)> callback, |
| const std::string& uuid) { |
| if (!context || uuid.empty()) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| std::move(callback).Run(context->GetBlobDataFromUUID(uuid)); |
| }, |
| std::move(blob), AsWeakPtr(), std::move(callback)), |
| "")); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::AddFinishedBlob( |
| std::unique_ptr<BlobDataBuilder> external_builder) { |
| TRACE_EVENT0("Blob", "Context::AddFinishedBlob"); |
| return BuildBlob(std::move(external_builder), TransportAllowedCallback()); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::AddFinishedBlob( |
| const std::string& uuid, |
| const std::string& content_type, |
| const std::string& content_disposition, |
| std::vector<scoped_refptr<ShareableBlobDataItem>> items) { |
| TRACE_EVENT0("Blob", "Context::AddFinishedBlobFromItems"); |
| BlobEntry* entry = |
| registry_.CreateEntry(uuid, content_type, content_disposition); |
| uint64_t total_memory_size = 0; |
| for (const auto& item : items) { |
| if (item->item()->type() == BlobDataItem::Type::kBytes) |
| total_memory_size += item->item()->length(); |
| DCHECK_EQ(item->state(), ShareableBlobDataItem::POPULATED_WITH_QUOTA); |
| DCHECK_NE(BlobDataItem::Type::kBytesDescription, item->item()->type()); |
| DCHECK(!item->item()->IsFutureFileItem()); |
| } |
| |
| entry->SetSharedBlobItems(std::move(items)); |
| std::unique_ptr<BlobDataHandle> handle = CreateHandle(uuid, entry); |
| UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.TotalSize", total_memory_size / 1024); |
| entry->set_status(BlobStatus::DONE); |
| memory_controller_.NotifyMemoryItemsUsed(entry->items()); |
| return handle; |
| } |
| |
| 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, |
| BuildAbortedCallback build_aborted_callback) { |
| DCHECK(!registry_.HasEntry(uuid)); |
| |
| BlobEntry* entry = |
| registry_.CreateEntry(uuid, content_type, content_disposition); |
| entry->set_size(blink::BlobUtils::kUnknownSize); |
| entry->set_status(BlobStatus::PENDING_CONSTRUCTION); |
| entry->set_building_state(std::make_unique<BlobEntry::BuildingState>( |
| false, TransportAllowedCallback(), 0)); |
| entry->building_state_->build_aborted_callback = |
| std::move(build_aborted_callback); |
| return CreateHandle(uuid, entry); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::BuildPreregisteredBlob( |
| std::unique_ptr<BlobDataBuilder> content, |
| 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, std::move(content), |
| std::move(transport_allowed_callback)); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::BuildBlob( |
| std::unique_ptr<BlobDataBuilder> content, |
| TransportAllowedCallback transport_allowed_callback) { |
| DCHECK(!registry_.HasEntry(content->uuid_)); |
| |
| BlobEntry* entry = registry_.CreateEntry( |
| content->uuid(), content->content_type_, content->content_disposition_); |
| |
| return BuildBlobInternal(entry, std::move(content), |
| std::move(transport_allowed_callback)); |
| } |
| |
| std::unique_ptr<BlobDataHandle> BlobStorageContext::BuildBlobInternal( |
| BlobEntry* entry, |
| std::unique_ptr<BlobDataBuilder> content, |
| TransportAllowedCallback transport_allowed_callback) { |
| #if DCHECK_IS_ON() |
| bool contains_unpopulated_transport_items = false; |
| for (const auto& item : content->pending_transport_items()) { |
| if (item->item()->type() == BlobDataItem::Type::kBytesDescription || |
| item->item()->type() == BlobDataItem::Type::kFile) |
| contains_unpopulated_transport_items = true; |
| } |
| |
| DCHECK(!contains_unpopulated_transport_items || transport_allowed_callback) |
| << "If we have pending unpopulated content then a callback is required"; |
| #endif |
| |
| entry->SetSharedBlobItems(content->ReleaseItems()); |
| |
| DCHECK((content->total_size() == 0 && !content->IsValid()) || |
| content->total_size() == entry->total_size()) |
| << content->total_size() << " vs " << entry->total_size(); |
| |
| if (!content->IsValid()) { |
| entry->set_size(0); |
| entry->set_status(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS); |
| } else if (content->transport_quota_needed()) { |
| entry->set_status(BlobStatus::PENDING_QUOTA); |
| } else { |
| entry->set_status(BlobStatus::PENDING_REFERENCED_BLOBS); |
| } |
| |
| std::unique_ptr<BlobDataHandle> handle = CreateHandle(content->uuid_, entry); |
| |
| UMA_HISTOGRAM_COUNTS_1M("Storage.Blob.TotalSize", |
| content->total_memory_size() / 1024); |
| |
| TransportQuotaType transport_quota_type = content->found_memory_transport() |
| ? TransportQuotaType::MEMORY |
| : TransportQuotaType::FILE; |
| |
| uint64_t total_memory_needed = |
| content->copy_quota_needed() + |
| (transport_quota_type == TransportQuotaType::MEMORY |
| ? content->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::string& dependent_blob_uuid : content->dependent_blobs()) { |
| BlobEntry* blob_entry = registry_.GetEntry(dependent_blob_uuid); |
| DCHECK(blob_entry); |
| if (BlobStatusIsError(blob_entry->status())) { |
| entry->set_status(BlobStatus::ERR_REFERENCED_BLOB_BROKEN); |
| break; |
| } |
| dependent_blobs.push_back(CreateHandle(dependent_blob_uuid, blob_entry)); |
| if (BlobStatusIsPending(blob_entry->status())) { |
| blob_entry->building_state_->build_completion_callbacks.push_back( |
| base::BindOnce(&BlobStorageContext::OnDependentBlobFinished, |
| ptr_factory_.GetWeakPtr(), content->uuid_)); |
| num_building_dependent_blobs++; |
| } |
| } |
| |
| std::vector<ShareableBlobDataItem*> transport_items; |
| transport_items.reserve(content->pending_transport_items().size()); |
| for (const auto& item : content->pending_transport_items()) |
| transport_items.emplace_back(item.get()); |
| |
| std::vector<scoped_refptr<ShareableBlobDataItem>> pending_copy_items; |
| pending_copy_items.reserve(content->copies().size()); |
| for (const auto& copy : content->copies()) |
| pending_copy_items.emplace_back(copy.dest_item); |
| |
| auto previous_building_state = std::move(entry->building_state_); |
| entry->set_building_state(std::make_unique<BlobEntry::BuildingState>( |
| !content->pending_transport_items().empty(), |
| std::move(transport_allowed_callback), num_building_dependent_blobs)); |
| BlobEntry::BuildingState* building_state = entry->building_state_.get(); |
| building_state->copies = content->ReleaseCopies(); |
| std::swap(building_state->dependent_blobs, dependent_blobs); |
| std::swap(building_state->transport_items, 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); |
| building_state->build_aborted_callback = |
| std::move(previous_building_state->build_aborted_callback); |
| auto runner = base::ThreadTaskRunnerHandle::Get(); |
| for (auto& callback : previous_building_state->build_started_callbacks) |
| runner->PostTask(FROM_HERE, |
| base::BindOnce(std::move(callback), entry->status())); |
| } |
| |
| // Break ourselves if we have an error. BuildingState must be set first so the |
| // callback is called correctly. |
| if (BlobStatusIsError(entry->status())) { |
| CancelBuildingBlobInternal(entry, entry->status()); |
| return handle; |
| } |
| |
| // Avoid the state where we might grant only one quota. |
| if (!memory_controller_.CanReserveQuota(content->copy_quota_needed() + |
| content->transport_quota_needed())) { |
| CancelBuildingBlobInternal(entry, BlobStatus::ERR_OUT_OF_MEMORY); |
| return handle; |
| } |
| |
| if (content->copy_quota_needed() > 0) { |
| // The blob can complete during the execution of |ReserveMemoryQuota|. |
| base::WeakPtr<QuotaAllocationTask> pending_request = |
| memory_controller_.ReserveMemoryQuota( |
| std::move(pending_copy_items), |
| base::BindOnce(&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 (content->transport_quota_needed() > 0) { |
| base::WeakPtr<QuotaAllocationTask> pending_request; |
| |
| switch (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( |
| content->ReleasePendingTransportItems(), |
| base::BindOnce(&BlobStorageContext::OnEnoughSpaceForTransport, |
| ptr_factory_.GetWeakPtr(), content->uuid_, |
| std::move(empty_files))); |
| break; |
| } |
| case TransportQuotaType::FILE: |
| pending_request = memory_controller_.ReserveFileQuota( |
| content->ReleasePendingTransportItems(), |
| base::BindOnce(&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, |
| BlobStatusCallback done) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| DCHECK(entry); |
| if (BlobStatusIsPending(entry->status())) { |
| entry->building_state_->build_completion_callbacks.push_back( |
| std::move(done)); |
| return; |
| } |
| std::move(done).Run(entry->status()); |
| } |
| |
| void BlobStorageContext::RunOnConstructionBegin(const std::string& uuid, |
| BlobStatusCallback done) { |
| BlobEntry* entry = registry_.GetEntry(uuid); |
| DCHECK(entry); |
| if (entry->status() == BlobStatus::PENDING_CONSTRUCTION) { |
| entry->building_state_->build_started_callbacks.push_back(std::move(done)); |
| return; |
| } |
| std::move(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_REFERENCED_BLOBS); |
| 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 = |
| std::move(entry->building_state_->transport_allowed_callback); |
| } |
| if (entry->building_state_ && |
| entry->status() == BlobStatus::PENDING_CONSTRUCTION) { |
| auto runner = base::ThreadTaskRunnerHandle::Get(); |
| for (auto& callback : entry->building_state_->build_started_callbacks) |
| runner->PostTask(FROM_HERE, base::BindOnce(std::move(callback), reason)); |
| } |
| ClearAndFreeMemory(entry); |
| entry->set_status(reason); |
| if (transport_allowed_callback) { |
| std::move(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()); |
| BlobDataItem::Type dest_type = copy.dest_item->item()->type(); |
| switch (copy.source_item->item()->type()) { |
| case BlobDataItem::Type::kBytes: { |
| DCHECK_EQ(dest_type, BlobDataItem::Type::kBytesDescription); |
| base::span<const char> src_data = |
| copy.source_item->item()->bytes().subspan(copy.source_item_offset, |
| dest_size); |
| copy.dest_item->item()->PopulateBytes(src_data); |
| break; |
| } |
| case BlobDataItem::Type::kFile: { |
| // If we expected a memory item (and our source was paged to disk) we |
| // free that memory. |
| if (dest_type == BlobDataItem::Type::kBytesDescription) |
| copy.dest_item->set_memory_allocation(nullptr); |
| |
| const auto& source_item = copy.source_item->item(); |
| scoped_refptr<BlobDataItem> new_item = BlobDataItem::CreateFile( |
| source_item->path(), |
| source_item->offset() + copy.source_item_offset, dest_size, |
| source_item->expected_modification_time(), |
| source_item->data_handle_); |
| copy.dest_item->set_item(std::move(new_item)); |
| break; |
| } |
| case BlobDataItem::Type::kBytesDescription: |
| case BlobDataItem::Type::kFileFilesystem: |
| case BlobDataItem::Type::kDiskCacheEntry: |
| 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 (auto& callback : callbacks) |
| runner->PostTask(FROM_HERE, |
| base::BindOnce(std::move(callback), entry->status())); |
| |
| for (const auto& shareable_item : entry->items()) { |
| DCHECK_NE(BlobDataItem::Type::kBytesDescription, |
| 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_->CancelRequestsAndAbort(); |
| entry->ClearItems(); |
| entry->ClearOffsets(); |
| entry->set_size(0); |
| } |
| |
| bool BlobStorageContext::OnMemoryDump( |
| const base::trace_event::MemoryDumpArgs& args, |
| base::trace_event::ProcessMemoryDump* pmd) { |
| const char* system_allocator_name = |
| base::trace_event::MemoryDumpManager::GetInstance() |
| ->system_allocator_pool_name(); |
| |
| auto* mad = pmd->CreateAllocatorDump( |
| base::StringPrintf("site_storage/blob_storage/0x%" PRIXPTR, |
| reinterpret_cast<uintptr_t>(this))); |
| mad->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
| memory_controller().memory_usage()); |
| mad->AddScalar("disk_usage", |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, |
| memory_controller().disk_usage()); |
| mad->AddScalar("blob_count", |
| base::trace_event::MemoryAllocatorDump::kUnitsObjects, |
| blob_count()); |
| if (system_allocator_name) |
| pmd->AddSuballocation(mad->guid(), system_allocator_name); |
| return true; |
| } |
| |
| } // namespace storage |