Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(345)

Unified Diff: content/browser/service_worker/service_worker_write_to_cache_job.cc

Issue 1166433003: Service Worker: Don't write to disk during update until proven necessary (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: review comments Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/browser/service_worker/service_worker_write_to_cache_job.cc
diff --git a/content/browser/service_worker/service_worker_write_to_cache_job.cc b/content/browser/service_worker/service_worker_write_to_cache_job.cc
index b04e98b3c15d949f752b8359940cfdef19f0ec7e..f2ad7061fcec7f06bc66239cebd552dec938a0c7 100644
--- a/content/browser/service_worker/service_worker_write_to_cache_job.cc
+++ b/content/browser/service_worker/service_worker_write_to_cache_job.cc
@@ -4,7 +4,8 @@
#include "content/browser/service_worker/service_worker_write_to_cache_job.h"
-#include "base/strings/stringprintf.h"
+#include <algorithm>
+
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/service_worker/service_worker_context_core.h"
@@ -39,7 +40,259 @@ const char kRedirectError[] =
"The script resource is behind a redirect, which is disallowed.";
const char kServiceWorkerAllowed[] = "Service-Worker-Allowed";
-}
+const int kBufferSize = 16 * 1024;
+
+} // namespace
+
+// Reads an existing resource and copies it via
+// ServiceWorkerWriteToCacheJob::WriteData.
+class ServiceWorkerWriteToCacheJob::Copier : public base::RefCounted<Copier> {
+ public:
+ Copier(base::WeakPtr<ServiceWorkerWriteToCacheJob> owner,
+ scoped_ptr<ServiceWorkerResponseReader> reader,
+ int bytes_to_copy,
+ const base::Callback<void(ServiceWorkerStatusCode)>& callback)
+ : owner_(owner),
+ reader_(reader.release()),
+ bytes_to_copy_(bytes_to_copy),
+ callback_(callback) {
+ DCHECK_LT(0, bytes_to_copy_);
+ }
+
+ void Start() {
+ io_buffer_ = new net::IOBuffer(kBufferSize);
+ ReadSomeData();
+ }
+
+ private:
+ friend class base::RefCounted<Copier>;
+ ~Copier() {}
+
+ void ReadSomeData() {
+ reader_->ReadData(io_buffer_.get(), kBufferSize,
+ base::Bind(&Copier::OnReadDataComplete, this));
+ }
+
+ void OnReadDataComplete(int result) {
+ if (!owner_)
+ return;
+ if (result <= 0) {
+ // We hit EOF or error but weren't done copying.
+ Complete(SERVICE_WORKER_ERROR_FAILED);
+ return;
+ }
+
+ int bytes_to_write = std::min(bytes_to_copy_, result);
+ owner_->WriteData(io_buffer_.get(), bytes_to_write,
+ base::Bind(&Copier::OnWriteDataComplete, this));
+ }
+
+ void OnWriteDataComplete(int result) {
+ if (result < 0) {
+ Complete(SERVICE_WORKER_ERROR_FAILED);
+ return;
+ }
+
+ DCHECK_LE(result, bytes_to_copy_);
+ bytes_to_copy_ -= result;
+ if (bytes_to_copy_ == 0) {
+ Complete(SERVICE_WORKER_OK);
+ return;
+ }
+
+ ReadSomeData();
+ }
+
+ void Complete(ServiceWorkerStatusCode status) {
+ if (!owner_)
+ return;
+ callback_.Run(status);
+ }
+
+ base::WeakPtr<ServiceWorkerWriteToCacheJob> owner_;
+ scoped_ptr<ServiceWorkerResponseReader> reader_;
+ int bytes_to_copy_ = 0;
+ base::Callback<void(ServiceWorkerStatusCode)> callback_;
+ scoped_refptr<net::IOBuffer> io_buffer_;
+};
+
+// Abstract consumer for ServiceWorkerWriteToCacheJob that processes data from
+// the network.
+class ServiceWorkerWriteToCacheJob::NetDataConsumer {
+ public:
+ virtual ~NetDataConsumer() {}
+
+ // Called by |owner_|'s OnResponseStarted.
+ virtual void OnResponseStarted() = 0;
+
+ // HandleData should call |owner_|->NotifyReadComplete() when done.
+ virtual void HandleData(net::IOBuffer* buf, int size) = 0;
+};
+
+// Dumb consumer that just writes everything it sees to disk.
+class ServiceWorkerWriteToCacheJob::PassThroughConsumer
+ : public ServiceWorkerWriteToCacheJob::NetDataConsumer {
+ public:
+ explicit PassThroughConsumer(ServiceWorkerWriteToCacheJob* owner)
+ : owner_(owner), weak_factory_(this) {}
+ ~PassThroughConsumer() override {}
+
+ void OnResponseStarted() override {
+ owner_->WriteHeaders(
+ base::Bind(&PassThroughConsumer::OnWriteHeadersComplete,
+ weak_factory_.GetWeakPtr()));
+ owner_->SetPendingIO();
+ }
+
+ void HandleData(net::IOBuffer* buf, int size) override {
+ if (size == 0) {
+ owner_->OnPassThroughComplete();
+ return;
+ }
+
+ owner_->WriteData(buf, size,
+ base::Bind(&PassThroughConsumer::OnWriteDataComplete,
+ weak_factory_.GetWeakPtr()));
+ owner_->SetPendingIO();
+ }
+
+ private:
+ void OnWriteHeadersComplete() {
+ owner_->ClearPendingIO();
+ owner_->CommitHeadersAndNotifyHeadersComplete();
+ }
+
+ void OnWriteDataComplete(int result) {
+ owner_->ClearPendingIO();
+ owner_->NotifyReadComplete(result);
+ }
+
+ ServiceWorkerWriteToCacheJob* owner_;
+ base::WeakPtrFactory<PassThroughConsumer> weak_factory_;
+};
+
+// Compares an existing resource with data progressively fed to it.
+// Calls back to |owner|->OnCompareComplete once done.
+class ServiceWorkerWriteToCacheJob::Comparer
+ : public ServiceWorkerWriteToCacheJob::NetDataConsumer {
+ public:
+ Comparer(ServiceWorkerWriteToCacheJob* owner,
+ scoped_ptr<ServiceWorkerResponseReader> reader)
+ : owner_(owner), reader_(reader.release()), weak_factory_(this) {}
+ ~Comparer() override {}
+
+ void OnResponseStarted() override {
+ owner_->CommitHeadersAndNotifyHeadersComplete();
+ }
+
+ void HandleData(net::IOBuffer* buf, int size) override {
+ net_data_ = buf;
+ net_data_offset_ = 0;
+ net_data_size_ = size;
+
+ if (size == 0 && info_) {
+ Complete(bytes_matched_ == info_->response_data_size);
+ return;
+ }
+
+ if (!info_) {
+ read_buffer_ = new net::IOBuffer(kBufferSize);
+ info_ = new HttpResponseInfoIOBuffer;
+ reader_->ReadInfo(info_.get(), base::Bind(&Comparer::OnReadInfoComplete,
+ weak_factory_.GetWeakPtr()));
+ owner_->SetPendingIO();
+ return;
+ }
+
+ ReadSomeData();
+ owner_->SetPendingIO();
+ }
+
+ private:
+ int bytes_remaining() {
+ DCHECK(net_data_);
+ DCHECK_LE(0, net_data_offset_);
+ DCHECK_LE(net_data_offset_, net_data_size_);
+ return net_data_size_ - net_data_offset_;
+ }
+
+ void OnReadInfoComplete(int result) {
+ if (result < 0) {
+ Complete(false);
+ return;
+ }
+
+ if (bytes_remaining() == 0) {
+ Complete(bytes_matched_ == info_->response_data_size);
+ return;
+ }
+
+ ReadSomeData();
+ }
+
+ void ReadSomeData() {
+ DCHECK_LT(0, bytes_remaining());
+ int bytes_to_read = std::min(bytes_remaining(), kBufferSize);
+ reader_->ReadData(
+ read_buffer_.get(), bytes_to_read,
+ base::Bind(&Comparer::OnReadDataComplete, weak_factory_.GetWeakPtr()));
+ }
+
+ void OnReadDataComplete(int result) {
+ if (result <= 0) {
+ // We hit error or EOF but had more to compare.
+ Complete(false);
+ return;
+ }
+
+ DCHECK_LE(result, bytes_remaining());
+ if (memcmp(net_data_->data() + net_data_offset_, read_buffer_->data(),
+ result) != 0) {
+ Complete(false);
+ return;
+ }
+
+ net_data_offset_ += result;
+ if (bytes_remaining() == 0) {
+ NotifyReadComplete();
+ return;
+ }
+
+ ReadSomeData();
+ }
+
+ // Completes one HandleData() call.
+ void NotifyReadComplete() {
+ int size = net_data_size_;
+ net_data_ = nullptr;
+ net_data_offset_ = 0;
+ net_data_size_ = 0;
+
+ bytes_matched_ += size;
+ owner_->ClearPendingIO();
+ owner_->NotifyReadComplete(size);
+ }
+
+ // Completes the entire Comparer.
+ void Complete(bool is_equal) {
+ owner_->OnCompareComplete(bytes_matched_, is_equal);
+ }
+
+ ServiceWorkerWriteToCacheJob* owner_;
+ scoped_ptr<ServiceWorkerResponseReader> reader_;
+ scoped_refptr<net::IOBuffer> read_buffer_;
+ scoped_refptr<HttpResponseInfoIOBuffer> info_;
+
+ // Cumulative number of bytes successfully compared.
+ int bytes_matched_ = 0;
+
+ // State used for one HandleData() call.
+ scoped_refptr<net::IOBuffer> net_data_;
+ int net_data_offset_ = 0;
+ int net_data_size_ = 0;
+
+ base::WeakPtrFactory<Comparer> weak_factory_;
+};
ServiceWorkerWriteToCacheJob::ServiceWorkerWriteToCacheJob(
net::URLRequest* request,
@@ -48,12 +301,14 @@ ServiceWorkerWriteToCacheJob::ServiceWorkerWriteToCacheJob(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerVersion* version,
int extra_load_flags,
- int64 response_id)
+ int64 response_id,
+ int64 incumbent_response_id)
: net::URLRequestJob(request, network_delegate),
resource_type_(resource_type),
context_(context),
url_(request->url()),
response_id_(response_id),
+ incumbent_response_id_(incumbent_response_id),
version_(version),
has_been_killed_(false),
did_notify_started_(false),
@@ -76,6 +331,14 @@ void ServiceWorkerWriteToCacheJob::Start() {
net::URLRequestStatus::FAILED, net::ERR_FAILED));
return;
}
+ if (incumbent_response_id_ != kInvalidServiceWorkerResourceId) {
+ scoped_ptr<ServiceWorkerResponseReader> incumbent_reader =
+ context_->storage()->CreateResponseReader(incumbent_response_id_);
+ consumer_.reset(new Comparer(this, incumbent_reader.Pass()));
+ } else {
+ consumer_.reset(new PassThroughConsumer(this));
+ }
+
version_->script_cache_map()->NotifyStartedCaching(
url_, response_id_);
did_notify_started_ = true;
@@ -140,20 +403,25 @@ void ServiceWorkerWriteToCacheJob::SetExtraRequestHeaders(
net_request_->SetExtraRequestHeaders(headers);
}
-bool ServiceWorkerWriteToCacheJob::ReadRawData(
- net::IOBuffer* buf,
- int buf_size,
- int *bytes_read) {
+bool ServiceWorkerWriteToCacheJob::ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) {
net::URLRequestStatus status = ReadNetData(buf, buf_size, bytes_read);
SetStatus(status);
if (status.is_io_pending())
return false;
- // No more data to process, the job is complete.
- io_buffer_ = NULL;
- version_->script_cache_map()->NotifyFinishedCaching(
- url_, writer_->amount_written(), status, std::string());
- did_notify_finished_ = true;
+ if (!status.is_success()) {
+ AsyncNotifyDoneHelper(status, kFetchScriptError);
+ return false;
+ }
+
+ DCHECK_EQ(0, *bytes_read);
+ consumer_->HandleData(buf, 0);
+ if (did_notify_finished_)
+ return GetStatus().is_success();
+ if (GetStatus().is_io_pending())
+ return false;
return status.is_success();
}
@@ -190,12 +458,12 @@ void ServiceWorkerWriteToCacheJob::StartNetRequest() {
net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData(
net::IOBuffer* buf,
int buf_size,
- int *bytes_read) {
+ int* bytes_read) {
DCHECK_GT(buf_size, 0);
DCHECK(bytes_read);
-
*bytes_read = 0;
io_buffer_ = buf;
+ io_buffer_bytes_ = 0;
int net_bytes_read = 0;
if (!net_request_->Read(buf, buf_size, &net_bytes_read)) {
if (net_request_->status().is_io_pending())
@@ -205,7 +473,7 @@ net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData(
}
if (net_bytes_read != 0) {
- WriteDataToCache(net_bytes_read);
+ HandleNetData(net_bytes_read);
DCHECK(GetStatus().is_io_pending());
return GetStatus();
}
@@ -214,7 +482,7 @@ net::URLRequestStatus ServiceWorkerWriteToCacheJob::ReadNetData(
return net_request_->status();
}
-void ServiceWorkerWriteToCacheJob::WriteHeadersToCache() {
+void ServiceWorkerWriteToCacheJob::WriteHeaders(const base::Closure& callback) {
if (!context_) {
AsyncNotifyDoneHelper(
net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED),
@@ -223,20 +491,20 @@ void ServiceWorkerWriteToCacheJob::WriteHeadersToCache() {
}
TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
"ServiceWorkerWriteToCacheJob::ExecutingJob",
- this,
- "WriteHeadersToCache");
+ this, "WriteHeaders");
writer_ = context_->storage()->CreateResponseWriter(response_id_);
- info_buffer_ = new HttpResponseInfoIOBuffer(
- new net::HttpResponseInfo(net_request_->response_info()));
+ scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
+ new HttpResponseInfoIOBuffer(
+ new net::HttpResponseInfo(net_request_->response_info()));
writer_->WriteInfo(
- info_buffer_.get(),
+ info_buffer.get(),
base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete,
- weak_factory_.GetWeakPtr()));
- SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ weak_factory_.GetWeakPtr(), callback));
}
-void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(int result) {
- SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
+void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(
+ const base::Closure& callback,
+ int result) {
if (result < 0) {
ServiceWorkerMetrics::CountWriteResponseResult(
ServiceWorkerMetrics::WRITE_HEADERS_ERROR);
@@ -245,33 +513,31 @@ void ServiceWorkerWriteToCacheJob::OnWriteHeadersComplete(int result) {
kFetchScriptError);
return;
}
- http_info_.reset(info_buffer_->http_info.release());
- info_buffer_ = NULL;
TRACE_EVENT_ASYNC_STEP_INTO0("ServiceWorker",
"ServiceWorkerWriteToCacheJob::ExecutingJob",
- this,
- "WriteHeadersToCacheCompleted");
- NotifyHeadersComplete();
+ this, "WriteHeadersCompleted");
+ callback.Run();
}
-void ServiceWorkerWriteToCacheJob::WriteDataToCache(int amount_to_write) {
- DCHECK_NE(0, amount_to_write);
- TRACE_EVENT_ASYNC_STEP_INTO1("ServiceWorker",
- "ServiceWorkerWriteToCacheJob::ExecutingJob",
- this,
- "WriteDataToCache",
- "Amount to write", amount_to_write);
- SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+void ServiceWorkerWriteToCacheJob::WriteData(
+ net::IOBuffer* buf,
+ int bytes_to_write,
+ const base::Callback<void(int result)>& callback) {
+ DCHECK_LT(0, bytes_to_write);
+ TRACE_EVENT_ASYNC_STEP_INTO1(
+ "ServiceWorker", "ServiceWorkerWriteToCacheJob::ExecutingJob", this,
+ "WriteData", "Amount to write", bytes_to_write);
+
writer_->WriteData(
- io_buffer_.get(),
- amount_to_write,
+ buf, bytes_to_write,
base::Bind(&ServiceWorkerWriteToCacheJob::OnWriteDataComplete,
- weak_factory_.GetWeakPtr()));
+ weak_factory_.GetWeakPtr(), callback));
}
-void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(int result) {
+void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(
+ const base::Callback<void(int result)>& callback,
+ int result) {
DCHECK_NE(0, result);
- io_buffer_ = NULL;
if (!context_) {
AsyncNotifyDoneHelper(
net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED),
@@ -288,11 +554,9 @@ void ServiceWorkerWriteToCacheJob::OnWriteDataComplete(int result) {
}
ServiceWorkerMetrics::CountWriteResponseResult(
ServiceWorkerMetrics::WRITE_OK);
- SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
- NotifyReadComplete(result);
+ callback.Run(result);
TRACE_EVENT_ASYNC_END0("ServiceWorker",
- "ServiceWorkerWriteToCacheJob::ExecutingJob",
- this);
+ "ServiceWorkerWriteToCacheJob::ExecutingJob", this);
}
void ServiceWorkerWriteToCacheJob::OnReceivedRedirect(
@@ -411,7 +675,17 @@ void ServiceWorkerWriteToCacheJob::OnResponseStarted(
if (net_request_->response_info().network_accessed)
version_->embedded_worker()->OnNetworkAccessedForScriptLoad();
- WriteHeadersToCache();
+ consumer_->OnResponseStarted();
+}
+
+void ServiceWorkerWriteToCacheJob::CommitHeadersAndNotifyHeadersComplete() {
+ http_info_.reset(new net::HttpResponseInfo(net_request_->response_info()));
+ NotifyHeadersComplete();
+}
+
+void ServiceWorkerWriteToCacheJob::HandleNetData(int bytes_read) {
+ io_buffer_bytes_ = bytes_read;
+ consumer_->HandleData(io_buffer_.get(), bytes_read);
}
void ServiceWorkerWriteToCacheJob::OnReadCompleted(
@@ -424,17 +698,14 @@ void ServiceWorkerWriteToCacheJob::OnReadCompleted(
return;
}
if (bytes_read > 0) {
- WriteDataToCache(bytes_read);
+ HandleNetData(bytes_read);
+ DCHECK(GetStatus().is_io_pending());
return;
}
+
// No more data to process, the job is complete.
DCHECK(request->status().is_success());
- io_buffer_ = NULL;
- version_->script_cache_map()->NotifyFinishedCaching(
- url_, writer_->amount_written(), net::URLRequestStatus(), std::string());
- did_notify_finished_ = true;
- SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
- NotifyReadComplete(0);
+ HandleNetData(0);
}
bool ServiceWorkerWriteToCacheJob::CheckPathRestriction(
@@ -456,20 +727,99 @@ bool ServiceWorkerWriteToCacheJob::CheckPathRestriction(
return true;
}
+void ServiceWorkerWriteToCacheJob::SetPendingIO() {
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+}
+
+void ServiceWorkerWriteToCacheJob::ClearPendingIO() {
+ SetStatus(net::URLRequestStatus());
+}
+
+void ServiceWorkerWriteToCacheJob::OnPassThroughComplete() {
+ NotifyFinishedCaching(net::URLRequestStatus(), std::string());
+ if (GetStatus().is_io_pending()) {
+ ClearPendingIO();
+ NotifyReadComplete(0);
+ }
+}
+
+void ServiceWorkerWriteToCacheJob::OnCompareComplete(int bytes_matched,
+ bool is_equal) {
+ if (is_equal) {
+ // This version is identical to the incumbent, so discard it and fail this
+ // job.
+ version_->SetStartWorkerStatusCode(SERVICE_WORKER_ERROR_EXISTS);
+ AsyncNotifyDoneHelper(
+ net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED),
+ kFetchScriptError);
+ return;
+ }
+
+ // We must switch to the pass through consumer. Write what is known
+ // (headers + bytes matched) to disk.
+ WriteHeaders(base::Bind(&ServiceWorkerWriteToCacheJob::CopyIncumbent,
+ weak_factory_.GetWeakPtr(), bytes_matched));
+ SetPendingIO();
+}
+
+void ServiceWorkerWriteToCacheJob::CopyIncumbent(int bytes_to_copy) {
+ if (bytes_to_copy == 0) {
+ OnCopyComplete(SERVICE_WORKER_OK);
+ return;
+ }
+ scoped_ptr<ServiceWorkerResponseReader> incumbent_reader =
+ context_->storage()->CreateResponseReader(incumbent_response_id_);
+ scoped_refptr<Copier> copier = new Copier(
+ weak_factory_.GetWeakPtr(), incumbent_reader.Pass(), bytes_to_copy,
+ base::Bind(&ServiceWorkerWriteToCacheJob::OnCopyComplete,
+ weak_factory_.GetWeakPtr()));
+ copier->Start(); // It deletes itself when done.
+ DCHECK(GetStatus().is_io_pending());
+}
+
+void ServiceWorkerWriteToCacheJob::OnCopyComplete(
+ ServiceWorkerStatusCode status) {
+ if (status != SERVICE_WORKER_OK) {
+ AsyncNotifyDoneHelper(
+ net::URLRequestStatus(net::URLRequestStatus::FAILED, net::ERR_FAILED),
+ kFetchScriptError);
+ return;
+ }
+
+ // Continue processing the net data that triggered the comparison fail and
+ // copy.
+ if (io_buffer_bytes_ > 0) {
+ consumer_.reset(new PassThroughConsumer(this));
+ consumer_->HandleData(io_buffer_.get(), io_buffer_bytes_);
+ return;
+ }
+
+ // The copy was triggered by EOF from the network, which
+ // means the job is now done.
+ NotifyFinishedCaching(net::URLRequestStatus(), std::string());
+ ClearPendingIO();
+ NotifyReadComplete(0);
+}
+
void ServiceWorkerWriteToCacheJob::AsyncNotifyDoneHelper(
const net::URLRequestStatus& status,
const std::string& status_message) {
DCHECK(!status.is_io_pending());
+ NotifyFinishedCaching(status, status_message);
+ SetStatus(status);
+ NotifyDone(status);
+}
+
+void ServiceWorkerWriteToCacheJob::NotifyFinishedCaching(
+ net::URLRequestStatus status,
+ const std::string& status_message) {
DCHECK(!did_notify_finished_);
int size = -1;
- if (writer_.get()) {
- size = writer_->amount_written();
- }
+ if (status.is_success())
+ size = writer_ ? writer_->amount_written() : 0;
version_->script_cache_map()->NotifyFinishedCaching(url_, size, status,
status_message);
did_notify_finished_ = true;
- SetStatus(status);
- NotifyDone(status);
}
} // namespace content

Powered by Google App Engine
This is Rietveld 408576698