blob: dfdfd539dc14eace171c9dee61ba4d425c123172 [file] [log] [blame]
// 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/browser/service_worker/service_worker_disk_cache_migrator.h"
#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_runner_util.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "content/common/service_worker/service_worker_types.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"
namespace content {
namespace {
// Disk cache entry data indices (Copied from appcache_diskcache.cc).
enum { kResponseInfoIndex, kResponseContentIndex, kResponseMetadataIndex };
} // namespace
// A task to move a cached resource from the src DiskCache to the dest
// DiskCache. This is owned by ServiceWorkerDiskCacheMigrator.
class ServiceWorkerDiskCacheMigrator::Task {
public:
using MigrationStatusCallback = base::Callback<void(MigrationStatus)>;
Task(InflightTaskMap::KeyType task_id,
int64 resource_id,
int32 data_size,
ServiceWorkerDiskCache* src,
ServiceWorkerDiskCache* dest,
const MigrationStatusCallback& callback);
~Task();
void Run();
InflightTaskMap::KeyType task_id() const { return task_id_; }
private:
void ReadResponseInfo();
void OnReadResponseInfo(
const scoped_refptr<HttpResponseInfoIOBuffer>& info_buffer,
int result);
void OnWriteResponseInfo(
const scoped_refptr<HttpResponseInfoIOBuffer>& info_buffer,
int result);
void WriteResponseMetadata(
const scoped_refptr<HttpResponseInfoIOBuffer>& info_buffer);
void OnWriteResponseMetadata(
const scoped_refptr<HttpResponseInfoIOBuffer>& info_buffer,
int result);
void ReadResponseData();
void OnReadResponseData(const scoped_refptr<net::IOBuffer>& buffer,
int result);
void OnWriteResponseData(int result);
InflightTaskMap::KeyType task_id_;
int64 resource_id_;
int32 data_size_;
MigrationStatusCallback callback_;
scoped_ptr<ServiceWorkerResponseReader> reader_;
scoped_ptr<ServiceWorkerResponseWriter> writer_;
scoped_ptr<ServiceWorkerResponseMetadataWriter> metadata_writer_;
base::WeakPtrFactory<Task> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(Task);
};
// A wrapper class for disk_cache::Entry. This is used for holding an open entry
// and ensuring that the entry gets closed on the dtor.
class ServiceWorkerDiskCacheMigrator::WrappedEntry {
public:
WrappedEntry() {}
~WrappedEntry() {
if (entry_)
entry_->Close();
}
disk_cache::Entry* Unwrap() {
disk_cache::Entry* entry = entry_;
entry_ = nullptr;
return entry;
}
disk_cache::Entry** GetPtr() { return &entry_; }
private:
disk_cache::Entry* entry_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WrappedEntry);
};
ServiceWorkerDiskCacheMigrator::Task::Task(
InflightTaskMap::KeyType task_id,
int64 resource_id,
int32 data_size,
ServiceWorkerDiskCache* src,
ServiceWorkerDiskCache* dest,
const MigrationStatusCallback& callback)
: task_id_(task_id),
resource_id_(resource_id),
data_size_(data_size),
callback_(callback),
weak_factory_(this) {
DCHECK_LE(0, data_size_);
reader_.reset(new ServiceWorkerResponseReader(resource_id, src));
writer_.reset(new ServiceWorkerResponseWriter(resource_id, dest));
metadata_writer_.reset(
new ServiceWorkerResponseMetadataWriter(resource_id, dest));
}
ServiceWorkerDiskCacheMigrator::Task::~Task() {
}
void ServiceWorkerDiskCacheMigrator::Task::Run() {
ReadResponseInfo();
}
void ServiceWorkerDiskCacheMigrator::Task::ReadResponseInfo() {
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer(
new HttpResponseInfoIOBuffer);
reader_->ReadInfo(info_buffer.get(),
base::Bind(&Task::OnReadResponseInfo,
weak_factory_.GetWeakPtr(), info_buffer));
}
void ServiceWorkerDiskCacheMigrator::Task::OnReadResponseInfo(
const scoped_refptr<HttpResponseInfoIOBuffer>& info_buffer,
int result) {
if (result < 0) {
LOG(ERROR) << "Failed to read the response info";
callback_.Run(MigrationStatus::ERROR_READ_RESPONSE_INFO);
return;
}
writer_->WriteInfo(info_buffer.get(),
base::Bind(&Task::OnWriteResponseInfo,
weak_factory_.GetWeakPtr(), info_buffer));
}
void ServiceWorkerDiskCacheMigrator::Task::OnWriteResponseInfo(
const scoped_refptr<HttpResponseInfoIOBuffer>& buffer,
int result) {
if (result < 0) {
LOG(ERROR) << "Failed to write the response info";
callback_.Run(MigrationStatus::ERROR_WRITE_RESPONSE_INFO);
return;
}
const net::HttpResponseInfo* http_info = buffer->http_info.get();
if (http_info->metadata && http_info->metadata->size()) {
WriteResponseMetadata(buffer);
return;
}
ReadResponseData();
}
void ServiceWorkerDiskCacheMigrator::Task::WriteResponseMetadata(
const scoped_refptr<HttpResponseInfoIOBuffer>& info_buffer) {
const net::HttpResponseInfo* http_info = info_buffer->http_info.get();
DCHECK(http_info->metadata);
DCHECK(http_info->metadata->size());
// |wrapped_buffer| does not own the given metadata buffer, so a callback
// for WriteMetadata keeps |info_buffer| which is the real owner of the
// metadata buffer.
scoped_refptr<net::WrappedIOBuffer> wrapped_buffer =
new net::WrappedIOBuffer(http_info->metadata->data());
metadata_writer_->WriteMetadata(
wrapped_buffer.get(), http_info->metadata->size(),
base::Bind(&Task::OnWriteResponseMetadata, weak_factory_.GetWeakPtr(),
info_buffer));
}
void ServiceWorkerDiskCacheMigrator::Task::OnWriteResponseMetadata(
const scoped_refptr<HttpResponseInfoIOBuffer>& info_buffer,
int result) {
if (result < 0) {
LOG(ERROR) << "Failed to write the response metadata";
callback_.Run(MigrationStatus::ERROR_WRITE_RESPONSE_METADATA);
return;
}
DCHECK_EQ(info_buffer->http_info->metadata->size(), result);
ReadResponseData();
}
void ServiceWorkerDiskCacheMigrator::Task::ReadResponseData() {
scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(data_size_);
reader_->ReadData(buffer.get(), data_size_,
base::Bind(&Task::OnReadResponseData,
weak_factory_.GetWeakPtr(), buffer));
}
void ServiceWorkerDiskCacheMigrator::Task::OnReadResponseData(
const scoped_refptr<net::IOBuffer>& buffer,
int result) {
if (result < 0) {
LOG(ERROR) << "Failed to read the response data";
callback_.Run(MigrationStatus::ERROR_READ_RESPONSE_DATA);
return;
}
DCHECK_EQ(data_size_, result);
writer_->WriteData(
buffer.get(), result,
base::Bind(&Task::OnWriteResponseData, weak_factory_.GetWeakPtr()));
}
void ServiceWorkerDiskCacheMigrator::Task::OnWriteResponseData(int result) {
if (result < 0) {
LOG(ERROR) << "Failed to write the response data";
callback_.Run(MigrationStatus::ERROR_WRITE_RESPONSE_DATA);
return;
}
DCHECK_EQ(data_size_, result);
callback_.Run(MigrationStatus::OK);
}
ServiceWorkerDiskCacheMigrator::ServiceWorkerDiskCacheMigrator(
const base::FilePath& src_path,
const base::FilePath& dest_path,
int max_disk_cache_size,
const scoped_refptr<base::SingleThreadTaskRunner>& disk_cache_thread)
: src_path_(src_path),
dest_path_(dest_path),
max_disk_cache_size_(max_disk_cache_size),
disk_cache_thread_(disk_cache_thread),
weak_factory_(this) {
}
ServiceWorkerDiskCacheMigrator::~ServiceWorkerDiskCacheMigrator() {
}
void ServiceWorkerDiskCacheMigrator::Start(const StatusCallback& callback) {
callback_ = callback;
start_time_ = base::TimeTicks::Now();
#if defined(OS_ANDROID)
PostTaskAndReplyWithResult(
disk_cache_thread_.get(), FROM_HERE,
base::Bind(&MigrateForAndroid, src_path_, dest_path_),
base::Bind(&ServiceWorkerDiskCacheMigrator::Complete,
weak_factory_.GetWeakPtr()));
#else
PostTaskAndReplyWithResult(
disk_cache_thread_.get(), FROM_HERE,
base::Bind(&base::DeleteFile, dest_path_, true),
base::Bind(&ServiceWorkerDiskCacheMigrator::DidDeleteDestDirectory,
weak_factory_.GetWeakPtr()));
#endif // defined(OS_ANDROID)
}
#if defined(OS_ANDROID)
// static
ServiceWorkerDiskCacheMigrator::MigrationStatus
ServiceWorkerDiskCacheMigrator::MigrateForAndroid(
const base::FilePath& src_path,
const base::FilePath& dest_path) {
// Continue the migration regardless of the deletion result. If the migrator
// cannot proceed or the diskcache gets corrupted due to the failure, the
// storage detects it and recovers by DeleteAndStartOver.
base::DeleteFile(dest_path, true);
if (!base::DirectoryExists(src_path))
return MigrationStatus::OK;
// Android has alredy used the Simple backend. Just move the existing
// diskcache files to a new location.
if (base::Move(src_path, dest_path))
return MigrationStatus::OK;
return MigrationStatus::ERROR_MOVE_DISKCACHE;
}
#endif // defined(OS_ANDROID)
void ServiceWorkerDiskCacheMigrator::DidDeleteDestDirectory(bool deleted) {
// Continue the migration regardless of the deletion result. If the migrator
// cannot proceed or the diskcache gets corrupted due to the failure, the
// storage detects it and recovers by DeleteAndStartOver.
src_ = ServiceWorkerDiskCache::CreateWithBlockFileBackend();
dest_ = ServiceWorkerDiskCache::CreateWithSimpleBackend();
scoped_ptr<MigrationStatus> status(new MigrationStatus(MigrationStatus::OK));
MigrationStatus* status_ptr = status.get();
// This closure is called when both diskcaches are initialized.
base::Closure barrier_closure = base::BarrierClosure(
2, base::Bind(&ServiceWorkerDiskCacheMigrator::DidInitializeAllDiskCaches,
weak_factory_.GetWeakPtr(), base::Passed(status.Pass())));
// Initialize the src DiskCache. |status_ptr| is guaranteed to be valid until
// calling |barrier_closure| which is the owner of the object.
net::CompletionCallback src_callback =
base::Bind(&ServiceWorkerDiskCacheMigrator::DidInitializeSrcDiskCache,
weak_factory_.GetWeakPtr(), barrier_closure, status_ptr);
int result = src_->InitWithDiskBackend(src_path_, max_disk_cache_size_,
false /* force */, disk_cache_thread_,
src_callback);
if (result != net::ERR_IO_PENDING)
src_callback.Run(result);
// Initialize the dest DiskCache. |status_ptr| is guaranteed to be valid until
// calling |barrier_closure| which is the owner of the object.
net::CompletionCallback dest_callback =
base::Bind(&ServiceWorkerDiskCacheMigrator::DidInitializeDestDiskCache,
weak_factory_.GetWeakPtr(), barrier_closure, status_ptr);
result = dest_->InitWithDiskBackend(dest_path_, max_disk_cache_size_,
false /* force */, disk_cache_thread_,
dest_callback);
if (result != net::ERR_IO_PENDING)
dest_callback.Run(result);
}
void ServiceWorkerDiskCacheMigrator::DidInitializeSrcDiskCache(
const base::Closure& barrier_closure,
MigrationStatus* status_ptr,
int result) {
if (result != net::OK)
*status_ptr = MigrationStatus::ERROR_INIT_SRC_DISKCACHE;
barrier_closure.Run();
}
void ServiceWorkerDiskCacheMigrator::DidInitializeDestDiskCache(
const base::Closure& barrier_closure,
MigrationStatus* status_ptr,
int result) {
if (result != net::OK)
*status_ptr = MigrationStatus::ERROR_INIT_DEST_DISKCACHE;
barrier_closure.Run();
}
void ServiceWorkerDiskCacheMigrator::DidInitializeAllDiskCaches(
scoped_ptr<MigrationStatus> status) {
if (*status != MigrationStatus::OK) {
LOG(ERROR) << "Failed to initialize the diskcache";
Complete(*status);
return;
}
// Iterate through existing entries in the src DiskCache.
iterator_ = src_->disk_cache()->CreateIterator();
OpenNextEntry();
}
void ServiceWorkerDiskCacheMigrator::OpenNextEntry() {
DCHECK(!pending_task_);
DCHECK(!is_iterating_);
is_iterating_ = true;
scoped_ptr<WrappedEntry> wrapped_entry(new WrappedEntry);
disk_cache::Entry** entry_ptr = wrapped_entry->GetPtr();
net::CompletionCallback callback = base::Bind(
&ServiceWorkerDiskCacheMigrator::OnNextEntryOpened,
weak_factory_.GetWeakPtr(), base::Passed(wrapped_entry.Pass()));
int result = iterator_->OpenNextEntry(entry_ptr, callback);
if (result == net::ERR_IO_PENDING)
return;
callback.Run(result);
}
void ServiceWorkerDiskCacheMigrator::OnNextEntryOpened(
scoped_ptr<WrappedEntry> wrapped_entry,
int result) {
DCHECK(!pending_task_);
is_iterating_ = false;
if (result == net::ERR_FAILED) {
// ERR_FAILED means the iterator reached the end of the enumeration.
if (inflight_tasks_.IsEmpty())
Complete(MigrationStatus::OK);
return;
}
if (result != net::OK) {
LOG(ERROR) << "Failed to open the next entry";
inflight_tasks_.Clear();
Complete(MigrationStatus::ERROR_OPEN_NEXT_ENTRY);
return;
}
disk_cache::ScopedEntryPtr scoped_entry(wrapped_entry->Unwrap());
DCHECK(scoped_entry);
int64 resource_id = kInvalidServiceWorkerResourceId;
if (!base::StringToInt64(scoped_entry->GetKey(), &resource_id)) {
LOG(ERROR) << "Failed to read the resource id";
inflight_tasks_.Clear();
Complete(MigrationStatus::ERROR_READ_ENTRY_KEY);
return;
}
InflightTaskMap::KeyType task_id = next_task_id_++;
pending_task_.reset(new Task(
task_id, resource_id, scoped_entry->GetDataSize(kResponseContentIndex),
src_.get(), dest_.get(),
base::Bind(&ServiceWorkerDiskCacheMigrator::OnEntryMigrated,
weak_factory_.GetWeakPtr(), task_id)));
if (inflight_tasks_.size() < max_number_of_inflight_tasks_) {
RunPendingTask();
OpenNextEntry();
return;
}
// |pending_task_| will run when an inflight task is completed.
}
void ServiceWorkerDiskCacheMigrator::RunPendingTask() {
DCHECK(pending_task_);
DCHECK_GT(max_number_of_inflight_tasks_, inflight_tasks_.size());
InflightTaskMap::KeyType task_id = pending_task_->task_id();
pending_task_->Run();
inflight_tasks_.AddWithID(pending_task_.release(), task_id);
}
void ServiceWorkerDiskCacheMigrator::OnEntryMigrated(
InflightTaskMap::KeyType task_id,
MigrationStatus status) {
DCHECK(inflight_tasks_.Lookup(task_id));
inflight_tasks_.Remove(task_id);
if (status != MigrationStatus::OK) {
inflight_tasks_.Clear();
pending_task_.reset();
Complete(status);
return;
}
++number_of_migrated_resources_;
if (pending_task_) {
RunPendingTask();
OpenNextEntry();
return;
}
if (is_iterating_)
return;
if (inflight_tasks_.IsEmpty())
Complete(MigrationStatus::OK);
}
void ServiceWorkerDiskCacheMigrator::Complete(MigrationStatus status) {
DCHECK(inflight_tasks_.IsEmpty());
DCHECK(!pending_task_);
if (status == MigrationStatus::OK) {
RecordMigrationTime(base::TimeTicks().Now() - start_time_);
RecordNumberOfMigratedResources(number_of_migrated_resources_);
}
RecordMigrationResult(status);
// Invalidate weakptrs to ensure that other running operations do not call
// OnEntryMigrated().
weak_factory_.InvalidateWeakPtrs();
// Use PostTask to avoid deleting AppCacheDiskCache in the middle of the
// execution (see http://crbug.com/502420).
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&ServiceWorkerDiskCacheMigrator::RunUserCallback,
weak_factory_.GetWeakPtr(),
status == MigrationStatus::OK ? SERVICE_WORKER_OK
: SERVICE_WORKER_ERROR_FAILED));
}
void ServiceWorkerDiskCacheMigrator::RunUserCallback(
ServiceWorkerStatusCode status) {
src_.reset();
dest_.reset();
callback_.Run(status);
}
void ServiceWorkerDiskCacheMigrator::RecordMigrationResult(
MigrationStatus status) {
UMA_HISTOGRAM_ENUMERATION("ServiceWorker.DiskCacheMigrator.MigrationResult",
static_cast<int>(status),
static_cast<int>(MigrationStatus::NUM_TYPES));
}
void ServiceWorkerDiskCacheMigrator::RecordNumberOfMigratedResources(
size_t migrated_resources) {
UMA_HISTOGRAM_COUNTS_1000(
"ServiceWorker.DiskCacheMigrator.NumberOfMigratedResources",
migrated_resources);
}
void ServiceWorkerDiskCacheMigrator::RecordMigrationTime(
const base::TimeDelta& time) {
UMA_HISTOGRAM_MEDIUM_TIMES("ServiceWorker.DiskCacheMigrator.MigrationTime",
time);
}
} // namespace content