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 #include "net/disk_cache/disk_cache.h" | |
10 #include "net/http/http_cache_transaction.h" | |
11 | |
12 namespace net { | |
13 | |
14 HttpCache::Writers::Writers(HttpCache* cache, ActiveEntry* entry) | |
15 : cache_(cache), entry_(entry), weak_factory_(this) { | |
16 io_callback_ = | |
17 base::Bind(&HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); | |
18 } | |
19 | |
20 HttpCache::Writers::~Writers() {} | |
21 | |
22 int HttpCache::Writers::Read(scoped_refptr<IOBuffer> buf, | |
23 int buf_len, | |
24 const CompletionCallback& callback, | |
25 Transaction* transaction) { | |
26 DCHECK(buf); | |
27 DCHECK_GT(buf_len, 0); | |
28 DCHECK(!callback.is_null()); | |
29 DCHECK(transaction); | |
30 | |
31 // If another transaction invoked a Read which is currently ongoing , then | |
32 // this transaction waits for the read to complete and gets its buffer filled | |
33 // with the data returned from that read. | |
34 if (next_state_ != State::NONE) { | |
35 WaitingForRead waiting_transaction(transaction, buf, buf_len, callback); | |
36 waiting_for_read_.push_back(waiting_transaction); | |
37 return ERR_IO_PENDING; | |
38 } | |
39 | |
40 DCHECK_EQ(next_state_, State::NONE); | |
41 DCHECK(callback_.is_null()); | |
42 active_transaction_ = transaction; | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
DCHECK_EQ(nullptr, active_transaction_.get())? (S
shivanisha
2017/06/07 15:19:19
done
| |
43 | |
44 read_buf_ = std::move(buf); | |
45 io_buf_len_ = buf_len; | |
46 next_state_ = State::NETWORK_READ; | |
47 | |
48 int rv = DoLoop(OK); | |
49 if (rv == ERR_IO_PENDING) { | |
50 callback_ = callback; | |
51 } | |
52 | |
53 return rv; | |
54 } | |
55 | |
56 bool HttpCache::Writers::StopCaching(Transaction* transaction) { | |
57 // If this is the only transaction in Writers, then stopping will be | |
58 // successful. If not, then we will not stop caching since there are | |
59 // other consumers waiting to read from the cache. | |
60 if (all_writers_.size() == 1) { | |
61 DCHECK(all_writers_.count(transaction)); | |
62 network_read_only_ = true; | |
63 return true; | |
64 } | |
65 return false; | |
66 } | |
67 | |
68 void HttpCache::Writers::AddTransaction( | |
69 Transaction* transaction, | |
70 std::unique_ptr<HttpTransaction> network_transaction) { | |
71 DCHECK(transaction); | |
72 DCHECK(CanAddWriters()); | |
73 | |
74 std::pair<TransactionSet::iterator, bool> return_val = | |
75 all_writers_.insert(transaction); | |
76 DCHECK_EQ(return_val.second, true); | |
77 | |
78 if (network_transaction) { | |
79 DCHECK(!network_transaction_); | |
80 network_transaction_ = std::move(network_transaction); | |
81 } | |
82 | |
83 DCHECK(network_transaction_); | |
84 | |
85 priority_ = std::max(transaction->priority(), priority_); | |
86 network_transaction_->SetPriority(priority_); | |
87 } | |
88 | |
89 void HttpCache::Writers::SetExclusive() { | |
90 DCHECK(all_writers_.size() <= 1); | |
91 is_exclusive_ = true; | |
92 } | |
93 | |
94 void HttpCache::Writers::RemoveTransaction(Transaction* transaction) { | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
Suggestion: DCHECK that the transaction was found
shivanisha
2017/06/07 15:19:18
DCHECK(it != all_writers_.end()); below takes care
| |
95 if (!transaction) | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:58
What's the use case for calling RemoveTransaction(
shivanisha
2017/06/07 15:19:18
Let's say active_transaction_ is null as it has be
Randy Smith (Not in Mondays)
2017/06/08 17:10:00
I guess I'll put this off until the integration CL
shivanisha
2017/06/08 17:55:56
DoneWritingToEntry() takes the transaction in the
| |
96 return; | |
97 | |
98 // The transaction should be part of all_writers. | |
99 auto it = all_writers_.find(transaction); | |
100 DCHECK(it != all_writers_.end()); | |
101 all_writers_.erase(transaction); | |
102 if (all_writers_.empty()) | |
103 ResetStateForEmptyWriters(); | |
104 PriorityChanged(); | |
105 | |
106 if (active_transaction_ == transaction) { | |
107 active_transaction_ = nullptr; | |
108 callback_.Reset(); | |
109 return; | |
110 } | |
111 | |
112 auto waiting_it = waiting_for_read_.begin(); | |
113 for (; waiting_it != waiting_for_read_.end(); waiting_it++) { | |
114 if (transaction == waiting_it->transaction) { | |
115 waiting_for_read_.erase(waiting_it); | |
116 // If a waiting transaction existed, there should have been an | |
117 // active_transaction_. | |
118 DCHECK(active_transaction_); | |
119 return; | |
120 } | |
121 } | |
122 } | |
123 | |
124 void HttpCache::Writers::PriorityChanged() { | |
125 // Get the current highest priority. | |
126 RequestPriority current_highest = MINIMUM_PRIORITY; | |
127 for (auto* transaction : all_writers_) | |
128 current_highest = std::max(transaction->priority(), current_highest); | |
129 | |
130 if (priority_ != current_highest) { | |
131 network_transaction_->SetPriority(current_highest); | |
132 priority_ = current_highest; | |
133 } | |
134 } | |
135 | |
136 bool HttpCache::Writers::ContainsOnlyIdleWriters() const { | |
137 return waiting_for_read_.empty() && !active_transaction_; | |
138 } | |
139 | |
140 void HttpCache::Writers::MoveIdleWritersToReaders() { | |
141 // Should be invoked after |waiting_for_read_| transactions and | |
142 // |active_transaction_| are processed so that all_writers_ only contains idle | |
143 // writers. | |
144 DCHECK(ContainsOnlyIdleWriters()); | |
145 for (auto* idle_writer : all_writers_) { | |
146 entry_->readers.insert(idle_writer); | |
147 } | |
148 all_writers_.clear(); | |
149 ResetStateForEmptyWriters(); | |
150 } | |
151 | |
152 bool HttpCache::Writers::CanAddWriters() { | |
153 if (all_writers_.empty()) | |
154 return true; | |
155 | |
156 return !is_exclusive_ && !network_read_only_; | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:58
What's the use case for adding writers when |all_w
shivanisha
2017/06/07 15:19:18
That shouldn't happen since network_read_only_ is
| |
157 } | |
158 | |
159 void HttpCache::Writers::ProcessFailure(Transaction* transaction, int error) { | |
160 DCHECK(!transaction || transaction == active_transaction_); | |
161 | |
162 // Notify waiting_for_read_ of the failure. Tasks will be posted for all the | |
163 // transactions. | |
164 ProcessWaitingForReadTransactions(error); | |
165 | |
166 // Idle readers should know to fail when Read is invoked by their consumers. | |
167 SetIdleWritersFailState(error); | |
168 DCHECK(all_writers_.empty()); | |
169 ResetStateForEmptyWriters(); | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
Shouldn't this already have happened if all_writer
shivanisha
2017/06/07 15:19:18
This basically resets some member variables and sh
Randy Smith (Not in Mondays)
2017/06/08 17:10:00
This makes the interface contract for ProcessWaiti
shivanisha
2017/06/08 17:55:56
ResetStateForEmptyWriters() is not really related
Randy Smith (Not in Mondays)
2017/06/13 17:45:05
TL;DR: I want to make on more try at explaining my
shivanisha
2017/06/14 02:33:16
I understand the concern here that it's not obviou
| |
170 } | |
171 | |
172 void HttpCache::Writers::TruncateEntry() { | |
173 // TODO(shivanisha) On integration, see if the entry really needs to be | |
174 // truncated on the lines of Transaction::AddTruncatedFlag and then proceed. | |
175 DCHECK_EQ(next_state_, State::NONE); | |
176 next_state_ = State::CACHE_WRITE_TRUNCATED_RESPONSE; | |
177 DoLoop(OK); | |
178 } | |
179 | |
180 LoadState HttpCache::Writers::GetWriterLoadState() { | |
181 return network_transaction_->GetLoadState(); | |
182 } | |
183 | |
184 HttpCache::Writers::WaitingForRead::WaitingForRead( | |
185 Transaction* cache_transaction, | |
186 scoped_refptr<IOBuffer> buf, | |
187 int len, | |
188 const CompletionCallback& consumer_callback) | |
189 : transaction(cache_transaction), | |
190 read_buf(std::move(buf)), | |
191 read_buf_len(len), | |
192 write_len(0), | |
193 callback(consumer_callback) { | |
194 DCHECK(cache_transaction); | |
195 DCHECK(buf); | |
196 DCHECK_GT(len, 0); | |
197 DCHECK(!consumer_callback.is_null()); | |
198 } | |
199 | |
200 HttpCache::Writers::WaitingForRead::~WaitingForRead() {} | |
201 HttpCache::Writers::WaitingForRead::WaitingForRead(const WaitingForRead&) = | |
202 default; | |
203 | |
204 int HttpCache::Writers::DoLoop(int result) { | |
205 DCHECK(next_state_ != State::NONE); | |
206 int rv = result; | |
207 do { | |
208 State state = next_state_; | |
209 next_state_ = State::NONE; | |
210 switch (state) { | |
211 case State::NETWORK_READ: | |
212 DCHECK_EQ(OK, rv); | |
213 rv = DoNetworkRead(); | |
214 break; | |
215 case State::NETWORK_READ_COMPLETE: | |
216 rv = DoNetworkReadComplete(rv); | |
217 break; | |
218 case State::CACHE_WRITE_DATA: | |
219 rv = DoCacheWriteData(rv); | |
220 break; | |
221 case State::CACHE_WRITE_DATA_COMPLETE: | |
222 rv = DoCacheWriteDataComplete(rv); | |
223 break; | |
224 case State::CACHE_WRITE_TRUNCATED_RESPONSE: | |
225 rv = DoCacheWriteTruncatedResponse(); | |
226 break; | |
227 case State::CACHE_WRITE_TRUNCATED_RESPONSE_COMPLETE: | |
228 rv = DoCacheWriteTruncatedResponseComplete(rv); | |
229 break; | |
230 default: | |
231 NOTREACHED() << "bad state"; | |
232 rv = ERR_FAILED; | |
233 break; | |
234 } | |
235 } while (next_state_ != State::NONE && rv != ERR_IO_PENDING); | |
236 | |
237 if (rv != ERR_IO_PENDING && !callback_.is_null()) { | |
238 read_buf_ = NULL; // Release the buffer before invoking the callback. | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:58
Does it matter whether we release the buffer befor
shivanisha
2017/06/07 15:19:19
From what I understand its important to release be
Randy Smith (Not in Mondays)
2017/06/08 17:10:00
It's true that it can be only used by one layer at
shivanisha
2017/06/12 18:51:14
Removed the comment.
| |
239 base::ResetAndReturn(&callback_).Run(rv); | |
240 } | |
241 return rv; | |
242 } | |
243 | |
244 int HttpCache::Writers::DoNetworkRead() { | |
245 next_state_ = State::NETWORK_READ_COMPLETE; | |
246 io_callback_ = | |
247 base::Bind(&HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); | |
248 return network_transaction_->Read(read_buf_.get(), io_buf_len_, io_callback_); | |
249 } | |
250 | |
251 int HttpCache::Writers::DoNetworkReadComplete(int result) { | |
252 if (result < 0) { | |
253 OnNetworkReadFailure(result); | |
254 return result; | |
255 } | |
256 | |
257 next_state_ = State::CACHE_WRITE_DATA; | |
258 return result; | |
259 } | |
260 | |
261 void HttpCache::Writers::OnNetworkReadFailure(int result) { | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
Why does this function take a |result| argument?
shivanisha
2017/06/07 15:19:19
A follow up CL will use result to invoke ProcessFa
| |
262 // At this point active_transaction_ may or may not be alive. | |
263 // If no consumer, invoke entry processing here itself. | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
This comment doesn't seem easily relatable to the
shivanisha
2017/06/07 15:19:19
Yes, removed it.
| |
264 cache_->DoneWithEntry(entry_, active_transaction_, true); | |
265 } | |
266 | |
267 int HttpCache::Writers::DoCacheWriteData(int num_bytes) { | |
268 next_state_ = State::CACHE_WRITE_DATA_COMPLETE; | |
269 write_len_ = num_bytes; | |
270 if (!num_bytes || network_read_only_) | |
271 return num_bytes; | |
272 | |
273 int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex); | |
274 io_callback_ = | |
275 base::Bind(&HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); | |
276 return WriteToEntry(kResponseContentIndex, current_size, read_buf_.get(), | |
277 num_bytes, io_callback_); | |
278 } | |
279 | |
280 int HttpCache::Writers::WriteToEntry(int index, | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:58
Given that this is a private function only called
shivanisha
2017/06/07 15:19:18
done
| |
281 int offset, | |
282 IOBuffer* data, | |
283 int data_len, | |
284 const CompletionCallback& callback) { | |
285 int rv = 0; | |
286 | |
287 PartialData* partial = nullptr; | |
288 // The active transaction must be alive if this is a partial request, as | |
289 // partial requests are exclusive and hence will always be the active | |
290 // transaction. | |
291 // Todo(shivanisha): When partial requests support parallel writing, this | |
292 // assumption will not be true. | |
293 if (active_transaction_) | |
294 partial = active_transaction_->partial(); | |
295 | |
296 if (!partial || !data_len) { | |
297 rv = entry_->disk_entry->WriteData(index, offset, data, data_len, callback, | |
298 true); | |
299 } else { | |
300 rv = partial->CacheWrite(entry_->disk_entry, data, data_len, callback); | |
301 } | |
302 return rv; | |
303 } | |
304 | |
305 int HttpCache::Writers::DoCacheWriteDataComplete(int result) { | |
306 if (result != write_len_) { | |
307 OnCacheWriteFailure(); | |
308 | |
309 // |active_transaction_| can continue reading from the network. | |
310 result = write_len_; | |
311 } else { | |
312 OnDataReceived(result); | |
313 } | |
314 return result; | |
315 } | |
316 | |
317 int HttpCache::Writers::DoCacheWriteTruncatedResponse() { | |
318 next_state_ = State::CACHE_WRITE_TRUNCATED_RESPONSE_COMPLETE; | |
319 const HttpResponseInfo* response = network_transaction_->GetResponseInfo(); | |
320 scoped_refptr<PickledIOBuffer> data(new PickledIOBuffer()); | |
321 response->Persist(data->pickle(), true /* skip_transient_headers*/, true); | |
322 data->Done(); | |
323 io_buf_len_ = data->pickle()->size(); | |
324 io_callback_ = | |
325 base::Bind(&HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr()); | |
326 return entry_->disk_entry->WriteData(kResponseInfoIndex, 0, data.get(), | |
327 io_buf_len_, io_callback_, true); | |
328 } | |
329 | |
330 int HttpCache::Writers::DoCacheWriteTruncatedResponseComplete(int result) { | |
331 if (result != io_buf_len_) { | |
332 DLOG(ERROR) << "failed to write response info to cache"; | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
What state does this leave the cache in? If it's
shivanisha
2017/06/07 15:19:19
done
| |
333 } | |
334 truncated_ = true; | |
335 return OK; | |
336 } | |
337 | |
338 void HttpCache::Writers::OnDataReceived(int result) { | |
339 if (result == 0) { | |
340 // Check if the response is actually completed or if not, attempt to mark | |
341 // the entry as truncated. | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
I don't see (here or in OnNetworkReadFailure()) an
shivanisha
2017/06/07 15:19:19
OnNetworkReadFailure() => DoneWithEntry() => Invok
Randy Smith (Not in Mondays)
2017/06/08 17:10:00
Gotchat, and that's fine. But I find the comment
shivanisha
2017/06/12 18:51:14
Added in the comment that the truncation attempt w
| |
342 bool completed = false; | |
343 int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex); | |
344 const HttpResponseInfo* response_info = | |
345 network_transaction_->GetResponseInfo(); | |
346 int64_t content_length = response_info->headers->GetContentLength(); | |
347 if ((content_length >= 0 && content_length <= current_size) || | |
348 content_length < 0) { | |
349 completed = true; | |
350 } | |
351 if (!completed) { | |
352 OnNetworkReadFailure(result); | |
353 return; | |
354 } | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:58
Why not combine these two conditionals? |complete
shivanisha
2017/06/07 15:19:19
done
| |
355 } | |
356 | |
357 // Notify waiting_for_read_. Tasks will be posted for all the | |
358 // transactions. | |
359 ProcessWaitingForReadTransactions(write_len_); | |
360 | |
361 active_transaction_ = nullptr; | |
362 | |
363 // TODO(shivanisha) Invoke cache_->DoneWritingToEntry if result is 0 implying | |
364 // response completion. | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:58
What gets in the way of doing this now?
shivanisha
2017/06/07 15:19:18
Messes up with the assertions in HttpCache layer s
| |
365 } | |
366 | |
367 void HttpCache::Writers::OnCacheWriteFailure() { | |
368 network_read_only_ = true; | |
369 | |
370 // Call the cache_ function here even if active_transaction_ is alive because | |
371 // it wouldn't know if this was an error case, since it gets a positive result | |
372 // back. This will in turn ProcessFailure. | |
373 // TODO(shivanisha) : Also pass active_transaction_ and | |
374 // ERR_CACHE_WRITE_FAILURE to DoneWritingToEntry on integration so that it can | |
375 // be passed back in ProcessFailure. | |
376 cache_->DoneWritingToEntry(entry_, false); | |
377 } | |
378 | |
379 void HttpCache::Writers::ProcessWaitingForReadTransactions(int result) { | |
380 for (auto it = waiting_for_read_.begin(); it != waiting_for_read_.end(); | |
381 it++) { | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:57
nit, suggestion: Why not use the new C++ range syn
shivanisha
2017/06/07 15:19:19
done
| |
382 Transaction* transaction = it->transaction; | |
383 int callback_result = result; | |
384 | |
385 if (result >= 0) { // success | |
386 // Save the data in all the waiting transactions' read buffers. | |
387 for (auto it = waiting_for_read_.begin(); it != waiting_for_read_.end(); | |
Randy Smith (Not in Mondays)
2017/06/06 12:54:58
I've read over this code a couple of times, and I'
shivanisha
2017/06/07 15:19:18
Oops, corrected.
| |
388 it++) { | |
389 it->write_len = std::min(it->read_buf_len, result); | |
390 memcpy(it->read_buf->data(), read_buf_->data(), it->write_len); | |
391 } | |
392 callback_result = it->write_len; | |
393 } | |
394 | |
395 // If its response completion or failure, this transaction needs to be | |
396 // removed. | |
397 if (result <= 0) | |
398 all_writers_.erase(transaction); | |
399 | |
400 // Post task to notify transaction. | |
401 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
402 FROM_HERE, base::Bind(it->callback, callback_result)); | |
403 } | |
404 | |
405 waiting_for_read_.clear(); | |
406 } | |
407 | |
408 void HttpCache::Writers::SetIdleWritersFailState(int result) { | |
409 // Since this is only for idle transactions, all waiting_for_read_ and | |
410 // active_transaction_ should be empty. | |
411 DCHECK(waiting_for_read_.empty()); | |
412 DCHECK(!active_transaction_); | |
413 for (auto* transaction : all_writers_) | |
414 transaction->SetSharedWritingFailState(result); | |
415 all_writers_.clear(); | |
416 ResetStateForEmptyWriters(); | |
417 } | |
418 | |
419 void HttpCache::Writers::ResetStateForEmptyWriters() { | |
420 DCHECK(all_writers_.empty()); | |
421 network_read_only_ = false; | |
422 network_transaction_.reset(); | |
423 } | |
424 | |
425 void HttpCache::Writers::OnIOComplete(int result) { | |
426 DoLoop(result); | |
427 } | |
428 | |
429 } // namespace net | |
OLD | NEW |