Add an exponential backoff to rechecking the app list doodle.
[chromium-blink-merge.git] / components / nacl / browser / pnacl_translation_cache.cc
blob1ccce9c0dff9ea3b44bb1ce00ed6d11ab620825f
1 // Copyright 2013 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.
5 #include "components/nacl/browser/pnacl_translation_cache.h"
7 #include <string>
9 #include "base/callback.h"
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/threading/thread_checker.h"
14 #include "components/nacl/common/pnacl_types.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "net/base/io_buffer.h"
17 #include "net/base/net_errors.h"
18 #include "net/disk_cache/disk_cache.h"
20 using base::IntToString;
21 using content::BrowserThread;
23 namespace {
25 void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); }
27 } // namespace
29 namespace pnacl {
30 // This is in pnacl namespace instead of static so they can be used
31 // by the unit test.
32 const int kMaxMemCacheSize = 100 * 1024 * 1024;
34 //////////////////////////////////////////////////////////////////////
35 // Handle Reading/Writing to Cache.
37 // PnaclTranslationCacheEntry is a shim that provides storage for the
38 // 'key' and 'data' strings as the disk_cache is performing various async
39 // operations. It also tracks the open disk_cache::Entry
40 // and ensures that the entry is closed.
41 class PnaclTranslationCacheEntry
42 : public base::RefCounted<PnaclTranslationCacheEntry> {
43 public:
44 static PnaclTranslationCacheEntry* GetReadEntry(
45 base::WeakPtr<PnaclTranslationCache> cache,
46 const std::string& key,
47 const GetNexeCallback& callback);
48 static PnaclTranslationCacheEntry* GetWriteEntry(
49 base::WeakPtr<PnaclTranslationCache> cache,
50 const std::string& key,
51 net::DrainableIOBuffer* write_nexe,
52 const CompletionCallback& callback);
54 void Start();
56 // Writes: ---
57 // v |
58 // Start -> Open Existing --------------> Write ---> Close
59 // \ ^
60 // \ /
61 // --> Create --
62 // Reads:
63 // Start -> Open --------Read ----> Close
64 // | ^
65 // |__|
66 enum CacheStep {
67 UNINITIALIZED,
68 OPEN_ENTRY,
69 CREATE_ENTRY,
70 TRANSFER_ENTRY,
71 CLOSE_ENTRY,
72 FINISHED
75 private:
76 friend class base::RefCounted<PnaclTranslationCacheEntry>;
77 PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache,
78 const std::string& key,
79 bool is_read);
80 ~PnaclTranslationCacheEntry();
82 // Try to open an existing entry in the backend
83 void OpenEntry();
84 // Create a new entry in the backend (for writes)
85 void CreateEntry();
86 // Write |len| bytes to the backend, starting at |offset|
87 void WriteEntry(int offset, int len);
88 // Read |len| bytes from the backend, starting at |offset|
89 void ReadEntry(int offset, int len);
90 // If there was an error, doom the entry. Then post a task to the IO
91 // thread to close (and delete) it.
92 void CloseEntry(int rv);
93 // Call the user callback, and signal to the cache to delete this.
94 void Finish(int rv);
95 // Used as the callback for all operations to the backend. Handle state
96 // transitions, track bytes transferred, and call the other helper methods.
97 void DispatchNext(int rv);
99 base::WeakPtr<PnaclTranslationCache> cache_;
100 std::string key_;
101 disk_cache::Entry* entry_;
102 CacheStep step_;
103 bool is_read_;
104 GetNexeCallback read_callback_;
105 CompletionCallback write_callback_;
106 scoped_refptr<net::DrainableIOBuffer> io_buf_;
107 base::ThreadChecker thread_checker_;
108 DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCacheEntry);
111 // static
112 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetReadEntry(
113 base::WeakPtr<PnaclTranslationCache> cache,
114 const std::string& key,
115 const GetNexeCallback& callback) {
116 PnaclTranslationCacheEntry* entry(
117 new PnaclTranslationCacheEntry(cache, key, true));
118 entry->read_callback_ = callback;
119 return entry;
122 // static
123 PnaclTranslationCacheEntry* PnaclTranslationCacheEntry::GetWriteEntry(
124 base::WeakPtr<PnaclTranslationCache> cache,
125 const std::string& key,
126 net::DrainableIOBuffer* write_nexe,
127 const CompletionCallback& callback) {
128 PnaclTranslationCacheEntry* entry(
129 new PnaclTranslationCacheEntry(cache, key, false));
130 entry->io_buf_ = write_nexe;
131 entry->write_callback_ = callback;
132 return entry;
135 PnaclTranslationCacheEntry::PnaclTranslationCacheEntry(
136 base::WeakPtr<PnaclTranslationCache> cache,
137 const std::string& key,
138 bool is_read)
139 : cache_(cache),
140 key_(key),
141 entry_(NULL),
142 step_(UNINITIALIZED),
143 is_read_(is_read) {}
145 PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() {
146 // Ensure we have called the user's callback
147 if (step_ != FINISHED) {
148 if (!read_callback_.is_null()) {
149 BrowserThread::PostTask(
150 BrowserThread::IO,
151 FROM_HERE,
152 base::Bind(read_callback_,
153 net::ERR_ABORTED,
154 scoped_refptr<net::DrainableIOBuffer>()));
156 if (!write_callback_.is_null()) {
157 BrowserThread::PostTask(BrowserThread::IO,
158 FROM_HERE,
159 base::Bind(write_callback_, net::ERR_ABORTED));
164 void PnaclTranslationCacheEntry::Start() {
165 DCHECK(thread_checker_.CalledOnValidThread());
166 step_ = OPEN_ENTRY;
167 OpenEntry();
170 // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called
171 // from DispatchNext, so they know that cache_ is still valid.
172 void PnaclTranslationCacheEntry::OpenEntry() {
173 int rv = cache_->backend()->OpenEntry(
174 key_,
175 &entry_,
176 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this));
177 if (rv != net::ERR_IO_PENDING)
178 DispatchNext(rv);
181 void PnaclTranslationCacheEntry::CreateEntry() {
182 int rv = cache_->backend()->CreateEntry(
183 key_,
184 &entry_,
185 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this));
186 if (rv != net::ERR_IO_PENDING)
187 DispatchNext(rv);
190 void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) {
191 DCHECK(io_buf_->BytesRemaining() == len);
192 int rv = entry_->WriteData(
194 offset,
195 io_buf_.get(),
196 len,
197 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this),
198 false);
199 if (rv != net::ERR_IO_PENDING)
200 DispatchNext(rv);
203 void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) {
204 int rv = entry_->ReadData(
206 offset,
207 io_buf_.get(),
208 len,
209 base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this));
210 if (rv != net::ERR_IO_PENDING)
211 DispatchNext(rv);
214 void PnaclTranslationCacheEntry::CloseEntry(int rv) {
215 DCHECK(entry_);
216 if (rv < 0) {
217 LOG(ERROR) << "Failed to close entry: " << net::ErrorToString(rv);
218 entry_->Doom();
220 BrowserThread::PostTask(
221 BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_));
222 Finish(rv);
225 void PnaclTranslationCacheEntry::Finish(int rv) {
226 step_ = FINISHED;
227 if (is_read_) {
228 if (!read_callback_.is_null()) {
229 BrowserThread::PostTask(BrowserThread::IO,
230 FROM_HERE,
231 base::Bind(read_callback_, rv, io_buf_));
233 } else {
234 if (!write_callback_.is_null()) {
235 BrowserThread::PostTask(
236 BrowserThread::IO, FROM_HERE, base::Bind(write_callback_, rv));
239 cache_->OpComplete(this);
242 void PnaclTranslationCacheEntry::DispatchNext(int rv) {
243 DCHECK(thread_checker_.CalledOnValidThread());
244 if (!cache_)
245 return;
247 switch (step_) {
248 case UNINITIALIZED:
249 case FINISHED:
250 LOG(ERROR) << "DispatchNext called uninitialized";
251 break;
253 case OPEN_ENTRY:
254 if (rv == net::OK) {
255 step_ = TRANSFER_ENTRY;
256 if (is_read_) {
257 int bytes_to_transfer = entry_->GetDataSize(1);
258 io_buf_ = new net::DrainableIOBuffer(
259 new net::IOBuffer(bytes_to_transfer), bytes_to_transfer);
260 ReadEntry(0, bytes_to_transfer);
261 } else {
262 WriteEntry(0, io_buf_->size());
264 } else {
265 if (rv != net::ERR_FAILED) {
266 // ERROR_FAILED is what we expect if the entry doesn't exist.
267 LOG(ERROR) << "OpenEntry failed: " << net::ErrorToString(rv);
269 if (is_read_) {
270 // Just a cache miss, not necessarily an error.
271 entry_ = NULL;
272 Finish(rv);
273 } else {
274 step_ = CREATE_ENTRY;
275 CreateEntry();
278 break;
280 case CREATE_ENTRY:
281 if (rv == net::OK) {
282 step_ = TRANSFER_ENTRY;
283 WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
284 } else {
285 LOG(ERROR) << "Failed to Create Entry: " << net::ErrorToString(rv);
286 Finish(rv);
288 break;
290 case TRANSFER_ENTRY:
291 if (rv < 0) {
292 // We do not call DispatchNext directly if WriteEntry/ReadEntry returns
293 // ERR_IO_PENDING, and the callback should not return that value either.
294 LOG(ERROR) << "Failed to complete write to entry: "
295 << net::ErrorToString(rv);
296 step_ = CLOSE_ENTRY;
297 CloseEntry(rv);
298 break;
299 } else if (rv > 0) {
300 io_buf_->DidConsume(rv);
301 if (io_buf_->BytesRemaining() > 0) {
302 is_read_
303 ? ReadEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining())
304 : WriteEntry(io_buf_->BytesConsumed(), io_buf_->BytesRemaining());
305 break;
308 // rv == 0 or we fell through (i.e. we have transferred all the bytes)
309 step_ = CLOSE_ENTRY;
310 DCHECK(io_buf_->BytesConsumed() == io_buf_->size());
311 if (is_read_)
312 io_buf_->SetOffset(0);
313 CloseEntry(0);
314 break;
316 case CLOSE_ENTRY:
317 step_ = UNINITIALIZED;
318 break;
322 //////////////////////////////////////////////////////////////////////
323 void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) {
324 open_entries_.erase(entry);
327 //////////////////////////////////////////////////////////////////////
328 // Construction and cache backend initialization
329 PnaclTranslationCache::PnaclTranslationCache() : in_memory_(false) {}
331 PnaclTranslationCache::~PnaclTranslationCache() {}
333 int PnaclTranslationCache::Init(net::CacheType cache_type,
334 const base::FilePath& cache_dir,
335 int cache_size,
336 const CompletionCallback& callback) {
337 int rv = disk_cache::CreateCacheBackend(
338 cache_type,
339 net::CACHE_BACKEND_DEFAULT,
340 cache_dir,
341 cache_size,
342 true /* force_initialize */,
343 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(),
344 NULL, /* dummy net log */
345 &disk_cache_,
346 base::Bind(&PnaclTranslationCache::OnCreateBackendComplete, AsWeakPtr()));
347 if (rv == net::ERR_IO_PENDING) {
348 init_callback_ = callback;
350 return rv;
353 void PnaclTranslationCache::OnCreateBackendComplete(int rv) {
354 if (rv < 0) {
355 LOG(ERROR) << "Backend init failed:" << net::ErrorToString(rv);
357 // Invoke our client's callback function.
358 if (!init_callback_.is_null()) {
359 BrowserThread::PostTask(
360 BrowserThread::IO, FROM_HERE, base::Bind(init_callback_, rv));
364 //////////////////////////////////////////////////////////////////////
365 // High-level API
367 void PnaclTranslationCache::StoreNexe(const std::string& key,
368 net::DrainableIOBuffer* nexe_data,
369 const CompletionCallback& callback) {
370 PnaclTranslationCacheEntry* entry = PnaclTranslationCacheEntry::GetWriteEntry(
371 AsWeakPtr(), key, nexe_data, callback);
372 open_entries_[entry] = entry;
373 entry->Start();
376 void PnaclTranslationCache::GetNexe(const std::string& key,
377 const GetNexeCallback& callback) {
378 PnaclTranslationCacheEntry* entry =
379 PnaclTranslationCacheEntry::GetReadEntry(AsWeakPtr(), key, callback);
380 open_entries_[entry] = entry;
381 entry->Start();
384 int PnaclTranslationCache::InitOnDisk(const base::FilePath& cache_directory,
385 const CompletionCallback& callback) {
386 in_memory_ = false;
387 return Init(net::PNACL_CACHE, cache_directory, 0 /* auto size */, callback);
390 int PnaclTranslationCache::InitInMemory(const CompletionCallback& callback) {
391 in_memory_ = true;
392 return Init(net::MEMORY_CACHE, base::FilePath(), kMaxMemCacheSize, callback);
395 int PnaclTranslationCache::Size() {
396 if (!disk_cache_)
397 return -1;
398 return disk_cache_->GetEntryCount();
401 // Beware that any changes to this function or to PnaclCacheInfo will
402 // effectively invalidate existing translation cache entries.
404 // static
405 std::string PnaclTranslationCache::GetKey(const nacl::PnaclCacheInfo& info) {
406 if (!info.pexe_url.is_valid() || info.abi_version < 0 || info.opt_level < 0 ||
407 info.extra_flags.size() > 512)
408 return std::string();
409 std::string retval("ABI:");
410 retval += IntToString(info.abi_version) + ";" + "opt:" +
411 IntToString(info.opt_level) +
412 (info.use_subzero ? "subzero;" : ";") + "URL:";
413 // Filter the username, password, and ref components from the URL
414 GURL::Replacements replacements;
415 replacements.ClearUsername();
416 replacements.ClearPassword();
417 replacements.ClearRef();
418 GURL key_url(info.pexe_url.ReplaceComponents(replacements));
419 retval += key_url.spec() + ";";
420 // You would think that there is already code to format base::Time values
421 // somewhere, but I haven't found it yet. In any case, doing it ourselves
422 // here means we can keep the format stable.
423 base::Time::Exploded exploded;
424 info.last_modified.UTCExplode(&exploded);
425 if (info.last_modified.is_null() || !exploded.HasValidValues()) {
426 memset(&exploded, 0, sizeof(exploded));
428 retval += "modified:" + IntToString(exploded.year) + ":" +
429 IntToString(exploded.month) + ":" +
430 IntToString(exploded.day_of_month) + ":" +
431 IntToString(exploded.hour) + ":" + IntToString(exploded.minute) +
432 ":" + IntToString(exploded.second) + ":" +
433 IntToString(exploded.millisecond) + ":UTC;";
434 retval += "etag:" + info.etag + ";";
435 retval += "sandbox:" + info.sandbox_isa + ";";
436 retval += "extra_flags:" + info.extra_flags + ";";
437 return retval;
440 int PnaclTranslationCache::DoomEntriesBetween(
441 base::Time initial,
442 base::Time end,
443 const CompletionCallback& callback) {
444 return disk_cache_->DoomEntriesBetween(initial, end, callback);
447 } // namespace pnacl