xref: /aosp_15_r20/external/cronet/net/http/http_cache_writers.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2017 The Chromium Authors
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/functional/bind.h"
11 #include "base/functional/callback_helpers.h"
12 #include "base/logging.h"
13 #include "base/task/single_thread_task_runner.h"
14 #include "net/base/net_errors.h"
15 #include "net/disk_cache/disk_cache.h"
16 #include "net/http/http_cache_transaction.h"
17 #include "net/http/http_response_info.h"
18 #include "net/http/http_status_code.h"
19 #include "net/http/partial_data.h"
20 
21 namespace net {
22 
23 namespace {
24 
IsValidResponseForWriter(bool is_partial,const HttpResponseInfo * response_info)25 bool IsValidResponseForWriter(bool is_partial,
26                               const HttpResponseInfo* response_info) {
27   if (!response_info->headers.get()) {
28     return false;
29   }
30 
31   // Return false if the response code sent by the server is garbled.
32   // Both 200 and 304 are valid since concurrent writing is supported.
33   if (!is_partial &&
34       (response_info->headers->response_code() != net::HTTP_OK &&
35        response_info->headers->response_code() != net::HTTP_NOT_MODIFIED)) {
36     return false;
37   }
38 
39   return true;
40 }
41 
42 }  // namespace
43 
TransactionInfo(PartialData * partial_data,const bool is_truncated,HttpResponseInfo info)44 HttpCache::Writers::TransactionInfo::TransactionInfo(PartialData* partial_data,
45                                                      const bool is_truncated,
46                                                      HttpResponseInfo info)
47     : partial(partial_data), truncated(is_truncated), response_info(info) {}
48 
49 HttpCache::Writers::TransactionInfo::~TransactionInfo() = default;
50 
51 HttpCache::Writers::TransactionInfo::TransactionInfo(const TransactionInfo&) =
52     default;
53 
Writers(HttpCache * cache,scoped_refptr<HttpCache::ActiveEntry> entry)54 HttpCache::Writers::Writers(HttpCache* cache,
55                             scoped_refptr<HttpCache::ActiveEntry> entry)
56     : cache_(cache), entry_(entry) {
57   DCHECK(cache_);
58   DCHECK(entry_);
59 }
60 
61 HttpCache::Writers::~Writers() = default;
62 
Read(scoped_refptr<IOBuffer> buf,int buf_len,CompletionOnceCallback callback,Transaction * transaction)63 int HttpCache::Writers::Read(scoped_refptr<IOBuffer> buf,
64                              int buf_len,
65                              CompletionOnceCallback callback,
66                              Transaction* transaction) {
67   DCHECK(buf);
68   DCHECK_GT(buf_len, 0);
69   DCHECK(!callback.is_null());
70   DCHECK(transaction);
71 
72   // If another transaction invoked a Read which is currently ongoing, then
73   // this transaction waits for the read to complete and gets its buffer filled
74   // with the data returned from that read.
75   if (next_state_ != State::NONE) {
76     WaitingForRead read_info(buf, buf_len, std::move(callback));
77     waiting_for_read_.emplace(transaction, std::move(read_info));
78     return ERR_IO_PENDING;
79   }
80 
81   DCHECK_EQ(next_state_, State::NONE);
82   DCHECK(callback_.is_null());
83   DCHECK_EQ(nullptr, active_transaction_);
84   DCHECK(HasTransaction(transaction));
85   active_transaction_ = transaction;
86 
87   read_buf_ = std::move(buf);
88   io_buf_len_ = buf_len;
89   next_state_ = State::NETWORK_READ;
90 
91   int rv = DoLoop(OK);
92   if (rv == ERR_IO_PENDING) {
93     callback_ = std::move(callback);
94   }
95 
96   return rv;
97 }
98 
StopCaching(bool keep_entry)99 bool HttpCache::Writers::StopCaching(bool keep_entry) {
100   // If this is the only transaction in Writers, then stopping will be
101   // successful. If not, then we will not stop caching since there are
102   // other consumers waiting to read from the cache.
103   if (all_writers_.size() != 1) {
104     return false;
105   }
106 
107   network_read_only_ = true;
108   if (!keep_entry) {
109     should_keep_entry_ = false;
110     cache_->WritersDoomEntryRestartTransactions(entry_.get());
111   }
112 
113   return true;
114 }
115 
AddTransaction(Transaction * transaction,ParallelWritingPattern initial_writing_pattern,RequestPriority priority,const TransactionInfo & info)116 void HttpCache::Writers::AddTransaction(
117     Transaction* transaction,
118     ParallelWritingPattern initial_writing_pattern,
119     RequestPriority priority,
120     const TransactionInfo& info) {
121   DCHECK(transaction);
122   ParallelWritingPattern writers_pattern;
123   DCHECK(CanAddWriters(&writers_pattern));
124 
125   DCHECK_EQ(0u, all_writers_.count(transaction));
126 
127   // Set truncation related information.
128   response_info_truncation_ = info.response_info;
129   should_keep_entry_ =
130       IsValidResponseForWriter(info.partial != nullptr, &(info.response_info));
131 
132   if (all_writers_.empty()) {
133     DCHECK_EQ(PARALLEL_WRITING_NONE, parallel_writing_pattern_);
134     parallel_writing_pattern_ = initial_writing_pattern;
135     if (parallel_writing_pattern_ != PARALLEL_WRITING_JOIN) {
136       is_exclusive_ = true;
137     }
138   } else {
139     DCHECK_EQ(PARALLEL_WRITING_JOIN, parallel_writing_pattern_);
140   }
141 
142   if (info.partial && !info.truncated) {
143     DCHECK(!partial_do_not_truncate_);
144     partial_do_not_truncate_ = true;
145   }
146 
147   std::pair<Transaction*, TransactionInfo> writer(transaction, info);
148   all_writers_.insert(writer);
149 
150   priority_ = std::max(priority, priority_);
151   if (network_transaction_) {
152     network_transaction_->SetPriority(priority_);
153   }
154 }
155 
SetNetworkTransaction(Transaction * transaction,std::unique_ptr<HttpTransaction> network_transaction)156 void HttpCache::Writers::SetNetworkTransaction(
157     Transaction* transaction,
158     std::unique_ptr<HttpTransaction> network_transaction) {
159   DCHECK_EQ(1u, all_writers_.count(transaction));
160   DCHECK(network_transaction);
161   DCHECK(!network_transaction_);
162   network_transaction_ = std::move(network_transaction);
163   network_transaction_->SetPriority(priority_);
164 }
165 
ResetNetworkTransaction()166 void HttpCache::Writers::ResetNetworkTransaction() {
167   DCHECK(is_exclusive_);
168   DCHECK_EQ(1u, all_writers_.size());
169   DCHECK(all_writers_.begin()->second.partial);
170   network_transaction_.reset();
171 }
172 
RemoveTransaction(Transaction * transaction,bool success)173 void HttpCache::Writers::RemoveTransaction(Transaction* transaction,
174                                            bool success) {
175   EraseTransaction(transaction, OK);
176 
177   if (!all_writers_.empty()) {
178     return;
179   }
180 
181   if (!success && ShouldTruncate()) {
182     TruncateEntry();
183   }
184 
185   // Destroys `this`.
186   cache_->WritersDoneWritingToEntry(entry_, success, should_keep_entry_,
187                                     TransactionSet());
188 }
189 
EraseTransaction(Transaction * transaction,int result)190 void HttpCache::Writers::EraseTransaction(Transaction* transaction,
191                                           int result) {
192   // The transaction should be part of all_writers.
193   auto it = all_writers_.find(transaction);
194   DCHECK(it != all_writers_.end());
195   EraseTransaction(it, result);
196 }
197 
198 HttpCache::Writers::TransactionMap::iterator
EraseTransaction(TransactionMap::iterator it,int result)199 HttpCache::Writers::EraseTransaction(TransactionMap::iterator it, int result) {
200   Transaction* transaction = it->first;
201   transaction->WriterAboutToBeRemovedFromEntry(result);
202 
203   auto return_it = all_writers_.erase(it);
204 
205   if (all_writers_.empty() && next_state_ == State::NONE) {
206     // This needs to be called to handle the edge case where even before Read is
207     // invoked all transactions are removed. In that case the
208     // network_transaction_ will still have a valid request info and so it
209     // should be destroyed before its consumer is destroyed (request info
210     // is a raw pointer owned by its consumer).
211     network_transaction_.reset();
212   } else {
213     UpdatePriority();
214   }
215 
216   if (active_transaction_ == transaction) {
217     active_transaction_ = nullptr;
218   } else {
219     // If waiting for read, remove it from the map.
220     waiting_for_read_.erase(transaction);
221   }
222   return return_it;
223 }
224 
UpdatePriority()225 void HttpCache::Writers::UpdatePriority() {
226   // Get the current highest priority.
227   RequestPriority current_highest = MINIMUM_PRIORITY;
228   for (auto& writer : all_writers_) {
229     Transaction* transaction = writer.first;
230     current_highest = std::max(transaction->priority(), current_highest);
231   }
232 
233   if (priority_ != current_highest) {
234     if (network_transaction_) {
235       network_transaction_->SetPriority(current_highest);
236     }
237     priority_ = current_highest;
238   }
239 }
240 
CloseConnectionOnDestruction()241 void HttpCache::Writers::CloseConnectionOnDestruction() {
242   if (network_transaction_) {
243     network_transaction_->CloseConnectionOnDestruction();
244   }
245 }
246 
ContainsOnlyIdleWriters() const247 bool HttpCache::Writers::ContainsOnlyIdleWriters() const {
248   return waiting_for_read_.empty() && !active_transaction_;
249 }
250 
CanAddWriters(ParallelWritingPattern * reason)251 bool HttpCache::Writers::CanAddWriters(ParallelWritingPattern* reason) {
252   *reason = parallel_writing_pattern_;
253 
254   if (all_writers_.empty()) {
255     return true;
256   }
257 
258   return !is_exclusive_ && !network_read_only_;
259 }
260 
ProcessFailure(int error)261 void HttpCache::Writers::ProcessFailure(int error) {
262   // Notify waiting_for_read_ of the failure. Tasks will be posted for all the
263   // transactions.
264   CompleteWaitingForReadTransactions(error);
265 
266   // Idle readers should fail when Read is invoked on them.
267   RemoveIdleWriters(error);
268 }
269 
TruncateEntry()270 void HttpCache::Writers::TruncateEntry() {
271   DCHECK(ShouldTruncate());
272   auto data = base::MakeRefCounted<PickledIOBuffer>();
273   response_info_truncation_.Persist(data->pickle(),
274                                     true /* skip_transient_headers*/,
275                                     true /* response_truncated */);
276   data->Done();
277   io_buf_len_ = data->pickle()->size();
278   entry_->GetEntry()->WriteData(kResponseInfoIndex, 0, data.get(), io_buf_len_,
279                                 base::DoNothing(), true);
280 }
281 
ShouldTruncate()282 bool HttpCache::Writers::ShouldTruncate() {
283   // Don't set the flag for sparse entries or for entries that cannot be
284   // resumed.
285   if (!should_keep_entry_ || partial_do_not_truncate_) {
286     return false;
287   }
288 
289   // Check the response headers for strong validators.
290   // Note that if this is a 206, content-length was already fixed after calling
291   // PartialData::ResponseHeadersOK().
292   if (response_info_truncation_.headers->GetContentLength() <= 0 ||
293       response_info_truncation_.headers->HasHeaderValue("Accept-Ranges",
294                                                         "none") ||
295       !response_info_truncation_.headers->HasStrongValidators()) {
296     should_keep_entry_ = false;
297     return false;
298   }
299 
300   // Double check that there is something worth keeping.
301   int current_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
302   if (!current_size) {
303     should_keep_entry_ = false;
304     return false;
305   }
306 
307   if (response_info_truncation_.headers->HasHeader("Content-Encoding")) {
308     should_keep_entry_ = false;
309     return false;
310   }
311 
312   int64_t content_length =
313       response_info_truncation_.headers->GetContentLength();
314   if (content_length >= 0 && content_length <= current_size) {
315     return false;
316   }
317 
318   return true;
319 }
320 
GetLoadState() const321 LoadState HttpCache::Writers::GetLoadState() const {
322   if (network_transaction_) {
323     return network_transaction_->GetLoadState();
324   }
325   return LOAD_STATE_IDLE;
326 }
327 
WaitingForRead(scoped_refptr<IOBuffer> buf,int len,CompletionOnceCallback consumer_callback)328 HttpCache::Writers::WaitingForRead::WaitingForRead(
329     scoped_refptr<IOBuffer> buf,
330     int len,
331     CompletionOnceCallback consumer_callback)
332     : read_buf(std::move(buf)),
333       read_buf_len(len),
334       callback(std::move(consumer_callback)) {
335   DCHECK(read_buf);
336   DCHECK_GT(len, 0);
337   DCHECK(!callback.is_null());
338 }
339 
340 HttpCache::Writers::WaitingForRead::~WaitingForRead() = default;
341 HttpCache::Writers::WaitingForRead::WaitingForRead(WaitingForRead&&) = default;
342 
DoLoop(int result)343 int HttpCache::Writers::DoLoop(int result) {
344   DCHECK_NE(State::UNSET, next_state_);
345   DCHECK_NE(State::NONE, next_state_);
346 
347   int rv = result;
348   do {
349     State state = next_state_;
350     next_state_ = State::UNSET;
351     switch (state) {
352       case State::NETWORK_READ:
353         DCHECK_EQ(OK, rv);
354         rv = DoNetworkRead();
355         break;
356       case State::NETWORK_READ_COMPLETE:
357         rv = DoNetworkReadComplete(rv);
358         break;
359       case State::CACHE_WRITE_DATA:
360         rv = DoCacheWriteData(rv);
361         break;
362       case State::CACHE_WRITE_DATA_COMPLETE:
363         rv = DoCacheWriteDataComplete(rv);
364         break;
365       case State::UNSET:
366         NOTREACHED() << "bad state";
367         rv = ERR_FAILED;
368         break;
369       case State::NONE:
370         // Do Nothing.
371         break;
372     }
373   } while (next_state_ != State::NONE && rv != ERR_IO_PENDING);
374 
375   if (next_state_ != State::NONE) {
376     if (rv != ERR_IO_PENDING && !callback_.is_null()) {
377       std::move(callback_).Run(rv);
378     }
379     return rv;
380   }
381 
382   // Save the callback as |this| may be destroyed when |cache_callback_| is run.
383   // Note that |callback_| is intentionally reset even if it is not run.
384   CompletionOnceCallback callback = std::move(callback_);
385   read_buf_ = nullptr;
386   DCHECK(!all_writers_.empty() || cache_callback_);
387   if (cache_callback_) {
388     std::move(cache_callback_).Run();
389   }
390   // |this| may have been destroyed in the |cache_callback_|.
391   if (rv != ERR_IO_PENDING && !callback.is_null()) {
392     std::move(callback).Run(rv);
393   }
394   return rv;
395 }
396 
DoNetworkRead()397 int HttpCache::Writers::DoNetworkRead() {
398   DCHECK(network_transaction_);
399   next_state_ = State::NETWORK_READ_COMPLETE;
400 
401   // TODO(https://crbug.com/778641): This is a partial mitigation. When
402   // reading from the network, a valid HttpNetworkTransaction must be always
403   // available.
404   if (!network_transaction_) {
405     return ERR_FAILED;
406   }
407 
408   CompletionOnceCallback io_callback = base::BindOnce(
409       &HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr());
410   return network_transaction_->Read(read_buf_.get(), io_buf_len_,
411                                     std::move(io_callback));
412 }
413 
DoNetworkReadComplete(int result)414 int HttpCache::Writers::DoNetworkReadComplete(int result) {
415   if (result < 0) {
416     next_state_ = State::NONE;
417     OnNetworkReadFailure(result);
418     return result;
419   }
420 
421   next_state_ = State::CACHE_WRITE_DATA;
422   return result;
423 }
424 
OnNetworkReadFailure(int result)425 void HttpCache::Writers::OnNetworkReadFailure(int result) {
426   ProcessFailure(result);
427 
428   if (active_transaction_) {
429     EraseTransaction(active_transaction_, result);
430   }
431   active_transaction_ = nullptr;
432 
433   if (ShouldTruncate()) {
434     TruncateEntry();
435   }
436 
437   SetCacheCallback(false, TransactionSet());
438 }
439 
DoCacheWriteData(int num_bytes)440 int HttpCache::Writers::DoCacheWriteData(int num_bytes) {
441   next_state_ = State::CACHE_WRITE_DATA_COMPLETE;
442   write_len_ = num_bytes;
443   if (!num_bytes || network_read_only_) {
444     return num_bytes;
445   }
446 
447   int current_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
448   CompletionOnceCallback io_callback = base::BindOnce(
449       &HttpCache::Writers::OnIOComplete, weak_factory_.GetWeakPtr());
450 
451   int rv = 0;
452 
453   PartialData* partial = nullptr;
454   // The active transaction must be alive if this is a partial request, as
455   // partial requests are exclusive and hence will always be the active
456   // transaction.
457   // TODO(shivanisha): When partial requests support parallel writing, this
458   // assumption will not be true.
459   if (active_transaction_) {
460     partial = all_writers_.find(active_transaction_)->second.partial;
461   }
462 
463   if (!partial) {
464     last_disk_cache_access_start_time_ = base::TimeTicks::Now();
465     rv = entry_->GetEntry()->WriteData(kResponseContentIndex, current_size,
466                                        read_buf_.get(), num_bytes,
467                                        std::move(io_callback), true);
468   } else {
469     rv = partial->CacheWrite(entry_->GetEntry(), read_buf_.get(), num_bytes,
470                              std::move(io_callback));
471   }
472   return rv;
473 }
474 
DoCacheWriteDataComplete(int result)475 int HttpCache::Writers::DoCacheWriteDataComplete(int result) {
476   DCHECK(!all_writers_.empty());
477   DCHECK_GE(write_len_, 0);
478 
479   if (result != write_len_) {
480     next_state_ = State::NONE;
481 
482     // Note that it is possible for cache write to fail if the size of the file
483     // exceeds the per-file limit.
484     OnCacheWriteFailure();
485 
486     // |active_transaction_| can continue reading from the network.
487     return write_len_;
488   }
489 
490   if (!last_disk_cache_access_start_time_.is_null() && active_transaction_ &&
491       !all_writers_.find(active_transaction_)->second.partial) {
492     active_transaction_->AddDiskCacheWriteTime(
493         base::TimeTicks::Now() - last_disk_cache_access_start_time_);
494     last_disk_cache_access_start_time_ = base::TimeTicks();
495   }
496 
497   next_state_ = State::NONE;
498   OnDataReceived(write_len_);
499 
500   return write_len_;
501 }
502 
OnDataReceived(int result)503 void HttpCache::Writers::OnDataReceived(int result) {
504   DCHECK(!all_writers_.empty());
505 
506   auto it = all_writers_.find(active_transaction_);
507   bool is_partial =
508       active_transaction_ != nullptr && it->second.partial != nullptr;
509 
510   // Partial transaction will process the result, return from here.
511   // This is done because partial requests handling require an awareness of both
512   // headers and body state machines as they might have to go to the headers
513   // phase for the next range, so it cannot be completely handled here.
514   if (is_partial) {
515     active_transaction_ = nullptr;
516     return;
517   }
518 
519   if (result == 0) {
520     // Check if the response is actually completed or if not, attempt to mark
521     // the entry as truncated in OnNetworkReadFailure.
522     int current_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
523     DCHECK(network_transaction_);
524     const HttpResponseInfo* response_info =
525         network_transaction_->GetResponseInfo();
526     int64_t content_length = response_info->headers->GetContentLength();
527     if (content_length >= 0 && content_length > current_size) {
528       OnNetworkReadFailure(result);
529       return;
530     }
531 
532     if (active_transaction_) {
533       EraseTransaction(active_transaction_, result);
534     }
535     active_transaction_ = nullptr;
536     CompleteWaitingForReadTransactions(write_len_);
537 
538     // Invoke entry processing.
539     DCHECK(ContainsOnlyIdleWriters());
540     TransactionSet make_readers;
541     for (auto& writer : all_writers_) {
542       make_readers.insert(writer.first);
543     }
544     all_writers_.clear();
545     SetCacheCallback(true, make_readers);
546     // We assume the set callback will be called immediately.
547     DCHECK_EQ(next_state_, State::NONE);
548     return;
549   }
550 
551   // Notify waiting_for_read_. Tasks will be posted for all the
552   // transactions.
553   CompleteWaitingForReadTransactions(write_len_);
554 
555   active_transaction_ = nullptr;
556 }
557 
OnCacheWriteFailure()558 void HttpCache::Writers::OnCacheWriteFailure() {
559   DLOG(ERROR) << "failed to write response data to cache";
560 
561   ProcessFailure(ERR_CACHE_WRITE_FAILURE);
562 
563   // Now writers will only be reading from the network.
564   network_read_only_ = true;
565 
566   active_transaction_ = nullptr;
567 
568   should_keep_entry_ = false;
569   if (all_writers_.empty()) {
570     SetCacheCallback(false, TransactionSet());
571   } else {
572     cache_->WritersDoomEntryRestartTransactions(entry_.get());
573   }
574 }
575 
CompleteWaitingForReadTransactions(int result)576 void HttpCache::Writers::CompleteWaitingForReadTransactions(int result) {
577   for (auto it = waiting_for_read_.begin(); it != waiting_for_read_.end();) {
578     Transaction* transaction = it->first;
579     int callback_result = result;
580 
581     if (result >= 0) {  // success
582       // Save the data in the waiting transaction's read buffer.
583       it->second.write_len = std::min(it->second.read_buf_len, result);
584       memcpy(it->second.read_buf->data(), read_buf_->data(),
585              it->second.write_len);
586       callback_result = it->second.write_len;
587     }
588 
589     // Post task to notify transaction.
590     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
591         FROM_HERE,
592         base::BindOnce(std::move(it->second.callback), callback_result));
593 
594     it = waiting_for_read_.erase(it);
595 
596     // If its response completion or failure, this transaction needs to be
597     // removed from writers.
598     if (result <= 0) {
599       EraseTransaction(transaction, result);
600     }
601   }
602 }
603 
RemoveIdleWriters(int result)604 void HttpCache::Writers::RemoveIdleWriters(int result) {
605   // Since this is only for idle transactions, waiting_for_read_
606   // should be empty.
607   DCHECK(waiting_for_read_.empty());
608   for (auto it = all_writers_.begin(); it != all_writers_.end();) {
609     Transaction* transaction = it->first;
610     if (transaction == active_transaction_) {
611       it++;
612       continue;
613     }
614     it = EraseTransaction(it, result);
615   }
616 }
617 
SetCacheCallback(bool success,const TransactionSet & make_readers)618 void HttpCache::Writers::SetCacheCallback(bool success,
619                                           const TransactionSet& make_readers) {
620   DCHECK(!cache_callback_);
621   cache_callback_ = base::BindOnce(&HttpCache::WritersDoneWritingToEntry,
622                                    cache_->GetWeakPtr(), entry_, success,
623                                    should_keep_entry_, make_readers);
624 }
625 
OnIOComplete(int result)626 void HttpCache::Writers::OnIOComplete(int result) {
627   DoLoop(result);
628 }
629 
630 }  // namespace net
631