Chromium Code Reviews| Index: net/url_request/sdch_dictionary_fetcher.cc |
| diff --git a/net/url_request/sdch_dictionary_fetcher.cc b/net/url_request/sdch_dictionary_fetcher.cc |
| index 3607bfd42a08531c55d05bc0075aa61e48261d0c..5806e659048817a446c03076cb2cca4eae38fbe0 100644 |
| --- a/net/url_request/sdch_dictionary_fetcher.cc |
| +++ b/net/url_request/sdch_dictionary_fetcher.cc |
| @@ -15,6 +15,7 @@ |
| #include "net/base/load_flags.h" |
| #include "net/base/net_log.h" |
| #include "net/base/sdch_net_log_params.h" |
| +#include "net/http/http_response_headers.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_status.h" |
| #include "net/url_request/url_request_throttler_manager.h" |
| @@ -41,66 +42,116 @@ int GetReadResult(int bytes_read, const URLRequest* request) { |
| return rv; |
| } |
| +struct FetchInfo { |
| + FetchInfo(const GURL& url, |
| + bool cache_only, |
| + const SdchDictionaryFetcher::OnDictionaryFetchedCallback& callback) |
| + : url(url), cache_only(cache_only), callback(callback) {} |
| + FetchInfo() {} |
| + GURL url; |
|
mmenke
2015/02/26 17:27:20
blank line before url.
Elly Fong-Jones
2015/03/03 21:37:46
Done.
|
| + bool cache_only; |
| + SdchDictionaryFetcher::OnDictionaryFetchedCallback callback; |
| +}; |
| + |
| +// A UniqueFetchQueue is used to queue outgoing requests, which are either cache |
| +// requests or network requests (which *may* still be served from cache). |
| +// The UniqueFetchQueue enforces that a dictionary can be loaded-from-cache and |
| +// loaded-from-network each at most once, and that if a dictionary has been |
| +// loaded-from-network, it cannot subsequently be loaded-from-cache. Practically |
| +// this means there are three valid patterns of requests for a URL u: |
| +// 1. load-from-cache(u) |
| +// 2. load-from-network(u) |
| +// 3. load-from-cache(u); load-from-network(u) |
| +// Any fetches that would violate these invariants are rejected by Push(). |
| +class UniqueFetchQueue { |
| + public: |
| + UniqueFetchQueue(); |
| + ~UniqueFetchQueue(); |
| + |
| + bool Push(const FetchInfo& info); |
| + bool Pop(FetchInfo* info); |
| + bool IsEmpty() const; |
| + void ForgetPriorFetches(); |
|
Randy Smith (Not in Mondays)
2015/02/23 21:23:49
nit, suggestion: Name change to Clear()?
Elly Fong-Jones
2015/03/03 21:37:45
Done.
|
| + private: |
|
mmenke
2015/02/26 17:27:20
nit: Blank line before private.
Elly Fong-Jones
2015/03/03 21:37:45
Done.
|
| + std::list<FetchInfo> queue_; |
|
mmenke
2015/02/26 17:27:20
Any reason this is a list and not a queue? Also,
Elly Fong-Jones
2015/03/03 21:37:45
Oops, it was meant to be a queue.
|
| + std::set<GURL> ever_cache_fetched_; |
|
mmenke
2015/02/26 17:27:20
Do we even need this? We load a dictionary of URL
Elly Fong-Jones
2015/03/03 21:37:45
Done.
|
| + std::set<GURL> ever_fetched_; |
|
Randy Smith (Not in Mondays)
2015/02/23 21:23:49
nit, suggestion: This is a bit wasteful of space;
mmenke
2015/02/26 17:27:20
+1.
mmenke
2015/02/26 17:27:20
The fact that something that was just fetched from
mmenke
2015/02/26 17:27:20
DISALLOW_COPY_AND_ASSIGN
Elly Fong-Jones
2015/03/03 21:37:45
Done.
Elly Fong-Jones
2015/03/03 21:37:46
Done.
Elly Fong-Jones
2015/03/03 21:37:48
Done.
Elly Fong-Jones
2015/03/03 21:37:48
Done.
|
| +}; |
| + |
| +UniqueFetchQueue::UniqueFetchQueue() {} |
| +UniqueFetchQueue::~UniqueFetchQueue() {} |
| + |
| +bool UniqueFetchQueue::Push(const FetchInfo& info) { |
| + if (info.cache_only) { |
| + if (ever_cache_fetched_.count(info.url) != 0 || |
| + ever_fetched_.count(info.url) != 0) { |
| + return false; |
| + } |
| + ever_cache_fetched_.insert(info.url); |
| + } else { |
| + if (ever_fetched_.count(info.url) != 0) { |
|
mmenke
2015/02/26 17:27:20
optional: Think it's a bit clearer if we do this
Elly Fong-Jones
2015/03/03 21:37:46
Done.
|
| + return false; |
| + } |
|
mmenke
2015/02/26 17:27:20
nit: Don't use braces with single-line if's.
Elly Fong-Jones
2015/03/03 21:37:45
Done.
|
| + ever_fetched_.insert(info.url); |
| + } |
|
Randy Smith (Not in Mondays)
2015/02/23 21:23:49
suggestion: I'd find this code easier to read if i
Elly Fong-Jones
2015/03/03 21:37:46
Done.
|
| + queue_.push_back(info); |
| + return true; |
| +} |
| + |
| +bool UniqueFetchQueue::Pop(FetchInfo* info) { |
| + if (IsEmpty()) |
| + return false; |
| + *info = queue_.front(); |
| + queue_.pop_front(); |
| + return true; |
| +} |
| + |
| +bool UniqueFetchQueue::IsEmpty() const { |
| + return queue_.empty(); |
| +} |
| + |
| +void UniqueFetchQueue::ForgetPriorFetches() { |
| + ever_cache_fetched_.clear(); |
| + ever_fetched_.clear(); |
| +} |
| + |
| } // namespace |
| -SdchDictionaryFetcher::SdchDictionaryFetcher( |
| - URLRequestContext* context, |
| - const OnDictionaryFetchedCallback& callback) |
| +SdchDictionaryFetcher::SdchDictionaryFetcher(URLRequestContext* context) |
| : next_state_(STATE_NONE), |
| in_loop_(false), |
| + fetch_queue_(new UniqueFetchQueue()), |
| context_(context), |
| - dictionary_fetched_callback_(callback), |
| weak_factory_(this) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(context); |
| } |
| SdchDictionaryFetcher::~SdchDictionaryFetcher() { |
| - DCHECK(CalledOnValidThread()); |
| } |
| -void SdchDictionaryFetcher::Schedule(const GURL& dictionary_url) { |
| - DCHECK(CalledOnValidThread()); |
| - |
| - // Avoid pushing duplicate copy onto queue. We may fetch this url again later |
| - // and get a different dictionary, but there is no reason to have it in the |
| - // queue twice at one time. |
| - if ((!fetch_queue_.empty() && fetch_queue_.back() == dictionary_url) || |
| - attempted_load_.find(dictionary_url) != attempted_load_.end()) { |
| - // TODO(rdsmith): log this error to the net log of the URLRequest |
| - // initiating this fetch, once URLRequest will be passed here. |
| - SdchManager::SdchErrorRecovery( |
| - SDCH_DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD); |
| - return; |
| - } |
| - |
| - attempted_load_.insert(dictionary_url); |
| - fetch_queue_.push(dictionary_url); |
| - |
| - // If the loop is already processing, it'll pick up the above in the |
| - // normal course of events. |
| - if (next_state_ != STATE_NONE) |
| - return; |
| - |
| - next_state_ = STATE_SEND_REQUEST; |
| +bool SdchDictionaryFetcher::Schedule( |
| + const GURL& dictionary_url, |
| + const OnDictionaryFetchedCallback& callback) { |
| + return ScheduleInternal(dictionary_url, false, callback); |
| +} |
| - // There are no callbacks to user code from the dictionary fetcher, |
| - // and Schedule() is only called from user code, so this call to DoLoop() |
| - // does not require an |if (in_loop_) return;| guard. |
| - DoLoop(OK); |
| +bool SdchDictionaryFetcher::ScheduleReload( |
| + const GURL& dictionary_url, |
| + const OnDictionaryFetchedCallback& callback) { |
| + return ScheduleInternal(dictionary_url, true, callback); |
| } |
| void SdchDictionaryFetcher::Cancel() { |
| DCHECK(CalledOnValidThread()); |
| next_state_ = STATE_NONE; |
| + current_request_.reset(); |
| + buffer_ = nullptr; |
| + current_callback_.Reset(); |
|
mmenke
2015/02/26 17:27:20
This looks a lot like ResetRequest...In fact, we'r
Elly Fong-Jones
2015/03/03 21:37:48
Done.
|
| - while (!fetch_queue_.empty()) |
| - fetch_queue_.pop(); |
| - attempted_load_.clear(); |
| + fetch_queue_->ForgetPriorFetches(); |
| weak_factory_.InvalidateWeakPtrs(); |
| - current_request_.reset(NULL); |
| - buffer_ = NULL; |
| dictionary_.clear(); |
| } |
| @@ -115,7 +166,23 @@ void SdchDictionaryFetcher::OnResponseStarted(URLRequest* request) { |
| DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE); |
| DCHECK(!in_loop_); |
| - DoLoop(request->status().error()); |
| + // Confirm that the response isn't a stale read from the cache (as |
| + // may happen in the reload case). If the response was not retrieved over |
| + // HTTP, it is presumed to be fresh. |
| + HttpResponseHeaders* response_headers = request->response_headers(); |
| + int result = request->status().error(); |
| + if (result == OK && response_headers) { |
| + ValidationType validation_type = response_headers->RequiresValidation( |
| + request->response_info().request_time, |
| + request->response_info().response_time, base::Time::Now()); |
| + // TODO(rdsmith): Maybe handle VALIDATION_ASYNCHRONOUS by queueing |
| + // a non-reload request for the dictionary. |
| + if (validation_type != VALIDATION_NONE) { |
| + result = ERR_FAILED; |
| + } |
|
mmenke
2015/02/26 17:27:20
nit: Don't use braces on 2 line ifs.
Elly Fong-Jones
2015/03/03 21:37:45
Done.
|
| + } |
| + |
| + DoLoop(result); |
| } |
| void SdchDictionaryFetcher::OnReadCompleted(URLRequest* request, |
| @@ -133,6 +200,47 @@ void SdchDictionaryFetcher::OnReadCompleted(URLRequest* request, |
| DoLoop(GetReadResult(bytes_read, current_request_.get())); |
| } |
| +bool SdchDictionaryFetcher::ScheduleInternal( |
| + const GURL& dictionary_url, |
| + bool reload, |
| + const OnDictionaryFetchedCallback& callback) { |
| + DCHECK(CalledOnValidThread()); |
| + |
| + // If Push() fails, |dictionary_url| has already been fetched or scheduled to |
| + // be fetched. |
| + if (!fetch_queue_->Push(FetchInfo(dictionary_url, reload, callback))) { |
| + // TODO(rdsmith): Log this error to the net log. In the case of a |
| + // normal fetch, this can be through the URLRequest |
| + // initiating this fetch (once the URLRequest is passed to the fetcher); |
| + // in the case of a reload, it's more complicated. |
| + SdchManager::SdchErrorRecovery( |
| + SDCH_DICTIONARY_PREVIOUSLY_SCHEDULED_TO_DOWNLOAD); |
| + return false; |
| + } |
| + |
| + // If the loop is already processing, it'll pick up the above in the |
| + // normal course of events. |
| + if (next_state_ != STATE_NONE) |
| + return true; |
| + |
| + next_state_ = STATE_SEND_REQUEST; |
| + |
| + // There are no callbacks to user code from the dictionary fetcher, |
| + // and Schedule() is only called from user code, so this call to DoLoop() |
| + // does not require an |if (in_loop_) return;| guard. |
| + DoLoop(OK); |
| + return true; |
| +} |
| + |
| +void SdchDictionaryFetcher::ResetRequest() { |
| + current_request_.reset(); |
| + buffer_ = nullptr; |
| + current_callback_.Reset(); |
| + next_state_ = STATE_SEND_REQUEST; |
| + dictionary_.clear(); |
| + return; |
| +} |
| + |
| int SdchDictionaryFetcher::DoLoop(int rv) { |
| DCHECK(!in_loop_); |
| base::AutoReset<bool> auto_reset_in_loop(&in_loop_, true); |
| @@ -170,19 +278,24 @@ int SdchDictionaryFetcher::DoSendRequest(int rv) { |
| // |rv| is ignored, as the result from the previous request doesn't |
| // affect the next request. |
| - if (fetch_queue_.empty() || current_request_.get()) { |
| + if (fetch_queue_->IsEmpty() || current_request_.get()) { |
| next_state_ = STATE_NONE; |
| return OK; |
| } |
| next_state_ = STATE_SEND_REQUEST_COMPLETE; |
| + FetchInfo info; |
| + fetch_queue_->Pop(&info); |
|
Randy Smith (Not in Mondays)
2015/02/23 21:23:49
DCHECK that return value is true?
Elly Fong-Jones
2015/03/03 21:37:46
Done.
|
| current_request_ = |
| - context_->CreateRequest(fetch_queue_.front(), IDLE, this, NULL); |
| - current_request_->SetLoadFlags(LOAD_DO_NOT_SEND_COOKIES | |
| - LOAD_DO_NOT_SAVE_COOKIES); |
| + context_->CreateRequest(info.url, IDLE, this, NULL); |
| + int load_flags = LOAD_DO_NOT_SEND_COOKIES | LOAD_DO_NOT_SAVE_COOKIES; |
| + if (info.cache_only) |
| + load_flags |= LOAD_ONLY_FROM_CACHE; |
| + current_request_->SetLoadFlags(load_flags); |
| + |
| buffer_ = new IOBuffer(kBufferSize); |
| - fetch_queue_.pop(); |
| + current_callback_ = info.callback; |
| current_request_->Start(); |
| current_request_->net_log().AddEvent(NetLog::TYPE_SDCH_DICTIONARY_FETCH); |
| @@ -211,10 +324,7 @@ int SdchDictionaryFetcher::DoReadBody(int rv) { |
| // If there's been an error, abort the current request. |
| if (rv != OK) { |
| - current_request_.reset(); |
| - buffer_ = NULL; |
| - next_state_ = STATE_SEND_REQUEST; |
| - |
| + ResetRequest(); |
| return OK; |
| } |
| @@ -255,18 +365,13 @@ int SdchDictionaryFetcher::DoReadBodyComplete(int rv) { |
| int SdchDictionaryFetcher::DoCompleteRequest(int rv) { |
| DCHECK(CalledOnValidThread()); |
| - // DoReadBodyComplete() only transitions to this state |
| - // on success. |
| - DCHECK_EQ(OK, rv); |
| - |
| - dictionary_fetched_callback_.Run(dictionary_, current_request_->url(), |
| - current_request_->net_log()); |
| - current_request_.reset(); |
| - buffer_ = NULL; |
| - dictionary_.clear(); |
| - |
| - next_state_ = STATE_SEND_REQUEST; |
| + // If the dictionary was successfully fetched, add it to the manager. |
| + if (rv == OK) { |
| + current_callback_.Run(dictionary_, current_request_->url(), |
| + current_request_->net_log()); |
| + } |
| + ResetRequest(); |
| return OK; |
| } |