diff options
author | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-08 17:46:21 +0000 |
---|---|---|
committer | jennb@chromium.org <jennb@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-08 17:46:21 +0000 |
commit | b75a52794712b69a59960901f4761aef748e38bf (patch) | |
tree | 3dd9dad03dba7b0dc8e60cdfd309dcd8f0d009d8 /webkit/appcache | |
parent | d0767cb54b2b5ee4d9cf00b3ee0fa585826b4036 (diff) | |
download | chromium_src-b75a52794712b69a59960901f4761aef748e38bf.zip chromium_src-b75a52794712b69a59960901f4761aef748e38bf.tar.gz chromium_src-b75a52794712b69a59960901f4761aef748e38bf.tar.bz2 |
Make appcache update job retry 503 requests with retry-after of 0.
TEST=test retry logic
BUG=none
Review URL: http://codereview.chromium.org/264002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@28406 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/appcache')
-rw-r--r-- | webkit/appcache/appcache_update_job.cc | 67 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job.h | 4 | ||||
-rw-r--r-- | webkit/appcache/appcache_update_job_unittest.cc | 302 |
3 files changed, 353 insertions, 20 deletions
diff --git a/webkit/appcache/appcache_update_job.cc b/webkit/appcache/appcache_update_job.cc index 146c8f2..0834039 100644 --- a/webkit/appcache/appcache_update_job.cc +++ b/webkit/appcache/appcache_update_job.cc @@ -6,6 +6,7 @@ #include "base/compiler_specific.h" #include "base/message_loop.h" +#include "base/string_util.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "webkit/appcache/appcache_group.h" @@ -15,6 +16,7 @@ namespace appcache { static const int kBufferSize = 4096; static const size_t kMaxConcurrentUrlFetches = 2; +static const int kMax503Retries = 3; // Extra info associated with requests for use during response processing. // This info is deleted when the URLRequest is deleted. @@ -27,12 +29,15 @@ struct UpdateJobInfo : public URLRequest::UserData { explicit UpdateJobInfo(RequestType request_type) : type(request_type), - buffer(new net::IOBuffer(kBufferSize)) { + buffer(new net::IOBuffer(kBufferSize)), + retry_503_attempts(0) { } RequestType type; scoped_refptr<net::IOBuffer> buffer; // TODO(jennb): need storage info to stream response data to storage + + int retry_503_attempts; }; // Helper class for collecting hosts per frontend when sending notifications @@ -219,7 +224,13 @@ void AppCacheUpdateJob::OnReceivedRedirect(URLRequest* request, } void AppCacheUpdateJob::OnResponseCompleted(URLRequest* request) { - // TODO(jennb): think about retrying for 503s where retry-after is 0 + // Retry for 503s where retry-after is 0. + if (request->status().is_success() && + request->GetResponseCode() == 503 && + RetryRequest(request)) { + return; + } + UpdateJobInfo* info = static_cast<UpdateJobInfo*>(request->GetUserData(this)); switch (info->type) { @@ -239,12 +250,50 @@ void AppCacheUpdateJob::OnResponseCompleted(URLRequest* request) { delete request; } +bool AppCacheUpdateJob::RetryRequest(URLRequest* request) { + UpdateJobInfo* info = + static_cast<UpdateJobInfo*>(request->GetUserData(this)); + if (info->retry_503_attempts >= kMax503Retries) { + return false; + } + + if (!request->response_headers()->HasHeaderValue("retry-after", "0")) + return false; + + const GURL& url = request->original_url(); + URLRequest* retry = new URLRequest(url, this); + UpdateJobInfo* retry_info = new UpdateJobInfo(info->type); + retry_info->retry_503_attempts = info->retry_503_attempts + 1; + retry->SetUserData(this, retry_info); + retry->set_context(request->context()); + retry->set_load_flags(request->load_flags()); + + switch (info->type) { + case UpdateJobInfo::MANIFEST_FETCH: + case UpdateJobInfo::MANIFEST_REFETCH: + manifest_url_request_ = retry; + manifest_data_.clear(); + break; + case UpdateJobInfo::URL_FETCH: + pending_url_fetches_.erase(url); + pending_url_fetches_.insert(PendingUrlFetches::value_type(url, retry)); + break; + default: + NOTREACHED(); + } + + retry->Start(); + + delete request; + return true; +} + void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) { DCHECK(internal_state_ == FETCH_MANIFEST); manifest_url_request_ = NULL; if (!request->status().is_success()) { - LOG(ERROR) << "Request non-success, status: " << request->status().status() + LOG(INFO) << "Request non-success, status: " << request->status().status() << " os_error: " << request->status().os_error(); internal_state_ = CACHE_FAILURE; MaybeCompleteUpdate(); // if not done, run async cache failure steps @@ -255,7 +304,7 @@ void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) { std::string mime_type; request->GetMimeType(&mime_type); - if (response_code == 200 && mime_type == kManifestMimeType) { + if ((response_code / 100 == 2) && mime_type == kManifestMimeType) { if (update_type_ == UPGRADE_ATTEMPT) CheckIfManifestChanged(); // continues asynchronously else @@ -268,7 +317,7 @@ void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) { NotifyAllPendingMasterHosts(ERROR_EVENT); DeleteSoon(); } else { - LOG(ERROR) << "Cache failure, response code: " << response_code; + LOG(INFO) << "Cache failure, response code: " << response_code; internal_state_ = CACHE_FAILURE; MaybeCompleteUpdate(); // if not done, run async cache failure steps } @@ -287,7 +336,7 @@ void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { Manifest manifest; if (!ParseManifest(manifest_url_, manifest_data_.data(), manifest_data_.length(), manifest)) { - LOG(ERROR) << "Failed to parse manifest: " << manifest_url_; + LOG(INFO) << "Failed to parse manifest: " << manifest_url_; internal_state_ = CACHE_FAILURE; MaybeCompleteUpdate(); // if not done, run async cache failure steps return; @@ -327,7 +376,7 @@ void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) { int response_code = request->GetResponseCode(); AppCacheEntry& entry = url_file_list_.find(url)->second; - if (request->status().is_success() && response_code == 200) { + if (request->status().is_success() && (response_code / 100 == 2)) { // TODO(jennb): associate storage with the new entry inprogress_cache_->AddEntry(url, entry); @@ -337,7 +386,7 @@ void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) { // whose value doesn't match the manifest url of the application cache // being processed, mark the entry as being foreign. } else { - LOG(ERROR) << "Request status: " << request->status().status() + LOG(INFO) << "Request status: " << request->status().status() << " os_error: " << request->status().os_error() << " response code: " << response_code; @@ -403,7 +452,7 @@ void AppCacheUpdateJob::HandleManifestRefetchCompleted(URLRequest* request) { DeleteSoon(); // TODO(jennb): end of part that needs to be made async. } else { - LOG(ERROR) << "Request status: " << request->status().status() + LOG(INFO) << "Request status: " << request->status().status() << " os_error: " << request->status().os_error() << " response code: " << response_code; ScheduleUpdateRetry(kRerunDelayMs); diff --git a/webkit/appcache/appcache_update_job.h b/webkit/appcache/appcache_update_job.h index 160e27e..0c19196 100644 --- a/webkit/appcache/appcache_update_job.h +++ b/webkit/appcache/appcache_update_job.h @@ -74,6 +74,10 @@ class AppCacheUpdateJob : public URLRequest::Delegate { void OnResponseCompleted(URLRequest* request); + // Retries a 503 request with retry-after header of 0. + // Returns true if request should be retried and deletes original request. + bool RetryRequest(URLRequest* request); + void ReadResponseData(URLRequest* request); // Returns false if response data is processed asynchronously, in which diff --git a/webkit/appcache/appcache_update_job_unittest.cc b/webkit/appcache/appcache_update_job_unittest.cc index f08625d..00fc819 100644 --- a/webkit/appcache/appcache_update_job_unittest.cc +++ b/webkit/appcache/appcache_update_job_unittest.cc @@ -75,6 +75,107 @@ static URLRequestJob* RedirectFactory(URLRequest* request, true); } +// Helper class to simulate a URL that returns retry or success. +class RetryRequestTestJob : public URLRequestTestJob { + public: + enum RetryHeader { + NO_RETRY_AFTER, + NONZERO_RETRY_AFTER, + RETRY_AFTER_0, + }; + + static const GURL kRetryUrl; + + // Call this at the start of each retry test. + static void Initialize(int num_retry_responses, RetryHeader header, + int expected_requests) { + num_requests_ = 0; + num_retries_ = num_retry_responses; + retry_after_ = header; + expected_requests_ = expected_requests; + } + + // Verifies results at end of test and resets counters. + static void Verify() { + EXPECT_EQ(expected_requests_, num_requests_); + num_requests_ = 0; + expected_requests_ = 0; + } + + static URLRequestJob* RetryFactory(URLRequest* request, + const std::string& scheme) { + ++num_requests_; + if (num_retries_ > 0 && request->original_url() == kRetryUrl) { + --num_retries_; + return new RetryRequestTestJob( + request, RetryRequestTestJob::retry_headers(), 503); + } else { + return new RetryRequestTestJob( + request, RetryRequestTestJob::manifest_headers(), 200); + } + } + + virtual int GetResponseCode() const { return response_code_; } + + private: + static std::string retry_headers() { + const char no_retry_after[] = + "HTTP/1.1 503 BOO HOO\0" + "\0"; + const char nonzero[] = + "HTTP/1.1 503 BOO HOO\0" + "Retry-After: 60\0" + "\0"; + const char retry_after_0[] = + "HTTP/1.1 503 BOO HOO\0" + "Retry-After: 0\0" + "\0"; + + switch (retry_after_) { + case NO_RETRY_AFTER: + return std::string(no_retry_after, arraysize(no_retry_after)); + case NONZERO_RETRY_AFTER: + return std::string(nonzero, arraysize(nonzero)); + case RETRY_AFTER_0: + default: + return std::string(retry_after_0, arraysize(retry_after_0)); + } + } + + static std::string manifest_headers() { + const char headers[] = + "HTTP/1.1 200 OK\0" + "Content-type: text/cache-manifest\0" + "\0"; + return std::string(headers, arraysize(headers)); + } + + static std::string data() { + return std::string("CACHE MANIFEST\r" + "http://retry\r"); // must be same as kRetryUrl + } + + explicit RetryRequestTestJob(URLRequest* request, const std::string& headers, + int response_code) + : URLRequestTestJob(request, headers, data(), true), + response_code_(response_code) { + } + + int response_code_; + + static int num_requests_; + static int num_retries_; + static RetryHeader retry_after_; + static int expected_requests_; +}; + +// static +const GURL RetryRequestTestJob::kRetryUrl("http://retry"); +int RetryRequestTestJob::num_requests_ = 0; +int RetryRequestTestJob::num_retries_; +RetryRequestTestJob::RetryHeader RetryRequestTestJob::retry_after_; +int RetryRequestTestJob::expected_requests_ = 0; + class AppCacheUpdateJobTest : public testing::Test, public AppCacheGroup::Observer { public: @@ -85,11 +186,13 @@ class AppCacheUpdateJobTest : public testing::Test, expect_group_has_cache_(false), expect_old_cache_(NULL), expect_newest_cache_(NULL), - tested_manifest_(NONE) { + tested_manifest_(NONE), + registered_factory_(false), + old_factory_(NULL) { } static void SetUpTestCase() { - io_thread_.reset(new base::Thread("AppCacheUpdateJob IO test thread")); + io_thread_.reset(new base::Thread("AppCacheUpdateJob IO test thread")); base::Thread::Options options(MessageLoop::TYPE_IO, 0); io_thread_->StartWithOptions(options); @@ -276,7 +379,9 @@ class AppCacheUpdateJobTest : public testing::Test, void ManifestRedirectTest() { ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); - URLRequest::RegisterProtocolFactory("http", RedirectFactory); + old_factory_ = + URLRequest::RegisterProtocolFactory("http", RedirectFactory); + registered_factory_ = true; MakeService(); group_ = new AppCacheGroup(service_.get(), GURL("http://testme")); @@ -362,7 +467,7 @@ class AppCacheUpdateJobTest : public testing::Test, MakeService(); group_ = new AppCacheGroup( - service_.get(), http_server_->TestServerPage("files/gone")); + service_.get(), http_server_->TestServerPage("files/gone")); AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); group_->update_job_ = update; @@ -386,7 +491,7 @@ class AppCacheUpdateJobTest : public testing::Test, MakeService(); group_ = new AppCacheGroup( - service_.get(), http_server_->TestServerPage("files/notmodified")); + service_.get(), http_server_->TestServerPage("files/notmodified")); AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); group_->update_job_ = update; @@ -483,7 +588,7 @@ class AppCacheUpdateJobTest : public testing::Test, MakeService(); group_ = new AppCacheGroup( - service_.get(), http_server_->TestServerPage("files/manifest1")); + service_.get(), http_server_->TestServerPage("files/manifest1")); AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); group_->update_job_ = update; @@ -604,8 +709,8 @@ class AppCacheUpdateJobTest : public testing::Test, ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); MakeService(); - group_ = new AppCacheGroup( - service_.get(), http_server_->TestServerPage("files/manifest-with-404")); + group_ = new AppCacheGroup(service_.get(), + http_server_->TestServerPage("files/manifest-with-404")); AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); group_->update_job_ = update; @@ -628,8 +733,8 @@ class AppCacheUpdateJobTest : public testing::Test, ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); MakeService(); - group_ = new AppCacheGroup( - service_.get(), http_server_->TestServerPage("files/manifest-fb-404")); + group_ = new AppCacheGroup(service_.get(), + http_server_->TestServerPage("files/manifest-fb-404")); AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); group_->update_job_ = update; @@ -785,6 +890,155 @@ class AppCacheUpdateJobTest : public testing::Test, WaitForUpdateToFinish(); } + void RetryRequestTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + // Set some large number of times to return retry. + // Expect 1 manifest fetch and 3 retries. + RetryRequestTestJob::Initialize(5, RetryRequestTestJob::RETRY_AFTER_0, 4); + old_factory_ = URLRequest::RegisterProtocolFactory( + "http", RetryRequestTestJob::RetryFactory); + registered_factory_ = true; + + MakeService(); + group_ = new AppCacheGroup(service_.get(), RetryRequestTestJob::kRetryUrl); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void RetryNoRetryAfterTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + // Set some large number of times to return retry. + // Expect 1 manifest fetch and 0 retries. + RetryRequestTestJob::Initialize(5, RetryRequestTestJob::NO_RETRY_AFTER, 1); + old_factory_ = URLRequest::RegisterProtocolFactory( + "http", RetryRequestTestJob::RetryFactory); + registered_factory_ = true; + + MakeService(); + group_ = new AppCacheGroup(service_.get(), RetryRequestTestJob::kRetryUrl); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void RetryNonzeroRetryAfterTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + // Set some large number of times to return retry. + // Expect 1 request and 0 retry attempts. + RetryRequestTestJob::Initialize( + 5, RetryRequestTestJob::NONZERO_RETRY_AFTER, 1); + old_factory_ = URLRequest::RegisterProtocolFactory( + "http", RetryRequestTestJob::RetryFactory); + registered_factory_ = true; + + MakeService(); + group_ = new AppCacheGroup(service_.get(), RetryRequestTestJob::kRetryUrl); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = false; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void RetrySuccessTest() { + ASSERT_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()); + + // Set 2 as the retry limit (does not exceed the max). + // Expect 1 manifest fetch, 2 retries, 1 url fetch, 1 manifest refetch. + RetryRequestTestJob::Initialize(2, RetryRequestTestJob::RETRY_AFTER_0, 5); + old_factory_ = URLRequest::RegisterProtocolFactory( + "http", RetryRequestTestJob::RetryFactory); + registered_factory_ = true; + + MakeService(); + group_ = new AppCacheGroup(service_.get(), RetryRequestTestJob::kRetryUrl); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + + void RetryUrlTest() { + // Set 1 as the retry limit (does not exceed the max). + // Expect 1 manifest fetch, 1 url fetch, 1 url retry, 1 manifest refetch. + RetryRequestTestJob::Initialize(1, RetryRequestTestJob::RETRY_AFTER_0, 4); + old_factory_ = URLRequest::RegisterProtocolFactory( + "http", RetryRequestTestJob::RetryFactory); + registered_factory_ = true; + + MakeService(); + group_ = new AppCacheGroup(service_.get(), GURL("http://retryurl")); + AppCacheUpdateJob* update = new AppCacheUpdateJob(service_.get(), group_); + group_->update_job_ = update; + + MockFrontend* frontend = MakeMockFrontend(); + AppCacheHost* host = MakeHost(1, frontend); + update->StartUpdate(host, GURL::EmptyGURL()); + EXPECT_TRUE(update->manifest_url_request_ != NULL); + + // Set up checks for when update job finishes. + do_checks_after_update_finished_ = true; + expect_group_obsolete_ = false; + expect_group_has_cache_ = true; + frontend->AddExpectedEvent(MockFrontend::HostIds(1, host->host_id()), + CHECKING_EVENT); + + WaitForUpdateToFinish(); + } + void WaitForUpdateToFinish() { if (group_->update_status() == AppCacheGroup::IDLE) UpdateFinished(); @@ -812,7 +1066,8 @@ class AppCacheUpdateJobTest : public testing::Test, STLDeleteContainerPointers(hosts_.begin(), hosts_.end()); STLDeleteContainerPointers(frontends_.begin(), frontends_.end()); service_.reset(NULL); - URLRequest::RegisterProtocolFactory("http", NULL); + if (registered_factory_) + URLRequest::RegisterProtocolFactory("http", old_factory_); event_->Signal(); } @@ -847,6 +1102,8 @@ class AppCacheUpdateJobTest : public testing::Test, // Verifies conditions about the group and notifications after an update // has finished. Cannot verify update job internals as update is deleted. void VerifyExpectations() { + RetryRequestTestJob::Verify(); + EXPECT_EQ(expect_group_obsolete_, group_->is_obsolete()); if (expect_group_has_cache_) { @@ -1034,6 +1291,9 @@ class AppCacheUpdateJobTest : public testing::Test, std::vector<MockFrontend*> frontends_; // to check expected events TestedManifest tested_manifest_; AppCache::EntryMap expect_extra_entries_; + + bool registered_factory_; + URLRequest::ProtocolFactory* old_factory_; }; // static @@ -1172,4 +1432,24 @@ TEST_F(AppCacheUpdateJobTest, EmptyManifest) { RunTestOnIOThread(&AppCacheUpdateJobTest::EmptyManifestTest); } +TEST_F(AppCacheUpdateJobTest, RetryRequest) { + RunTestOnIOThread(&AppCacheUpdateJobTest::RetryRequestTest); +} + +TEST_F(AppCacheUpdateJobTest, RetryNoRetryAfter) { + RunTestOnIOThread(&AppCacheUpdateJobTest::RetryNoRetryAfterTest); +} + +TEST_F(AppCacheUpdateJobTest, RetryNonzeroRetryAfter) { + RunTestOnIOThread(&AppCacheUpdateJobTest::RetryNonzeroRetryAfterTest); +} + +TEST_F(AppCacheUpdateJobTest, RetrySuccess) { + RunTestOnIOThread(&AppCacheUpdateJobTest::RetrySuccessTest); +} + +TEST_F(AppCacheUpdateJobTest, RetryUrl) { + RunTestOnIOThread(&AppCacheUpdateJobTest::RetryUrlTest); +} + } // namespace appcache |