|  | // 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_INTERNALS); | 
|  | } | 
|  |  | 
|  | 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_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 = | 
|  | 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 |