| // 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_host.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "chrome/browser/nacl_host/nacl_browser.h" |
| #include "chrome/browser/nacl_host/pnacl_translation_cache.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| |
| using content::BrowserThread; |
| |
| namespace { |
| static const base::FilePath::CharType kTranslationCacheDirectoryName[] = |
| FILE_PATH_LITERAL("PnaclTranslationCache"); |
| // Delay to wait for initialization of the cache backend |
| static const int kTranslationCacheInitializationDelayMs = 20; |
| } |
| |
| PnaclHost::PnaclHost() |
| : pending_backend_operations_(0), |
| cache_state_(CacheUninitialized), |
| weak_factory_(this) {} |
| |
| PnaclHost::~PnaclHost() { |
| // When PnaclHost is destroyed, it's too late to post anything to the cache |
| // thread (it will hang shutdown). So just leak the cache backend. |
| pnacl::PnaclTranslationCache* cache = disk_cache_.release(); |
| (void)cache; |
| } |
| |
| PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); } |
| |
| PnaclHost::PendingTranslation::PendingTranslation() |
| : process_handle(base::kNullProcessHandle), |
| render_view_id(0), |
| nexe_fd(base::kInvalidPlatformFileValue), |
| got_nexe_fd(false), |
| got_cache_reply(false), |
| got_cache_hit(false), |
| is_incognito(false), |
| callback(NexeFdCallback()), |
| cache_info(nacl::PnaclCacheInfo()) {} |
| PnaclHost::PendingTranslation::~PendingTranslation() {} |
| |
| bool PnaclHost::TranslationMayBeCached( |
| const PendingTranslationMap::iterator& entry) { |
| return !entry->second.is_incognito && |
| !entry->second.cache_info.has_no_store_header; |
| } |
| |
| /////////////////////////////////////// Initialization |
| |
| static base::FilePath GetCachePath() { |
| NaClBrowserDelegate* browser_delegate = NaClBrowser::GetDelegate(); |
| // Determine where the translation cache resides in the file system. It |
| // exists in Chrome's cache directory and is not tied to any specific |
| // profile. If we fail, return an empty path. |
| // Start by finding the user data directory. |
| base::FilePath user_data_dir; |
| if (!browser_delegate || |
| !browser_delegate->GetUserDirectory(&user_data_dir)) { |
| return base::FilePath(); |
| } |
| // The cache directory may or may not be the user data directory. |
| base::FilePath cache_file_path; |
| browser_delegate->GetCacheDirectory(&cache_file_path); |
| |
| // Append the base file name to the cache directory. |
| return cache_file_path.Append(kTranslationCacheDirectoryName); |
| } |
| |
| void PnaclHost::OnCacheInitialized(int net_error) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| // If the cache was cleared before the load completed, ignore. |
| if (cache_state_ == CacheReady) |
| return; |
| if (net_error != net::OK) { |
| // This will cause the cache to attempt to re-init on the next call to |
| // GetNexeFd. |
| cache_state_ = CacheUninitialized; |
| } else { |
| cache_state_ = CacheReady; |
| } |
| } |
| |
| void PnaclHost::Init() { |
| // Extra check that we're on the real IO thread since this version of |
| // Init isn't used in unit tests. |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| base::FilePath cache_path(GetCachePath()); |
| if (cache_path.empty() || cache_state_ != CacheUninitialized) |
| return; |
| disk_cache_.reset(new pnacl::PnaclTranslationCache()); |
| cache_state_ = CacheInitializing; |
| int rv = disk_cache_->InitOnDisk( |
| cache_path, |
| base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); |
| if (rv != net::ERR_IO_PENDING) |
| OnCacheInitialized(rv); |
| } |
| |
| // Initialize using the in-memory backend, and manually set the temporary file |
| // directory instead of using the system directory. |
| void PnaclHost::InitForTest(base::FilePath temp_dir) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| disk_cache_.reset(new pnacl::PnaclTranslationCache()); |
| cache_state_ = CacheInitializing; |
| temp_dir_ = temp_dir; |
| int rv = disk_cache_->InitInMemory( |
| base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); |
| if (rv != net::ERR_IO_PENDING) |
| OnCacheInitialized(rv); |
| } |
| |
| ///////////////////////////////////////// Temp files |
| |
| // Create a temporary file on the blocking pool |
| // static |
| void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir, |
| TempFileCallback cb) { |
| DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| |
| base::FilePath file_path; |
| base::PlatformFile file_handle(base::kInvalidPlatformFileValue); |
| bool rv = temp_dir.empty() |
| ? file_util::CreateTemporaryFile(&file_path) |
| : file_util::CreateTemporaryFileInDir(temp_dir, &file_path); |
| if (!rv) { |
| PLOG(ERROR) << "Temp file creation failed."; |
| } else { |
| base::PlatformFileError error; |
| file_handle = base::CreatePlatformFile( |
| file_path, |
| base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ | |
| base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY | |
| base::PLATFORM_FILE_DELETE_ON_CLOSE, |
| NULL, |
| &error); |
| |
| if (error != base::PLATFORM_FILE_OK) { |
| PLOG(ERROR) << "Temp file open failed: " << error; |
| file_handle = base::kInvalidPlatformFileValue; |
| } |
| } |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, base::Bind(cb, file_handle)); |
| } |
| |
| void PnaclHost::CreateTemporaryFile(TempFileCallback cb) { |
| if (!BrowserThread::PostBlockingPoolSequencedTask( |
| "PnaclHostCreateTempFile", |
| FROM_HERE, |
| base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| cb.Run(base::kInvalidPlatformFileValue); |
| } |
| } |
| |
| ///////////////////////////////////////// GetNexeFd implementation |
| ////////////////////// Common steps |
| |
| void PnaclHost::GetNexeFd(int render_process_id, |
| int render_view_id, |
| int pp_instance, |
| bool is_incognito, |
| const nacl::PnaclCacheInfo& cache_info, |
| const NexeFdCallback& cb) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (cache_state_ == CacheUninitialized) { |
| Init(); |
| } |
| if (cache_state_ != CacheReady) { |
| // If the backend hasn't yet initialized, try the request again later. |
| BrowserThread::PostDelayedTask(BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&PnaclHost::GetNexeFd, |
| weak_factory_.GetWeakPtr(), |
| render_process_id, |
| render_view_id, |
| pp_instance, |
| is_incognito, |
| cache_info, |
| cb), |
| base::TimeDelta::FromMilliseconds( |
| kTranslationCacheInitializationDelayMs)); |
| return; |
| } |
| |
| TranslationID id(render_process_id, pp_instance); |
| PendingTranslationMap::iterator entry = pending_translations_.find(id); |
| if (entry != pending_translations_.end()) { |
| // Existing translation must have been abandonded. Clean it up. |
| LOG(ERROR) << "GetNexeFd for already-pending translation"; |
| pending_translations_.erase(entry); |
| } |
| |
| std::string cache_key(disk_cache_->GetKey(cache_info)); |
| if (cache_key.empty()) { |
| LOG(ERROR) << "GetNexeFd: Invalid cache info"; |
| cb.Run(base::kInvalidPlatformFileValue, false); |
| return; |
| } |
| |
| PendingTranslation pt; |
| pt.render_view_id = render_view_id; |
| pt.callback = cb; |
| pt.cache_info = cache_info; |
| pt.cache_key = cache_key; |
| pt.is_incognito = is_incognito; |
| pending_translations_[id] = pt; |
| SendCacheQueryAndTempFileRequest(cache_key, id); |
| } |
| |
| // Dispatch the cache read request and the temp file creation request |
| // simultaneously; currently we need a temp file regardless of whether the |
| // request hits. |
| void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key, |
| const TranslationID& id) { |
| pending_backend_operations_++; |
| disk_cache_->GetNexe( |
| cache_key, |
| base::Bind( |
| &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id)); |
| |
| CreateTemporaryFile( |
| base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id)); |
| } |
| |
| // Callback from the translation cache query. |id| is bound from |
| // SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for |
| // our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated |
| // by PnaclTranslationCache and now belongs to PnaclHost. |
| // (Bound callbacks must re-lookup the TranslationID because the translation |
| // could be cancelled before they get called). |
| void PnaclHost::OnCacheQueryReturn( |
| const TranslationID& id, |
| int net_error, |
| scoped_refptr<net::DrainableIOBuffer> buffer) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| pending_backend_operations_--; |
| PendingTranslationMap::iterator entry(pending_translations_.find(id)); |
| if (entry == pending_translations_.end()) { |
| LOG(ERROR) << "OnCacheQueryReturn: id not found"; |
| DeInitIfSafe(); |
| return; |
| } |
| PendingTranslation* pt = &entry->second; |
| pt->got_cache_reply = true; |
| pt->got_cache_hit = (net_error == net::OK); |
| if (pt->got_cache_hit) |
| pt->nexe_read_buffer = buffer; |
| CheckCacheQueryReady(entry); |
| } |
| |
| // Callback from temp file creation. |id| is bound from |
| // SendCacheQueryAndTempFileRequest, and fd is the created file descriptor. |
| // If there was an error, fd is kInvalidPlatformFileValue. |
| // (Bound callbacks must re-lookup the TranslationID because the translation |
| // could be cancelled before they get called). |
| void PnaclHost::OnTempFileReturn(const TranslationID& id, |
| base::PlatformFile fd) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| PendingTranslationMap::iterator entry(pending_translations_.find(id)); |
| if (entry == pending_translations_.end()) { |
| // The renderer may have signaled an error or closed while the temp |
| // file was being created. |
| LOG(ERROR) << "OnTempFileReturn: id not found"; |
| BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd)); |
| return; |
| } |
| if (fd == base::kInvalidPlatformFileValue) { |
| // This translation will fail, but we need to retry any translation |
| // waiting for its result. |
| LOG(ERROR) << "OnTempFileReturn: temp file creation failed"; |
| std::string key(entry->second.cache_key); |
| entry->second.callback.Run(fd, false); |
| bool may_be_cached = TranslationMayBeCached(entry); |
| pending_translations_.erase(entry); |
| // No translations will be waiting for entries that will not be stored. |
| if (may_be_cached) |
| RequeryMatchingTranslations(key); |
| return; |
| } |
| PendingTranslation* pt = &entry->second; |
| pt->got_nexe_fd = true; |
| pt->nexe_fd = fd; |
| CheckCacheQueryReady(entry); |
| } |
| |
| // Check whether both the cache query and the temp file have returned, and check |
| // whether we actually got a hit or not. |
| void PnaclHost::CheckCacheQueryReady( |
| const PendingTranslationMap::iterator& entry) { |
| PendingTranslation* pt = &entry->second; |
| if (!(pt->got_cache_reply && pt->got_nexe_fd)) |
| return; |
| if (!pt->got_cache_hit) { |
| // Check if there is already a pending translation for this file. If there |
| // is, we will wait for it to come back, to avoid redundant translations. |
| for (PendingTranslationMap::iterator it = pending_translations_.begin(); |
| it != pending_translations_.end(); |
| ++it) { |
| // Another translation matches if it's a request for the same file, |
| if (it->second.cache_key == entry->second.cache_key && |
| // and it's not this translation, |
| it->first != entry->first && |
| // and it can be stored in the cache, |
| TranslationMayBeCached(it) && |
| // and it's already gotten past this check and returned the miss. |
| it->second.got_cache_reply && |
| it->second.got_nexe_fd) { |
| return; |
| } |
| } |
| ReturnMiss(entry); |
| return; |
| } |
| |
| if (!base::PostTaskAndReplyWithResult( |
| BrowserThread::GetBlockingPool(), |
| FROM_HERE, |
| base::Bind( |
| &PnaclHost::CopyBufferToFile, pt->nexe_fd, pt->nexe_read_buffer), |
| base::Bind(&PnaclHost::OnBufferCopiedToTempFile, |
| weak_factory_.GetWeakPtr(), |
| entry->first))) { |
| pt->callback.Run(base::kInvalidPlatformFileValue, false); |
| } |
| } |
| |
| //////////////////// GetNexeFd miss path |
| // Return the temp fd to the renderer, reporting a miss. |
| void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) { |
| // Return the fd |
| PendingTranslation* pt = &entry->second; |
| NexeFdCallback cb(pt->callback); |
| if (pt->nexe_fd == base::kInvalidPlatformFileValue) { |
| // Bad FD is unrecoverable, so clear out the entry |
| pending_translations_.erase(entry); |
| } |
| cb.Run(pt->nexe_fd, false); |
| } |
| |
| // On error, just return a null refptr. |
| // static |
| scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer( |
| base::PlatformFile fd) { |
| base::PlatformFileInfo info; |
| scoped_refptr<net::DrainableIOBuffer> buffer; |
| bool error = false; |
| if (!base::GetPlatformFileInfo(fd, &info) || |
| info.size >= std::numeric_limits<int>::max()) { |
| PLOG(ERROR) << "GetPlatformFileInfo failed"; |
| error = true; |
| } else { |
| buffer = new net::DrainableIOBuffer( |
| new net::IOBuffer(static_cast<int>(info.size)), info.size); |
| if (base::ReadPlatformFile(fd, 0, buffer->data(), buffer->size()) != |
| info.size) { |
| PLOG(ERROR) << "CopyFileToBuffer file read failed"; |
| error = true; |
| } |
| } |
| if (error) { |
| buffer = NULL; |
| } |
| base::ClosePlatformFile(fd); |
| return buffer; |
| } |
| |
| // Called by the renderer in the miss path to report a finished translation |
| void PnaclHost::TranslationFinished(int render_process_id, |
| int pp_instance, |
| bool success) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (cache_state_ != CacheReady) |
| return; |
| TranslationID id(render_process_id, pp_instance); |
| PendingTranslationMap::iterator entry(pending_translations_.find(id)); |
| if (entry == pending_translations_.end()) { |
| LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id |
| << "," << pp_instance << " not found."; |
| return; |
| } |
| bool store_nexe = true; |
| // If this is a premature response (i.e. we haven't returned a temp file |
| // yet) or if it's an unsuccessful translation, or if we are incognito, |
| // don't store in the cache. |
| // TODO(dschuff): use a separate in-memory cache for incognito |
| // translations. |
| if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply || |
| !success || !TranslationMayBeCached(entry)) { |
| store_nexe = false; |
| } else if (!base::PostTaskAndReplyWithResult( |
| BrowserThread::GetBlockingPool(), |
| FROM_HERE, |
| base::Bind(&PnaclHost::CopyFileToBuffer, |
| entry->second.nexe_fd), |
| base::Bind(&PnaclHost::StoreTranslatedNexe, |
| weak_factory_.GetWeakPtr(), |
| id))) { |
| store_nexe = false; |
| } |
| |
| if (!store_nexe) { |
| // If store_nexe is true, the fd will be closed by CopyFileToBuffer. |
| if (entry->second.got_nexe_fd) { |
| BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(base::ClosePlatformFile), |
| entry->second.nexe_fd)); |
| } |
| pending_translations_.erase(entry); |
| } |
| } |
| |
| // Store the translated nexe in the translation cache. Called back with the |
| // TranslationID from the host and the result of CopyFileToBuffer. |
| // (Bound callbacks must re-lookup the TranslationID because the translation |
| // could be cancelled before they get called). |
| void PnaclHost::StoreTranslatedNexe( |
| TranslationID id, |
| scoped_refptr<net::DrainableIOBuffer> buffer) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (cache_state_ != CacheReady) |
| return; |
| PendingTranslationMap::iterator it(pending_translations_.find(id)); |
| if (it == pending_translations_.end()) { |
| LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << "," |
| << id.second << " not found."; |
| return; |
| } |
| |
| if (buffer.get() == NULL) { |
| LOG(ERROR) << "Error reading translated nexe"; |
| return; |
| } |
| pending_backend_operations_++; |
| disk_cache_->StoreNexe(it->second.cache_key, |
| buffer, |
| base::Bind(&PnaclHost::OnTranslatedNexeStored, |
| weak_factory_.GetWeakPtr(), |
| it->first)); |
| } |
| |
| // After we know the nexe has been stored, we can clean up, and unblock any |
| // outstanding requests for the same file. |
| // (Bound callbacks must re-lookup the TranslationID because the translation |
| // could be cancelled before they get called). |
| void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) { |
| PendingTranslationMap::iterator entry(pending_translations_.find(id)); |
| pending_backend_operations_--; |
| if (entry == pending_translations_.end()) { |
| // If the renderer closed while we were storing the nexe, we land here. |
| // Make sure we try to de-init. |
| DeInitIfSafe(); |
| return; |
| } |
| std::string key(entry->second.cache_key); |
| pending_translations_.erase(entry); |
| RequeryMatchingTranslations(key); |
| } |
| |
| // Check if any pending translations match |key|. If so, re-issue the cache |
| // query. In the overlapped miss case, we expect a hit this time, but a miss |
| // is also possible in case of an error. |
| void PnaclHost::RequeryMatchingTranslations(const std::string& key) { |
| // Check for outstanding misses to this same file |
| for (PendingTranslationMap::iterator it = pending_translations_.begin(); |
| it != pending_translations_.end(); |
| ++it) { |
| if (it->second.cache_key == key) { |
| // Re-send the cache read request. This time we expect a hit, but if |
| // something goes wrong, it will just handle it like a miss. |
| it->second.got_cache_reply = false; |
| pending_backend_operations_++; |
| disk_cache_->GetNexe(key, |
| base::Bind(&PnaclHost::OnCacheQueryReturn, |
| weak_factory_.GetWeakPtr(), |
| it->first)); |
| } |
| } |
| } |
| |
| //////////////////// GetNexeFd hit path |
| |
| // static |
| int PnaclHost::CopyBufferToFile(base::PlatformFile fd, |
| scoped_refptr<net::DrainableIOBuffer> buffer) { |
| int rv = base::WritePlatformFile(fd, 0, buffer->data(), buffer->size()); |
| if (rv == -1) |
| PLOG(ERROR) << "CopyBufferToFile write error"; |
| return rv; |
| } |
| |
| void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id, |
| int file_error) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| PendingTranslationMap::iterator entry(pending_translations_.find(id)); |
| if (entry == pending_translations_.end()) { |
| return; |
| } |
| if (file_error == -1) { |
| // Write error on the temp file. Request a new file and start over. |
| BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(base::ClosePlatformFile), |
| entry->second.nexe_fd)); |
| entry->second.got_nexe_fd = false; |
| CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn, |
| weak_factory_.GetWeakPtr(), |
| entry->first)); |
| return; |
| } |
| base::PlatformFile fd = entry->second.nexe_fd; |
| entry->second.callback.Run(fd, true); |
| BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd)); |
| pending_translations_.erase(entry); |
| } |
| |
| /////////////////// |
| |
| void PnaclHost::RendererClosing(int render_process_id) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (cache_state_ != CacheReady) |
| return; |
| for (PendingTranslationMap::iterator it = pending_translations_.begin(); |
| it != pending_translations_.end();) { |
| PendingTranslationMap::iterator to_erase(it++); |
| if (to_erase->first.first == render_process_id) { |
| // Clean up the open files. |
| BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(base::ClosePlatformFile), |
| to_erase->second.nexe_fd)); |
| std::string key(to_erase->second.cache_key); |
| bool may_be_cached = TranslationMayBeCached(to_erase); |
| pending_translations_.erase(to_erase); |
| // No translations will be waiting for entries that will not be stored. |
| if (may_be_cached) |
| RequeryMatchingTranslations(key); |
| } |
| } |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr())); |
| } |
| |
| ////////////////// Cache data removal |
| void PnaclHost::ClearTranslationCacheEntriesBetween( |
| base::Time initial_time, |
| base::Time end_time, |
| const base::Closure& callback) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (cache_state_ == CacheUninitialized) { |
| Init(); |
| } |
| if (cache_state_ == CacheInitializing) { |
| // If the backend hasn't yet initialized, try the request again later. |
| BrowserThread::PostDelayedTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween, |
| weak_factory_.GetWeakPtr(), |
| initial_time, |
| end_time, |
| callback), |
| base::TimeDelta::FromMilliseconds( |
| kTranslationCacheInitializationDelayMs)); |
| return; |
| } |
| pending_backend_operations_++; |
| int rv = disk_cache_->DoomEntriesBetween( |
| initial_time, |
| end_time, |
| base::Bind( |
| &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback)); |
| if (rv != net::ERR_IO_PENDING) |
| OnEntriesDoomed(callback, rv); |
| } |
| |
| void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback); |
| pending_backend_operations_--; |
| // When clearing the cache, the UI is blocked on all the cache-clearing |
| // operations, and freeing the backend actually blocks the IO thread. So |
| // instead of calling DeInitIfSafe directly, post it for later. |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr())); |
| } |
| |
| // Destroying the cache backend causes it to post tasks to the cache thread to |
| // flush to disk. Because PnaclHost is a singleton, it does not get destroyed |
| // until all the browser threads have gone away and it's too late to post |
| // anything (attempting to do so hangs shutdown). So we make sure to destroy it |
| // when we no longer have any outstanding operations that need it. These include |
| // pending translations, cache clear requests, and requests to read or write |
| // translated nexes. We check when renderers close, when cache clear requests |
| // finish, and when backend operations complete. |
| |
| // It is not safe to delete the backend while it is initializing, nor if it has |
| // outstanding entry open requests; it is in theory safe to delete it with |
| // outstanding read/write requests, but because that distinction is hidden |
| // inside PnaclTranslationCache, we do not delete the backend if there are any |
| // backend requests in flight. As a last resort in the destructor, we just leak |
| // the backend to avoid hanging shutdown. |
| void PnaclHost::DeInitIfSafe() { |
| DCHECK(pending_backend_operations_ >= 0); |
| if (pending_translations_.empty() && pending_backend_operations_ <= 0) { |
| cache_state_ = CacheUninitialized; |
| disk_cache_.reset(); |
| } |
| } |