| // Copyright 2015 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 "content/child/blob_storage/blob_transport_controller.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/lazy_instance.h" |
| #include "base/memory/scoped_vector.h" |
| #include "base/memory/shared_memory.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "content/child/blob_storage/blob_consolidation.h" |
| #include "content/child/child_process.h" |
| #include "content/child/thread_safe_sender.h" |
| #include "content/common/fileapi/webblob_messages.h" |
| #include "ipc/ipc_sender.h" |
| #include "storage/common/blob_storage/blob_item_bytes_request.h" |
| #include "storage/common/blob_storage/blob_item_bytes_response.h" |
| #include "storage/common/data_element.h" |
| #include "third_party/WebKit/public/platform/Platform.h" |
| |
| using base::SharedMemory; |
| using base::SharedMemoryHandle; |
| using storage::BlobItemBytesRequest; |
| using storage::BlobItemBytesResponse; |
| using storage::IPCBlobItemRequestStrategy; |
| using storage::DataElement; |
| using storage::kBlobStorageIPCThresholdBytes; |
| |
| namespace content { |
| |
| using storage::IPCBlobCreationCancelCode; |
| |
| using ConsolidatedItem = BlobConsolidation::ConsolidatedItem; |
| using ReadStatus = BlobConsolidation::ReadStatus; |
| |
| namespace { |
| static base::LazyInstance<BlobTransportController>::Leaky g_controller = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| // This keeps the process alive while blobs are being transferred. |
| // These need to be called on the main thread. |
| void IncChildProcessRefCount() { |
| blink::Platform::current()->suddenTerminationChanged(false); |
| ChildProcess::current()->AddRefProcess(); |
| } |
| |
| void DecChildProcessRefCount() { |
| blink::Platform::current()->suddenTerminationChanged(true); |
| ChildProcess::current()->ReleaseProcess(); |
| } |
| } // namespace |
| |
| BlobTransportController* BlobTransportController::GetInstance() { |
| return g_controller.Pointer(); |
| } |
| |
| // static |
| void BlobTransportController::InitiateBlobTransfer( |
| const std::string& uuid, |
| const std::string& content_type, |
| scoped_ptr<BlobConsolidation> consolidation, |
| scoped_refptr<ThreadSafeSender> sender, |
| base::SingleThreadTaskRunner* io_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> main_runner) { |
| if (main_runner->BelongsToCurrentThread()) { |
| IncChildProcessRefCount(); |
| } else { |
| main_runner->PostTask(FROM_HERE, base::Bind(&IncChildProcessRefCount)); |
| } |
| |
| std::vector<storage::DataElement> descriptions; |
| std::set<std::string> referenced_blobs = consolidation->referenced_blobs(); |
| BlobTransportController::GetDescriptions( |
| consolidation.get(), kBlobStorageIPCThresholdBytes, &descriptions); |
| // I post the task first to make sure that we store our consolidation before |
| // we get a request back from the browser. |
| io_runner->PostTask( |
| FROM_HERE, |
| base::Bind(&BlobTransportController::StoreBlobDataForRequests, |
| base::Unretained(BlobTransportController::GetInstance()), uuid, |
| base::Passed(std::move(consolidation)), |
| base::Passed(std::move(main_runner)))); |
| // TODO(dmurph): Merge register and start messages. |
| sender->Send(new BlobStorageMsg_RegisterBlobUUID(uuid, content_type, "", |
| referenced_blobs)); |
| sender->Send(new BlobStorageMsg_StartBuildingBlob(uuid, descriptions)); |
| } |
| |
| void BlobTransportController::OnMemoryRequest( |
| const std::string& uuid, |
| const std::vector<storage::BlobItemBytesRequest>& requests, |
| std::vector<base::SharedMemoryHandle>* memory_handles, |
| const std::vector<IPC::PlatformFileForTransit>& file_handles, |
| IPC::Sender* sender) { |
| std::vector<storage::BlobItemBytesResponse> responses; |
| ResponsesStatus status = |
| GetResponses(uuid, requests, memory_handles, file_handles, &responses); |
| |
| switch (status) { |
| case ResponsesStatus::BLOB_NOT_FOUND: |
| // sender->Send(new BlobStorageMsg_CancelBuildingBlob(uuid, |
| // IPCBlobCreationCancelCode::UNKNOWN)); |
| return; |
| case ResponsesStatus::SHARED_MEMORY_MAP_FAILED: |
| // This would happen if the renderer process doesn't have enough memory |
| // to map the shared memory, which is possible if we don't have much |
| // memory. If this scenario happens often, we could delay the response |
| // until we have enough memory. For now we just fail. |
| CHECK(false) << "Unable to map shared memory to send blob " << uuid |
| << "."; |
| break; |
| case ResponsesStatus::SUCCESS: |
| break; |
| } |
| |
| sender->Send(new BlobStorageMsg_MemoryItemResponse(uuid, responses)); |
| } |
| |
| void BlobTransportController::OnCancel( |
| const std::string& uuid, |
| storage::IPCBlobCreationCancelCode code) { |
| DVLOG(1) << "Received blob cancel for blob " << uuid |
| << " with code: " << static_cast<int>(code); |
| ReleaseBlobConsolidation(uuid); |
| } |
| |
| void BlobTransportController::OnDone(const std::string& uuid) { |
| ReleaseBlobConsolidation(uuid); |
| } |
| |
| // static |
| void BlobTransportController::GetDescriptions( |
| BlobConsolidation* consolidation, |
| size_t max_data_population, |
| std::vector<storage::DataElement>* out) { |
| DCHECK(out->empty()); |
| DCHECK(consolidation); |
| const auto& consolidated_items = consolidation->consolidated_items(); |
| |
| size_t current_memory_population = 0; |
| size_t current_item = 0; |
| out->reserve(consolidated_items.size()); |
| for (const ConsolidatedItem& item : consolidated_items) { |
| out->push_back(DataElement()); |
| auto& element = out->back(); |
| |
| switch (item.type) { |
| case DataElement::TYPE_BYTES: { |
| size_t bytes_length = static_cast<size_t>(item.length); |
| if (current_memory_population + bytes_length <= max_data_population) { |
| element.SetToAllocatedBytes(bytes_length); |
| consolidation->ReadMemory(current_item, 0, bytes_length, |
| element.mutable_bytes()); |
| current_memory_population += bytes_length; |
| } else { |
| element.SetToBytesDescription(bytes_length); |
| } |
| break; |
| } |
| case DataElement::TYPE_FILE: { |
| element.SetToFilePathRange( |
| item.path, item.offset, item.length, |
| base::Time::FromDoubleT(item.expected_modification_time)); |
| break; |
| } |
| case DataElement::TYPE_BLOB: { |
| element.SetToBlobRange(item.blob_uuid, item.offset, item.length); |
| break; |
| } |
| case DataElement::TYPE_FILE_FILESYSTEM: { |
| element.SetToFileSystemUrlRange( |
| item.filesystem_url, item.offset, item.length, |
| base::Time::FromDoubleT(item.expected_modification_time)); |
| break; |
| } |
| case DataElement::TYPE_DISK_CACHE_ENTRY: |
| case DataElement::TYPE_BYTES_DESCRIPTION: |
| case DataElement::TYPE_UNKNOWN: |
| NOTREACHED(); |
| } |
| ++current_item; |
| } |
| } |
| |
| BlobTransportController::BlobTransportController() {} |
| |
| BlobTransportController::~BlobTransportController() {} |
| |
| void BlobTransportController::ClearForTesting() { |
| if (!blob_storage_.empty() && main_thread_runner_) { |
| main_thread_runner_->PostTask(FROM_HERE, |
| base::Bind(&DecChildProcessRefCount)); |
| } |
| blob_storage_.clear(); |
| } |
| |
| void BlobTransportController::StoreBlobDataForRequests( |
| const std::string& uuid, |
| scoped_ptr<BlobConsolidation> consolidation, |
| scoped_refptr<base::SingleThreadTaskRunner> main_runner) { |
| if (!main_thread_runner_.get()) { |
| main_thread_runner_ = std::move(main_runner); |
| } |
| blob_storage_[uuid] = std::move(consolidation); |
| } |
| |
| BlobTransportController::ResponsesStatus BlobTransportController::GetResponses( |
| const std::string& uuid, |
| const std::vector<BlobItemBytesRequest>& requests, |
| std::vector<SharedMemoryHandle>* memory_handles, |
| const std::vector<IPC::PlatformFileForTransit>& file_handles, |
| std::vector<BlobItemBytesResponse>* out) { |
| DCHECK(out->empty()); |
| auto it = blob_storage_.find(uuid); |
| if (it == blob_storage_.end()) |
| return ResponsesStatus::BLOB_NOT_FOUND; |
| |
| BlobConsolidation* consolidation = it->second.get(); |
| const auto& consolidated_items = consolidation->consolidated_items(); |
| |
| // Since we can be writing to the same shared memory handle from multiple |
| // requests, we keep them in a vector and lazily create them. |
| ScopedVector<SharedMemory> opened_memory; |
| opened_memory.resize(memory_handles->size()); |
| for (const BlobItemBytesRequest& request : requests) { |
| DCHECK_LT(request.renderer_item_index, consolidated_items.size()) |
| << "Invalid item index"; |
| |
| const ConsolidatedItem& item = |
| consolidated_items[request.renderer_item_index]; |
| DCHECK_LE(request.renderer_item_offset + request.size, item.length) |
| << "Invalid data range"; |
| DCHECK_EQ(item.type, DataElement::TYPE_BYTES) << "Invalid element type"; |
| |
| out->push_back(BlobItemBytesResponse(request.request_number)); |
| switch (request.transport_strategy) { |
| case IPCBlobItemRequestStrategy::IPC: { |
| BlobItemBytesResponse& response = out->back(); |
| ReadStatus status = consolidation->ReadMemory( |
| request.renderer_item_index, request.renderer_item_offset, |
| request.size, response.allocate_mutable_data(request.size)); |
| DCHECK(status == ReadStatus::OK) |
| << "Error reading from consolidated blob: " |
| << static_cast<int>(status); |
| break; |
| } |
| case IPCBlobItemRequestStrategy::SHARED_MEMORY: { |
| DCHECK_LT(request.handle_index, memory_handles->size()) |
| << "Invalid handle index."; |
| SharedMemory* memory = opened_memory[request.handle_index]; |
| if (!memory) { |
| SharedMemoryHandle& handle = (*memory_handles)[request.handle_index]; |
| DCHECK(SharedMemory::IsHandleValid(handle)); |
| scoped_ptr<SharedMemory> shared_memory( |
| new SharedMemory(handle, false)); |
| if (!shared_memory->Map(request.size)) |
| return ResponsesStatus::SHARED_MEMORY_MAP_FAILED; |
| memory = shared_memory.get(); |
| opened_memory[request.handle_index] = shared_memory.release(); |
| } |
| CHECK(memory->memory()) << "Couldn't map memory for blob transfer."; |
| ReadStatus status = consolidation->ReadMemory( |
| request.renderer_item_index, request.renderer_item_offset, |
| request.size, |
| static_cast<char*>(memory->memory()) + request.handle_offset); |
| DCHECK(status == ReadStatus::OK) |
| << "Error reading from consolidated blob: " |
| << static_cast<int>(status); |
| break; |
| } |
| case IPCBlobItemRequestStrategy::FILE: |
| NOTREACHED() << "TODO(dmurph): Not implemented."; |
| break; |
| case IPCBlobItemRequestStrategy::UNKNOWN: |
| NOTREACHED(); |
| break; |
| } |
| } |
| return ResponsesStatus::SUCCESS; |
| } |
| |
| void BlobTransportController::ReleaseBlobConsolidation( |
| const std::string& uuid) { |
| if (blob_storage_.erase(uuid)) { |
| main_thread_runner_->PostTask(FROM_HERE, |
| base::Bind(&DecChildProcessRefCount)); |
| } |
| } |
| |
| } // namespace content |