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 } |
| 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 |