OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2017 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "net/http/http_cache_writers.h" | |
6 | |
7 #include <algorithm> | |
8 #include <utility> | |
9 | |
10 #include "base/logging.h" | |
11 | |
12 #include "net/base/net_errors.h" | |
13 #include "net/disk_cache/disk_cache.h" | |
14 #include "net/http/http_cache_transaction.h" | |
15 | |
16 namespace net { | |
17 | |
18 HttpCache::Writers::Writers(disk_cache::Entry* disk_entry) | |
19 : disk_entry_(disk_entry), weak_factory_(this) {} | |
20 | |
21 HttpCache::Writers::~Writers() {} | |
22 | |
23 int HttpCache::Writers::Read(scoped_refptr<IOBuffer> buf, | |
24 int buf_len, | |
25 const CompletionCallback& callback, | |
26 Transaction* transaction) { | |
27 DCHECK(buf); | |
28 DCHECK_GT(buf_len, 0); | |
29 DCHECK(!callback.is_null()); | |
30 DCHECK(transaction); | |
31 | |
32 // If another transaction invoked a Read which is currently ongoing, then | |
33 // this transaction waits for the read to complete and gets its buffer filled | |
34 // with the data returned from that read. | |
35 if (next_state_ != State::NONE) { | |
36 WaitingForRead waiting_transaction(transaction, buf, buf_len, callback); | |
37 waiting_for_read_.push_back(waiting_transaction); | |
38 return ERR_IO_PENDING; | |
39 } | |
40 | |
41 DCHECK_EQ(next_state_, State::NONE); | |
42 DCHECK(callback_.is_null()); | |
43 DCHECK_EQ(nullptr, active_transaction_); | |
44 DCHECK(HasTransaction(transaction)); | |
45 active_transaction_ = transaction; | |
46 | |
47 read_buf_ = std::move(buf); | |
48 io_buf_len_ = buf_len; | |
49 next_state_ = State::NETWORK_READ; | |
50 | |
51 int rv = DoLoop(OK); | |
52 if (rv == ERR_IO_PENDING) | |
53 callback_ = callback; | |
54 | |
55 return rv; | |
56 } | |
57 | |
58 bool HttpCache::Writers::StopCaching(Transaction* transaction) { | |
59 // If this is the only transaction in Writers, then stopping will be | |
60 // successful. If not, then we will not stop caching since there are | |
61 // other consumers waiting to read from the cache. | |
62 if (all_writers_.size() == 1) { | |
63 DCHECK(all_writers_.count(transaction)); | |
64 network_read_only_ = true; | |
65 return true; | |
66 } | |
67 return false; | |
68 } | |
69 | |
70 void HttpCache::Writers::AddTransaction( | |
71 Transaction* transaction, | |
72 std::unique_ptr<HttpTransaction> network_transaction, | |
73 bool is_exclusive) { | |
74 DCHECK(transaction); | |
75 DCHECK(CanAddWriters()); | |
76 DCHECK(network_transaction_ || network_transaction); | |
77 | |
78 std::pair<TransactionSet::iterator, bool> return_val = | |
79 all_writers_.insert(transaction); | |
80 DCHECK_EQ(return_val.second, true); | |
81 | |
82 if (is_exclusive) { | |
83 DCHECK_EQ(1u, all_writers_.size()); | |
84 is_exclusive_ = true; | |
85 } | |
86 | |
87 if (network_transaction) { | |
88 DCHECK(!network_transaction_); | |
89 network_transaction_ = std::move(network_transaction); | |
90 } | |
91 | |
92 priority_ = std::max(transaction->priority(), priority_); | |
93 network_transaction_->SetPriority(priority_); | |
94 } | |
95 | |
96 void HttpCache::Writers::RemoveTransaction(Transaction* transaction) { | |
97 if (!transaction) | |
98 return; | |
99 | |
100 // The transaction should be part of all_writers. | |
101 auto it = all_writers_.find(transaction); | |
102 DCHECK(it != all_writers_.end()); | |
103 all_writers_.erase(it); | |
104 | |
105 if (all_writers_.empty() && next_state_ == State::NONE) | |
106 ResetStateForEmptyWriters(); | |
107 else | |
108 UpdatePriority(); | |
109 | |
110 if (active_transaction_ == transaction) { | |
111 active_transaction_ = nullptr; | |
112 callback_.Reset(); | |
113 return; | |
114 } | |
115 | |
116 auto waiting_it = waiting_for_read_.begin(); | |
117 for (; waiting_it != waiting_for_read_.end(); waiting_it++) { | |
118 if (transaction == waiting_it->transaction) { | |
119 waiting_for_read_.erase(waiting_it); | |
120 // If a waiting transaction existed, there should have been an | |
121 // active_transaction_. | |
122 DCHECK(active_transaction_); | |
123 return; | |
124 } | |
125 } | |
126 } | |
127 | |
128 void HttpCache::Writers::UpdatePriority() { | |
129 // Get the current highest priority. | |
130 RequestPriority current_highest = MINIMUM_PRIORITY; | |
131 for (auto* transaction : all_writers_) | |
132 current_highest = std::max(transaction->priority(), current_highest); | |
133 | |
134 if (priority_ != current_highest) { | |
135 network_transaction_->SetPriority(current_highest); | |
136 priority_ = current_highest; | |
137 } | |
138 } | |
139 | |
140 bool HttpCache::Writers::ContainsOnlyIdleWriters() const { | |
141 return waiting_for_read_.empty() && !active_transaction_; | |
142 } | |
143 | |
144 HttpCache::TransactionSet HttpCache::Writers::RemoveAllIdleWriters() { | |
145 // Should be invoked after |waiting_for_read_| transactions and | |
146 // |active_transaction_| are processed so that all_writers_ only contains idle | |
147 // writers. | |
148 DCHECK(ContainsOnlyIdleWriters()); | |
149 | |
150 TransactionSet idle_writers; | |
151 idle_writers.insert(all_writers_.begin(), all_writers_.end()); | |
152 all_writers_.clear(); | |
153 ResetStateForEmptyWriters(); | |
154 return idle_writers; | |
155 } | |
156 | |
157 bool HttpCache::Writers::CanAddWriters() { | |
158 if (all_writers_.empty()) | |
159 return true; | |
160 | |
161 return !is_exclusive_ && !network_read_only_; | |
162 } | |
163 | |
164 void HttpCache::Writers::ProcessFailure(Transaction* transaction, int error) { | |
165 DCHECK(!transaction || transaction == active_transaction_); | |
166 | |
167 // Notify waiting_for_read_ of the failure. Tasks will be posted for all the | |
168 // transactions. | |
169 ProcessWaitingForReadTransactions(error); | |
170 | |
171 // Idle readers should fail when Read is invoked on them. | |
172 SetIdleWritersFailState(error); | |
173 | |
174 if (all_writers_.empty()) | |
175 ResetStateForEmptyWriters(); | |
176 } | |
177 | |
178 void HttpCache::Writers::TruncateEntry() { | |
179 // TODO(shivanisha) On integration, see if the entry really needs to be | |
180 // truncated on the lines of Transaction::AddTruncatedFlag and then proceed. | |
181 DCHECK_EQ(next_state_, State::NONE); | |
182 next_state_ = State::CACHE_WRITE_TRUNCATED_RESPONSE; | |
183 DoLoop(OK); | |
184 } | |
185 | |
186 LoadState HttpCache::Writers::GetWriterLoadState() { | |
187 DCHECK(network_transaction_); | |
188 return network_transaction_->GetLoadState(); | |
189 } | |
190 | |
191 HttpCache::Writers::WaitingForRead::WaitingForRead( | |
192 Transaction* cache_transaction, | |
193 scoped_refptr<IOBuffer> buf, | |
194 int len, | |
195 const CompletionCallback& consumer_callback) | |
196 : transaction(cache_transaction), | |
197 read_buf(std::move(buf)), | |
198 read_buf_len(len), | |
199 write_len(0), | |
200 callback(consumer_callback) { | |
201 DCHECK(cache_transaction); | |
202 DCHECK(read_buf); | |
203 DCHECK_GT(len, 0); | |
204 DCHECK(!consumer_callback.is_null()); | |
205 } | |
206 | |
207 HttpCache::Writers::WaitingForRead::~WaitingForRead() {} | |
208 HttpCache::Writers::WaitingForRead::WaitingForRead(const WaitingForRead&) = | |
209 default; | |
210 | |
211 int HttpCache::Writers::DoLoop(int result) { | |
212 DCHECK_NE(State::UNSET, next_state_); | |
213 DCHECK_NE(State::NONE, next_state_); | |
214 int rv = result; | |
215 do { | |
216 State state = next_state_; | |
217 next_state_ = State::UNSET; | |
218 switch (state) { | |
219 case State::NETWORK_READ: | |
220 DCHECK_EQ(OK, rv); | |
221 rv = DoNetworkRead(); | |
222 break; | |
223 case State::NETWORK_READ_COMPLETE: | |
224 rv = DoNetworkReadComplete(rv); | |
225 break; | |
226 case State::CACHE_WRITE_DATA: | |
227 rv = DoCacheWriteData(rv); | |
228 break; | |
229 case State::CACHE_WRITE_DATA_COMPLETE: | |
230 rv = DoCacheWriteDataComplete(rv); | |
231 break; | |
232 case State::CACHE_WRITE_TRUNCATED_RESPONSE: | |
233 rv = DoCacheWriteTruncatedResponse(); | |
234 break; | |
235 case State::CACHE_WRITE_TRUNCATED_RESPONSE_COMPLETE: | |
236 rv = DoCacheWriteTruncatedResponseComplete(rv); | |
237 break; | |
238 case State::UNSET: | |
239 NOTREACHED() << "bad state"; | |
240 rv = ERR_FAILED; | |
241 break; | |
242 case State::NONE: | |
243 // Do Nothing. | |
244 break; | |
245 } | |
jkarlin
2017/07/12 15:05:26
To enforce that the next state is set:
DCHECK(nex
shivanisha
2017/07/13 18:35:50
Because state is a class enum, getting a compile e
jkarlin
2017/07/14 18:30:48
Ah yes, the unset state does it. Thanks.
| |
246 } while (next_state_ != State::NONE && rv != ERR_IO_PENDING); | |
247 | |
248 if (rv != ERR_IO_PENDING && !callback_.is_null()) { | |
249 read_buf_ = NULL; | |
250 base::ResetAndReturn(&callback_).Run(rv); | |
251 } | |
252 return rv; | |
253 } | |
254 | |
255 int HttpCache::Writers::DoNetworkRead() { | |
256 next_state_ = State::NETWORK_READ_COMPLETE; | |
257 CompletionCallback io_callback = | |
258 base::Bind(&HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); | |
259 return network_transaction_->Read(read_buf_.get(), io_buf_len_, io_callback); | |
260 } | |
261 | |
262 int HttpCache::Writers::DoNetworkReadComplete(int result) { | |
263 if (result < 0) { | |
264 next_state_ = State::NONE; | |
265 OnNetworkReadFailure(result); | |
266 return result; | |
267 } | |
268 | |
269 next_state_ = State::CACHE_WRITE_DATA; | |
270 return result; | |
271 } | |
272 | |
273 void HttpCache::Writers::OnNetworkReadFailure(int result) { | |
274 ProcessFailure(active_transaction_, result); | |
275 | |
276 active_transaction_ = nullptr; | |
277 | |
278 // TODO(shivanisha): Invoke DoneWithEntry here while | |
279 // integrating this class with HttpCache. That will also invoke truncation of | |
280 // the entry. | |
281 } | |
282 | |
283 int HttpCache::Writers::DoCacheWriteData(int num_bytes) { | |
284 next_state_ = State::CACHE_WRITE_DATA_COMPLETE; | |
285 write_len_ = num_bytes; | |
286 if (!num_bytes || network_read_only_) | |
287 return num_bytes; | |
288 | |
289 int current_size = disk_entry_->GetDataSize(kResponseContentIndex); | |
290 CompletionCallback io_callback = | |
291 base::Bind(&HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); | |
292 | |
293 int rv = 0; | |
294 | |
295 PartialData* partial = nullptr; | |
296 // The active transaction must be alive if this is a partial request, as | |
297 // partial requests are exclusive and hence will always be the active | |
298 // transaction. | |
299 // TODO(shivanisha): When partial requests support parallel writing, this | |
300 // assumption will not be true. | |
301 if (active_transaction_) | |
302 partial = active_transaction_->partial(); | |
303 | |
304 if (!partial) { | |
305 rv = disk_entry_->WriteData(kResponseContentIndex, current_size, | |
306 read_buf_.get(), num_bytes, io_callback, true); | |
307 } else { | |
308 rv = partial->CacheWrite(disk_entry_, read_buf_.get(), num_bytes, | |
309 io_callback); | |
310 } | |
311 return rv; | |
312 } | |
313 | |
314 int HttpCache::Writers::DoCacheWriteDataComplete(int result) { | |
315 if (result != write_len_) { | |
316 OnCacheWriteFailure(); | |
317 | |
318 // |active_transaction_| can continue reading from the network. | |
319 result = write_len_; | |
320 } else { | |
321 OnDataReceived(result); | |
322 } | |
323 next_state_ = State::NONE; | |
324 return result; | |
325 } | |
326 | |
327 int HttpCache::Writers::DoCacheWriteTruncatedResponse() { | |
328 next_state_ = State::CACHE_WRITE_TRUNCATED_RESPONSE_COMPLETE; | |
329 const HttpResponseInfo* response = network_transaction_->GetResponseInfo(); | |
330 scoped_refptr<PickledIOBuffer> data(new PickledIOBuffer()); | |
331 response->Persist(data->pickle(), true /* skip_transient_headers*/, true); | |
332 data->Done(); | |
333 io_buf_len_ = data->pickle()->size(); | |
334 CompletionCallback io_callback = | |
335 base::Bind(&HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); | |
336 return disk_entry_->WriteData(kResponseInfoIndex, 0, data.get(), io_buf_len_, | |
337 io_callback, true); | |
338 } | |
339 | |
340 int HttpCache::Writers::DoCacheWriteTruncatedResponseComplete(int result) { | |
341 next_state_ = State::NONE; | |
342 if (result != io_buf_len_) { | |
343 DLOG(ERROR) << "failed to write response info to cache"; | |
344 | |
345 // TODO(shivanisha): Invoke DoneWritingToEntry so that this entry is doomed. | |
346 } | |
347 truncated_ = true; | |
348 return OK; | |
349 } | |
350 | |
351 void HttpCache::Writers::OnDataReceived(int result) { | |
352 if (result == 0) { | |
353 // Check if the response is actually completed or if not, attempt to mark | |
354 // the entry as truncated in OnNetworkReadFailure. | |
355 int current_size = disk_entry_->GetDataSize(kResponseContentIndex); | |
356 const HttpResponseInfo* response_info = | |
357 network_transaction_->GetResponseInfo(); | |
358 int64_t content_length = response_info->headers->GetContentLength(); | |
359 if (content_length >= 0 && content_length > current_size) { | |
360 OnNetworkReadFailure(result); | |
361 return; | |
362 } | |
363 // TODO(shivanisha) Invoke cache_->DoneWritingToEntry() with success after | |
364 // integration with HttpCache layer. | |
365 } | |
366 | |
367 // Notify waiting_for_read_. Tasks will be posted for all the | |
368 // transactions. | |
369 ProcessWaitingForReadTransactions(write_len_); | |
370 | |
371 active_transaction_ = nullptr; | |
372 | |
373 if (all_writers_.empty()) | |
374 ResetStateForEmptyWriters(); | |
375 } | |
376 | |
377 void HttpCache::Writers::OnCacheWriteFailure() { | |
378 DLOG(ERROR) << "failed to write response data to cache"; | |
379 | |
380 // Now writers will only be reading from the network. | |
381 network_read_only_ = true; | |
382 | |
383 ProcessFailure(active_transaction_, ERR_CACHE_WRITE_FAILURE); | |
384 | |
385 active_transaction_ = nullptr; | |
386 | |
387 // Call the cache_ function here even if |active_transaction_| is alive | |
388 // because it wouldn't know if this was an error case, since it gets a | |
389 // positive result back. | |
390 // TODO(shivanisha) : Invoke DoneWritingToEntry on integration. Since the | |
391 // active_transaction_ continues to read from the network, invoke | |
392 // DoneWritingToEntry with nullptr as transaction so that it is not removed | |
393 // from |this|. | |
394 } | |
395 | |
396 void HttpCache::Writers::ProcessWaitingForReadTransactions(int result) { | |
397 for (auto& waiting : waiting_for_read_) { | |
398 Transaction* transaction = waiting.transaction; | |
399 int callback_result = result; | |
400 | |
401 if (result >= 0) { // success | |
402 // Save the data in the waiting transaction's read buffer. | |
403 waiting.write_len = std::min(waiting.read_buf_len, result); | |
404 memcpy(waiting.read_buf->data(), read_buf_->data(), waiting.write_len); | |
405 callback_result = waiting.write_len; | |
406 } | |
407 | |
408 // If its response completion or failure, this transaction needs to be | |
409 // removed. | |
410 if (result <= 0) | |
411 all_writers_.erase(transaction); | |
412 | |
413 // Post task to notify transaction. | |
414 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
415 FROM_HERE, base::Bind(waiting.callback, callback_result)); | |
416 } | |
417 | |
418 waiting_for_read_.clear(); | |
419 } | |
420 | |
421 void HttpCache::Writers::SetIdleWritersFailState(int result) { | |
422 // Since this is only for idle transactions, waiting_for_read_ | |
423 // should be empty. | |
424 DCHECK(waiting_for_read_.empty()); | |
425 for (auto* transaction : all_writers_) { | |
426 if (transaction == active_transaction_) | |
427 continue; | |
428 transaction->SetSharedWritingFailState(result); | |
429 all_writers_.erase(transaction); | |
430 } | |
431 } | |
432 | |
433 void HttpCache::Writers::ResetStateForEmptyWriters() { | |
434 DCHECK(all_writers_.empty()); | |
435 network_read_only_ = false; | |
436 network_transaction_.reset(); | |
437 } | |
438 | |
439 void HttpCache::Writers::OnIOComplete(int result) { | |
440 DoLoop(result); | |
441 } | |
442 | |
443 } // namespace net | |
OLD | NEW |