| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/nacl_host/pnacl_translation_cache.h" |
| |
| #include <string> |
| |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/threading/thread_checker.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "net/disk_cache/disk_cache.h" |
| |
| using content::BrowserThread; |
| |
| namespace { |
| |
| void CloseDiskCacheEntry(disk_cache::Entry* entry) { entry->Close(); } |
| |
| } // namespace |
| |
| namespace pnacl { |
| // These are in pnacl_cache namespace instead of static so they can be used |
| // by the unit test. |
| const int kMaxDiskCacheSize = 1000 * 1024 * 1024; |
| const int kMaxMemCacheSize = 100 * 1024 * 1024; |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Handle Reading/Writing to Cache. |
| |
| // PnaclTranslationCacheEntry is a shim that provides storage for the |
| // 'key' and 'data' strings as the disk_cache is performing various async |
| // operations. It also tracks the open disk_cache::Entry |
| // and ensures that the entry is closed. |
| class PnaclTranslationCacheEntry |
| : public base::RefCounted<PnaclTranslationCacheEntry> { |
| public: |
| PnaclTranslationCacheEntry(base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| std::string* read_nexe, |
| const std::string& write_nexe, |
| const CompletionCallback& callback, |
| bool is_read); |
| void Start(); |
| |
| // Writes: --- |
| // v | |
| // Start -> Open Existing --------------> Write ---> Close |
| // \ ^ |
| // \ / |
| // --> Create -- |
| // Reads: |
| // Start -> Open --------Read ----> Close |
| // | ^ |
| // |__| |
| enum CacheStep { |
| UNINITIALIZED, |
| OPEN_ENTRY, |
| CREATE_ENTRY, |
| TRANSFER_ENTRY, |
| CLOSE_ENTRY |
| }; |
| |
| private: |
| friend class base::RefCounted<PnaclTranslationCacheEntry>; |
| ~PnaclTranslationCacheEntry(); |
| |
| // Try to open an existing entry in the backend |
| void OpenEntry(); |
| // Create a new entry in the backend (for writes) |
| void CreateEntry(); |
| // Write |len| bytes to the backend, starting at |offset| |
| void WriteEntry(int offset, int len); |
| // Read |len| bytes from the backend, starting at |offset| |
| void ReadEntry(int offset, int len); |
| // If there was an error, doom the entry. Then post a task to the IO |
| // thread to close (and delete) it. |
| void CloseEntry(int rv); |
| // Call the user callback, and signal to the cache to delete this. |
| void Finish(int rv); |
| // Used as the callback for all operations to the backend. Handle state |
| // transitions, track bytes transferred, and call the other helper methods. |
| void DispatchNext(int rv); |
| // Get the total transfer size. For reads, must be called after the backend |
| // entry has been opened. |
| int GetTransferSize(); |
| |
| base::WeakPtr<PnaclTranslationCache> cache_; |
| |
| std::string key_; |
| std::string* read_nexe_; |
| std::string write_nexe_; |
| disk_cache::Entry* entry_; |
| CacheStep step_; |
| bool is_read_; |
| int bytes_transferred_; |
| int bytes_to_transfer_; |
| scoped_refptr<net::IOBufferWithSize> read_buf_; |
| CompletionCallback finish_callback_; |
| base::ThreadChecker thread_checker_; |
| DISALLOW_COPY_AND_ASSIGN(PnaclTranslationCacheEntry); |
| }; |
| |
| PnaclTranslationCacheEntry::PnaclTranslationCacheEntry( |
| base::WeakPtr<PnaclTranslationCache> cache, |
| const std::string& key, |
| std::string* read_nexe, |
| const std::string& write_nexe, |
| const CompletionCallback& callback, |
| bool is_read) |
| : cache_(cache), |
| key_(key), |
| read_nexe_(read_nexe), |
| write_nexe_(write_nexe), |
| entry_(NULL), |
| step_(UNINITIALIZED), |
| is_read_(is_read), |
| bytes_transferred_(0), |
| bytes_to_transfer_(-1), |
| finish_callback_(callback) {} |
| |
| PnaclTranslationCacheEntry::~PnaclTranslationCacheEntry() { |
| // Ensure we have called the user's callback |
| DCHECK(finish_callback_.is_null()); |
| } |
| |
| void PnaclTranslationCacheEntry::Start() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| step_ = OPEN_ENTRY; |
| OpenEntry(); |
| } |
| |
| // OpenEntry, CreateEntry, WriteEntry, ReadEntry and CloseEntry are only called |
| // from DispatchNext, so they know that cache_ is still valid. |
| void PnaclTranslationCacheEntry::OpenEntry() { |
| int rv = cache_->backend() |
| ->OpenEntry(key_, |
| &entry_, |
| base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); |
| if (rv != net::ERR_IO_PENDING) |
| DispatchNext(rv); |
| } |
| |
| void PnaclTranslationCacheEntry::CreateEntry() { |
| int rv = cache_->backend()->CreateEntry( |
| key_, |
| &entry_, |
| base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); |
| if (rv != net::ERR_IO_PENDING) |
| DispatchNext(rv); |
| } |
| |
| void PnaclTranslationCacheEntry::WriteEntry(int offset, int len) { |
| scoped_refptr<net::StringIOBuffer> io_buf = |
| new net::StringIOBuffer(write_nexe_.substr(offset, len)); |
| int rv = entry_->WriteData( |
| 1, |
| offset, |
| io_buf.get(), |
| len, |
| base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this), |
| false); |
| if (rv != net::ERR_IO_PENDING) |
| DispatchNext(rv); |
| } |
| |
| void PnaclTranslationCacheEntry::ReadEntry(int offset, int len) { |
| read_buf_ = new net::IOBufferWithSize(len); |
| int rv = entry_->ReadData( |
| 1, |
| offset, |
| read_buf_.get(), |
| len, |
| base::Bind(&PnaclTranslationCacheEntry::DispatchNext, this)); |
| if (rv != net::ERR_IO_PENDING) |
| DispatchNext(rv); |
| } |
| |
| int PnaclTranslationCacheEntry::GetTransferSize() { |
| if (is_read_) { |
| DCHECK(entry_); |
| return entry_->GetDataSize(1); |
| } |
| return write_nexe_.size(); |
| } |
| |
| void PnaclTranslationCacheEntry::CloseEntry(int rv) { |
| DCHECK(entry_); |
| if (rv < 0) |
| entry_->Doom(); |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, base::Bind(&CloseDiskCacheEntry, entry_)); |
| Finish(rv); |
| } |
| |
| void PnaclTranslationCacheEntry::Finish(int rv) { |
| if (!finish_callback_.is_null()) { |
| finish_callback_.Run(rv); |
| finish_callback_.Reset(); |
| } |
| cache_->OpComplete(this); |
| } |
| |
| void PnaclTranslationCacheEntry::DispatchNext(int rv) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (!cache_) |
| return; |
| |
| switch (step_) { |
| case UNINITIALIZED: |
| LOG(ERROR) << "Unexpected step in DispatchNext"; |
| break; |
| |
| case OPEN_ENTRY: |
| if (rv == net::OK) { |
| step_ = TRANSFER_ENTRY; |
| bytes_to_transfer_ = GetTransferSize(); |
| is_read_ ? ReadEntry(0, bytes_to_transfer_) |
| : WriteEntry(0, bytes_to_transfer_); |
| } else { |
| if (is_read_) { |
| // Just a cache miss, not necessarily an error. |
| entry_ = NULL; |
| Finish(rv); |
| break; |
| } |
| step_ = CREATE_ENTRY; |
| CreateEntry(); |
| } |
| break; |
| |
| case CREATE_ENTRY: |
| if (rv == net::OK) { |
| step_ = TRANSFER_ENTRY; |
| bytes_to_transfer_ = GetTransferSize(); |
| WriteEntry(bytes_transferred_, bytes_to_transfer_ - bytes_transferred_); |
| } else { |
| LOG(ERROR) << "Failed to Create a PNaCl Translation Cache Entry"; |
| Finish(rv); |
| } |
| break; |
| |
| case TRANSFER_ENTRY: |
| if (rv < 0) { |
| // We do not call DispatchNext directly if WriteEntry/ReadEntry returns |
| // ERR_IO_PENDING, and the callback should not return that value either. |
| LOG(ERROR) |
| << "Failed to complete write to PNaCl Translation Cache Entry: " |
| << rv; |
| step_ = CLOSE_ENTRY; |
| CloseEntry(rv); |
| break; |
| } else if (rv > 0) { |
| // For reads, copy the data that was just returned |
| if (is_read_) |
| read_nexe_->append(read_buf_->data(), rv); |
| bytes_transferred_ += rv; |
| if (bytes_transferred_ < bytes_to_transfer_) { |
| int len = bytes_to_transfer_ - bytes_transferred_; |
| is_read_ ? ReadEntry(bytes_transferred_, len) |
| : WriteEntry(bytes_transferred_, len); |
| break; |
| } |
| } |
| // rv == 0 or we fell through (i.e. we have transferred all the bytes) |
| step_ = CLOSE_ENTRY; |
| CloseEntry(0); |
| break; |
| |
| case CLOSE_ENTRY: |
| step_ = UNINITIALIZED; |
| break; |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| void PnaclTranslationCache::OpComplete(PnaclTranslationCacheEntry* entry) { |
| open_entries_.erase(entry); |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // Construction and cache backend initialization |
| PnaclTranslationCache::PnaclTranslationCache() |
| : disk_cache_(NULL), in_memory_(false) {} |
| |
| PnaclTranslationCache::~PnaclTranslationCache() { delete disk_cache_; } |
| |
| int PnaclTranslationCache::InitWithDiskBackend( |
| const base::FilePath& cache_dir, |
| int cache_size, |
| const CompletionCallback& callback) { |
| return Init(net::DISK_CACHE, cache_dir, cache_size, callback); |
| } |
| |
| int PnaclTranslationCache::InitWithMemBackend( |
| int cache_size, |
| const CompletionCallback& callback) { |
| return Init(net::MEMORY_CACHE, base::FilePath(), cache_size, callback); |
| } |
| |
| int PnaclTranslationCache::Init(net::CacheType cache_type, |
| const base::FilePath& cache_dir, |
| int cache_size, |
| const CompletionCallback& callback) { |
| int rv = disk_cache::CreateCacheBackend( |
| cache_type, |
| net::CACHE_BACKEND_DEFAULT, |
| cache_dir, |
| cache_size, |
| true /* force_initialize */, |
| BrowserThread::GetMessageLoopProxyForThread(BrowserThread::CACHE).get(), |
| NULL, /* dummy net log */ |
| &disk_cache_, |
| base::Bind(&PnaclTranslationCache::OnCreateBackendComplete, AsWeakPtr())); |
| init_callback_ = callback; |
| if (rv != net::ERR_IO_PENDING) { |
| OnCreateBackendComplete(rv); |
| } |
| return rv; |
| } |
| |
| void PnaclTranslationCache::OnCreateBackendComplete(int rv) { |
| // Invoke our client's callback function. |
| if (!init_callback_.is_null()) { |
| init_callback_.Run(rv); |
| init_callback_.Reset(); |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////// |
| // High-level API |
| |
| void PnaclTranslationCache::StoreNexe(const std::string& key, |
| const std::string& nexe) { |
| StoreNexe(key, nexe, CompletionCallback()); |
| } |
| |
| void PnaclTranslationCache::StoreNexe(const std::string& key, |
| const std::string& nexe, |
| const CompletionCallback& callback) { |
| PnaclTranslationCacheEntry* entry = new PnaclTranslationCacheEntry( |
| AsWeakPtr(), key, NULL, nexe, callback, false); |
| open_entries_[entry] = entry; |
| entry->Start(); |
| } |
| |
| void PnaclTranslationCache::GetNexe(const std::string& key, |
| std::string* nexe, |
| const CompletionCallback& callback) { |
| PnaclTranslationCacheEntry* entry = new PnaclTranslationCacheEntry( |
| AsWeakPtr(), key, nexe, std::string(), callback, true); |
| open_entries_[entry] = entry; |
| entry->Start(); |
| } |
| |
| int PnaclTranslationCache::InitCache(const base::FilePath& cache_directory, |
| bool in_memory, |
| const CompletionCallback& callback) { |
| int rv; |
| in_memory_ = in_memory; |
| if (in_memory_) { |
| rv = InitWithMemBackend(kMaxMemCacheSize, callback); |
| } else { |
| rv = InitWithDiskBackend(cache_directory, |
| kMaxDiskCacheSize, |
| callback); |
| } |
| |
| return rv; |
| } |
| |
| int PnaclTranslationCache::Size() { |
| if (!disk_cache_) |
| return -1; |
| return disk_cache_->GetEntryCount(); |
| } |
| |
| } // namespace pnacl |