Home | History | Annotate | Download | only in browser
      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.
      4 
      5 #include "components/nacl/browser/pnacl_host.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/file_util.h"
     10 #include "base/files/file_path.h"
     11 #include "base/logging.h"
     12 #include "base/task_runner_util.h"
     13 #include "base/threading/sequenced_worker_pool.h"
     14 #include "components/nacl/browser/nacl_browser.h"
     15 #include "components/nacl/browser/pnacl_translation_cache.h"
     16 #include "content/public/browser/browser_thread.h"
     17 #include "net/base/io_buffer.h"
     18 #include "net/base/net_errors.h"
     19 
     20 using content::BrowserThread;
     21 
     22 namespace {
     23 static const base::FilePath::CharType kTranslationCacheDirectoryName[] =
     24     FILE_PATH_LITERAL("PnaclTranslationCache");
     25 // Delay to wait for initialization of the cache backend
     26 static const int kTranslationCacheInitializationDelayMs = 20;
     27 }
     28 
     29 namespace pnacl {
     30 
     31 PnaclHost::PnaclHost()
     32     : pending_backend_operations_(0),
     33       cache_state_(CacheUninitialized),
     34       weak_factory_(this) {}
     35 
     36 PnaclHost::~PnaclHost() {
     37   // When PnaclHost is destroyed, it's too late to post anything to the cache
     38   // thread (it will hang shutdown). So just leak the cache backend.
     39   pnacl::PnaclTranslationCache* cache = disk_cache_.release();
     40   (void)cache;
     41 }
     42 
     43 PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); }
     44 
     45 PnaclHost::PendingTranslation::PendingTranslation()
     46     : process_handle(base::kNullProcessHandle),
     47       render_view_id(0),
     48       nexe_fd(base::kInvalidPlatformFileValue),
     49       got_nexe_fd(false),
     50       got_cache_reply(false),
     51       got_cache_hit(false),
     52       is_incognito(false),
     53       callback(NexeFdCallback()),
     54       cache_info(nacl::PnaclCacheInfo()) {}
     55 PnaclHost::PendingTranslation::~PendingTranslation() {}
     56 
     57 bool PnaclHost::TranslationMayBeCached(
     58     const PendingTranslationMap::iterator& entry) {
     59   return !entry->second.is_incognito &&
     60          !entry->second.cache_info.has_no_store_header;
     61 }
     62 
     63 /////////////////////////////////////// Initialization
     64 
     65 static base::FilePath GetCachePath() {
     66   NaClBrowserDelegate* browser_delegate = nacl::NaClBrowser::GetDelegate();
     67   // Determine where the translation cache resides in the file system.  It
     68   // exists in Chrome's cache directory and is not tied to any specific
     69   // profile. If we fail, return an empty path.
     70   // Start by finding the user data directory.
     71   base::FilePath user_data_dir;
     72   if (!browser_delegate ||
     73       !browser_delegate->GetUserDirectory(&user_data_dir)) {
     74     return base::FilePath();
     75   }
     76   // The cache directory may or may not be the user data directory.
     77   base::FilePath cache_file_path;
     78   browser_delegate->GetCacheDirectory(&cache_file_path);
     79 
     80   // Append the base file name to the cache directory.
     81   return cache_file_path.Append(kTranslationCacheDirectoryName);
     82 }
     83 
     84 void PnaclHost::OnCacheInitialized(int net_error) {
     85   DCHECK(thread_checker_.CalledOnValidThread());
     86   // If the cache was cleared before the load completed, ignore.
     87   if (cache_state_ == CacheReady)
     88     return;
     89   if (net_error != net::OK) {
     90     // This will cause the cache to attempt to re-init on the next call to
     91     // GetNexeFd.
     92     cache_state_ = CacheUninitialized;
     93   } else {
     94     cache_state_ = CacheReady;
     95   }
     96 }
     97 
     98 void PnaclHost::Init() {
     99   // Extra check that we're on the real IO thread since this version of
    100   // Init isn't used in unit tests.
    101   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    102   DCHECK(thread_checker_.CalledOnValidThread());
    103   base::FilePath cache_path(GetCachePath());
    104   if (cache_path.empty() || cache_state_ != CacheUninitialized)
    105     return;
    106   disk_cache_.reset(new pnacl::PnaclTranslationCache());
    107   cache_state_ = CacheInitializing;
    108   int rv = disk_cache_->InitOnDisk(
    109       cache_path,
    110       base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
    111   if (rv != net::ERR_IO_PENDING)
    112     OnCacheInitialized(rv);
    113 }
    114 
    115 // Initialize using the in-memory backend, and manually set the temporary file
    116 // directory instead of using the system directory.
    117 void PnaclHost::InitForTest(base::FilePath temp_dir) {
    118   DCHECK(thread_checker_.CalledOnValidThread());
    119   disk_cache_.reset(new pnacl::PnaclTranslationCache());
    120   cache_state_ = CacheInitializing;
    121   temp_dir_ = temp_dir;
    122   int rv = disk_cache_->InitInMemory(
    123       base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
    124   if (rv != net::ERR_IO_PENDING)
    125     OnCacheInitialized(rv);
    126 }
    127 
    128 ///////////////////////////////////////// Temp files
    129 
    130 // Create a temporary file on the blocking pool
    131 // static
    132 void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir,
    133                                       TempFileCallback cb) {
    134   DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    135 
    136   base::FilePath file_path;
    137   base::PlatformFile file_handle(base::kInvalidPlatformFileValue);
    138   bool rv = temp_dir.empty()
    139                 ? base::CreateTemporaryFile(&file_path)
    140                 : base::CreateTemporaryFileInDir(temp_dir, &file_path);
    141   if (!rv) {
    142     PLOG(ERROR) << "Temp file creation failed.";
    143   } else {
    144     base::PlatformFileError error;
    145     file_handle = base::CreatePlatformFile(
    146         file_path,
    147         base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_READ |
    148             base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_TEMPORARY |
    149             base::PLATFORM_FILE_DELETE_ON_CLOSE,
    150         NULL,
    151         &error);
    152 
    153     if (error != base::PLATFORM_FILE_OK) {
    154       PLOG(ERROR) << "Temp file open failed: " << error;
    155       file_handle = base::kInvalidPlatformFileValue;
    156     }
    157   }
    158   BrowserThread::PostTask(
    159       BrowserThread::IO, FROM_HERE, base::Bind(cb, file_handle));
    160 }
    161 
    162 void PnaclHost::CreateTemporaryFile(TempFileCallback cb) {
    163   if (!BrowserThread::PostBlockingPoolSequencedTask(
    164            "PnaclHostCreateTempFile",
    165            FROM_HERE,
    166            base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) {
    167     DCHECK(thread_checker_.CalledOnValidThread());
    168     cb.Run(base::kInvalidPlatformFileValue);
    169   }
    170 }
    171 
    172 ///////////////////////////////////////// GetNexeFd implementation
    173 ////////////////////// Common steps
    174 
    175 void PnaclHost::GetNexeFd(int render_process_id,
    176                           int render_view_id,
    177                           int pp_instance,
    178                           bool is_incognito,
    179                           const nacl::PnaclCacheInfo& cache_info,
    180                           const NexeFdCallback& cb) {
    181   DCHECK(thread_checker_.CalledOnValidThread());
    182   if (cache_state_ == CacheUninitialized) {
    183     Init();
    184   }
    185   if (cache_state_ != CacheReady) {
    186     // If the backend hasn't yet initialized, try the request again later.
    187     BrowserThread::PostDelayedTask(BrowserThread::IO,
    188                                    FROM_HERE,
    189                                    base::Bind(&PnaclHost::GetNexeFd,
    190                                               weak_factory_.GetWeakPtr(),
    191                                               render_process_id,
    192                                               render_view_id,
    193                                               pp_instance,
    194                                               is_incognito,
    195                                               cache_info,
    196                                               cb),
    197                                    base::TimeDelta::FromMilliseconds(
    198                                        kTranslationCacheInitializationDelayMs));
    199     return;
    200   }
    201 
    202   TranslationID id(render_process_id, pp_instance);
    203   PendingTranslationMap::iterator entry = pending_translations_.find(id);
    204   if (entry != pending_translations_.end()) {
    205     // Existing translation must have been abandonded. Clean it up.
    206     LOG(ERROR) << "GetNexeFd for already-pending translation";
    207     pending_translations_.erase(entry);
    208   }
    209 
    210   std::string cache_key(disk_cache_->GetKey(cache_info));
    211   if (cache_key.empty()) {
    212     LOG(ERROR) << "GetNexeFd: Invalid cache info";
    213     cb.Run(base::kInvalidPlatformFileValue, false);
    214     return;
    215   }
    216 
    217   PendingTranslation pt;
    218   pt.render_view_id = render_view_id;
    219   pt.callback = cb;
    220   pt.cache_info = cache_info;
    221   pt.cache_key = cache_key;
    222   pt.is_incognito = is_incognito;
    223   pending_translations_[id] = pt;
    224   SendCacheQueryAndTempFileRequest(cache_key, id);
    225 }
    226 
    227 // Dispatch the cache read request and the temp file creation request
    228 // simultaneously; currently we need a temp file regardless of whether the
    229 // request hits.
    230 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key,
    231                                                  const TranslationID& id) {
    232   pending_backend_operations_++;
    233   disk_cache_->GetNexe(
    234       cache_key,
    235       base::Bind(
    236           &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id));
    237 
    238   CreateTemporaryFile(
    239       base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id));
    240 }
    241 
    242 // Callback from the translation cache query. |id| is bound from
    243 // SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for
    244 // our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated
    245 // by PnaclTranslationCache and now belongs to PnaclHost.
    246 // (Bound callbacks must re-lookup the TranslationID because the translation
    247 // could be cancelled before they get called).
    248 void PnaclHost::OnCacheQueryReturn(
    249     const TranslationID& id,
    250     int net_error,
    251     scoped_refptr<net::DrainableIOBuffer> buffer) {
    252   DCHECK(thread_checker_.CalledOnValidThread());
    253   pending_backend_operations_--;
    254   PendingTranslationMap::iterator entry(pending_translations_.find(id));
    255   if (entry == pending_translations_.end()) {
    256     LOG(ERROR) << "OnCacheQueryReturn: id not found";
    257     DeInitIfSafe();
    258     return;
    259   }
    260   PendingTranslation* pt = &entry->second;
    261   pt->got_cache_reply = true;
    262   pt->got_cache_hit = (net_error == net::OK);
    263   if (pt->got_cache_hit)
    264     pt->nexe_read_buffer = buffer;
    265   CheckCacheQueryReady(entry);
    266 }
    267 
    268 // Callback from temp file creation. |id| is bound from
    269 // SendCacheQueryAndTempFileRequest, and fd is the created file descriptor.
    270 // If there was an error, fd is kInvalidPlatformFileValue.
    271 // (Bound callbacks must re-lookup the TranslationID because the translation
    272 // could be cancelled before they get called).
    273 void PnaclHost::OnTempFileReturn(const TranslationID& id,
    274                                  base::PlatformFile fd) {
    275   DCHECK(thread_checker_.CalledOnValidThread());
    276   PendingTranslationMap::iterator entry(pending_translations_.find(id));
    277   if (entry == pending_translations_.end()) {
    278     // The renderer may have signaled an error or closed while the temp
    279     // file was being created.
    280     LOG(ERROR) << "OnTempFileReturn: id not found";
    281     BrowserThread::PostBlockingPoolTask(
    282         FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
    283     return;
    284   }
    285   if (fd == base::kInvalidPlatformFileValue) {
    286     // This translation will fail, but we need to retry any translation
    287     // waiting for its result.
    288     LOG(ERROR) << "OnTempFileReturn: temp file creation failed";
    289     std::string key(entry->second.cache_key);
    290     entry->second.callback.Run(fd, false);
    291     bool may_be_cached = TranslationMayBeCached(entry);
    292     pending_translations_.erase(entry);
    293     // No translations will be waiting for entries that will not be stored.
    294     if (may_be_cached)
    295       RequeryMatchingTranslations(key);
    296     return;
    297   }
    298   PendingTranslation* pt = &entry->second;
    299   pt->got_nexe_fd = true;
    300   pt->nexe_fd = fd;
    301   CheckCacheQueryReady(entry);
    302 }
    303 
    304 // Check whether both the cache query and the temp file have returned, and check
    305 // whether we actually got a hit or not.
    306 void PnaclHost::CheckCacheQueryReady(
    307     const PendingTranslationMap::iterator& entry) {
    308   PendingTranslation* pt = &entry->second;
    309   if (!(pt->got_cache_reply && pt->got_nexe_fd))
    310     return;
    311   if (!pt->got_cache_hit) {
    312     // Check if there is already a pending translation for this file. If there
    313     // is, we will wait for it to come back, to avoid redundant translations.
    314     for (PendingTranslationMap::iterator it = pending_translations_.begin();
    315          it != pending_translations_.end();
    316          ++it) {
    317       // Another translation matches if it's a request for the same file,
    318       if (it->second.cache_key == entry->second.cache_key &&
    319           // and it's not this translation,
    320           it->first != entry->first &&
    321           // and it can be stored in the cache,
    322           TranslationMayBeCached(it) &&
    323           // and it's already gotten past this check and returned the miss.
    324           it->second.got_cache_reply &&
    325           it->second.got_nexe_fd) {
    326         return;
    327       }
    328     }
    329     ReturnMiss(entry);
    330     return;
    331   }
    332 
    333   if (!base::PostTaskAndReplyWithResult(
    334            BrowserThread::GetBlockingPool(),
    335            FROM_HERE,
    336            base::Bind(
    337                &PnaclHost::CopyBufferToFile, pt->nexe_fd, pt->nexe_read_buffer),
    338            base::Bind(&PnaclHost::OnBufferCopiedToTempFile,
    339                       weak_factory_.GetWeakPtr(),
    340                       entry->first))) {
    341     pt->callback.Run(base::kInvalidPlatformFileValue, false);
    342   }
    343 }
    344 
    345 //////////////////// GetNexeFd miss path
    346 // Return the temp fd to the renderer, reporting a miss.
    347 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) {
    348   // Return the fd
    349   PendingTranslation* pt = &entry->second;
    350   NexeFdCallback cb(pt->callback);
    351   if (pt->nexe_fd == base::kInvalidPlatformFileValue) {
    352     // Bad FD is unrecoverable, so clear out the entry
    353     pending_translations_.erase(entry);
    354   }
    355   cb.Run(pt->nexe_fd, false);
    356 }
    357 
    358 // On error, just return a null refptr.
    359 // static
    360 scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer(
    361     base::PlatformFile fd) {
    362   base::PlatformFileInfo info;
    363   scoped_refptr<net::DrainableIOBuffer> buffer;
    364   bool error = false;
    365   if (!base::GetPlatformFileInfo(fd, &info) ||
    366       info.size >= std::numeric_limits<int>::max()) {
    367     PLOG(ERROR) << "GetPlatformFileInfo failed";
    368     error = true;
    369   } else {
    370     buffer = new net::DrainableIOBuffer(
    371         new net::IOBuffer(static_cast<int>(info.size)), info.size);
    372     if (base::ReadPlatformFile(fd, 0, buffer->data(), buffer->size()) !=
    373         info.size) {
    374       PLOG(ERROR) << "CopyFileToBuffer file read failed";
    375       error = true;
    376     }
    377   }
    378   if (error) {
    379     buffer = NULL;
    380   }
    381   base::ClosePlatformFile(fd);
    382   return buffer;
    383 }
    384 
    385 // Called by the renderer in the miss path to report a finished translation
    386 void PnaclHost::TranslationFinished(int render_process_id,
    387                                     int pp_instance,
    388                                     bool success) {
    389   DCHECK(thread_checker_.CalledOnValidThread());
    390   if (cache_state_ != CacheReady)
    391     return;
    392   TranslationID id(render_process_id, pp_instance);
    393   PendingTranslationMap::iterator entry(pending_translations_.find(id));
    394   if (entry == pending_translations_.end()) {
    395     LOG(ERROR) << "TranslationFinished: TranslationID " << render_process_id
    396                << "," << pp_instance << " not found.";
    397     return;
    398   }
    399   bool store_nexe = true;
    400   // If this is a premature response (i.e. we haven't returned a temp file
    401   // yet) or if it's an unsuccessful translation, or if we are incognito,
    402   // don't store in the cache.
    403   // TODO(dschuff): use a separate in-memory cache for incognito
    404   // translations.
    405   if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply ||
    406       !success || !TranslationMayBeCached(entry)) {
    407     store_nexe = false;
    408   } else if (!base::PostTaskAndReplyWithResult(
    409                   BrowserThread::GetBlockingPool(),
    410                   FROM_HERE,
    411                   base::Bind(&PnaclHost::CopyFileToBuffer,
    412                              entry->second.nexe_fd),
    413                   base::Bind(&PnaclHost::StoreTranslatedNexe,
    414                              weak_factory_.GetWeakPtr(),
    415                              id))) {
    416     store_nexe = false;
    417   }
    418 
    419   if (!store_nexe) {
    420     // If store_nexe is true, the fd will be closed by CopyFileToBuffer.
    421     if (entry->second.got_nexe_fd) {
    422       BrowserThread::PostBlockingPoolTask(
    423           FROM_HERE,
    424           base::Bind(base::IgnoreResult(base::ClosePlatformFile),
    425                      entry->second.nexe_fd));
    426     }
    427     pending_translations_.erase(entry);
    428   }
    429 }
    430 
    431 // Store the translated nexe in the translation cache. Called back with the
    432 // TranslationID from the host and the result of CopyFileToBuffer.
    433 // (Bound callbacks must re-lookup the TranslationID because the translation
    434 // could be cancelled before they get called).
    435 void PnaclHost::StoreTranslatedNexe(
    436     TranslationID id,
    437     scoped_refptr<net::DrainableIOBuffer> buffer) {
    438   DCHECK(thread_checker_.CalledOnValidThread());
    439   if (cache_state_ != CacheReady)
    440     return;
    441   PendingTranslationMap::iterator it(pending_translations_.find(id));
    442   if (it == pending_translations_.end()) {
    443     LOG(ERROR) << "StoreTranslatedNexe: TranslationID " << id.first << ","
    444                << id.second << " not found.";
    445     return;
    446   }
    447 
    448   if (buffer.get() == NULL) {
    449     LOG(ERROR) << "Error reading translated nexe";
    450     return;
    451   }
    452   pending_backend_operations_++;
    453   disk_cache_->StoreNexe(it->second.cache_key,
    454                          buffer,
    455                          base::Bind(&PnaclHost::OnTranslatedNexeStored,
    456                                     weak_factory_.GetWeakPtr(),
    457                                     it->first));
    458 }
    459 
    460 // After we know the nexe has been stored, we can clean up, and unblock any
    461 // outstanding requests for the same file.
    462 // (Bound callbacks must re-lookup the TranslationID because the translation
    463 // could be cancelled before they get called).
    464 void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) {
    465   PendingTranslationMap::iterator entry(pending_translations_.find(id));
    466   pending_backend_operations_--;
    467   if (entry == pending_translations_.end()) {
    468     // If the renderer closed while we were storing the nexe, we land here.
    469     // Make sure we try to de-init.
    470     DeInitIfSafe();
    471     return;
    472   }
    473   std::string key(entry->second.cache_key);
    474   pending_translations_.erase(entry);
    475   RequeryMatchingTranslations(key);
    476 }
    477 
    478 // Check if any pending translations match |key|. If so, re-issue the cache
    479 // query. In the overlapped miss case, we expect a hit this time, but a miss
    480 // is also possible in case of an error.
    481 void PnaclHost::RequeryMatchingTranslations(const std::string& key) {
    482   // Check for outstanding misses to this same file
    483   for (PendingTranslationMap::iterator it = pending_translations_.begin();
    484        it != pending_translations_.end();
    485        ++it) {
    486     if (it->second.cache_key == key) {
    487       // Re-send the cache read request. This time we expect a hit, but if
    488       // something goes wrong, it will just handle it like a miss.
    489       it->second.got_cache_reply = false;
    490       pending_backend_operations_++;
    491       disk_cache_->GetNexe(key,
    492                            base::Bind(&PnaclHost::OnCacheQueryReturn,
    493                                       weak_factory_.GetWeakPtr(),
    494                                       it->first));
    495     }
    496   }
    497 }
    498 
    499 //////////////////// GetNexeFd hit path
    500 
    501 // static
    502 int PnaclHost::CopyBufferToFile(base::PlatformFile fd,
    503                                 scoped_refptr<net::DrainableIOBuffer> buffer) {
    504   int rv = base::WritePlatformFile(fd, 0, buffer->data(), buffer->size());
    505   if (rv == -1)
    506     PLOG(ERROR) << "CopyBufferToFile write error";
    507   return rv;
    508 }
    509 
    510 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id,
    511                                          int file_error) {
    512   DCHECK(thread_checker_.CalledOnValidThread());
    513   PendingTranslationMap::iterator entry(pending_translations_.find(id));
    514   if (entry == pending_translations_.end()) {
    515     return;
    516   }
    517   if (file_error == -1) {
    518     // Write error on the temp file. Request a new file and start over.
    519     BrowserThread::PostBlockingPoolTask(
    520         FROM_HERE,
    521         base::Bind(base::IgnoreResult(base::ClosePlatformFile),
    522                    entry->second.nexe_fd));
    523     entry->second.got_nexe_fd = false;
    524     CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn,
    525                                    weak_factory_.GetWeakPtr(),
    526                                    entry->first));
    527     return;
    528   }
    529   base::PlatformFile fd = entry->second.nexe_fd;
    530   entry->second.callback.Run(fd, true);
    531   BrowserThread::PostBlockingPoolTask(
    532       FROM_HERE, base::Bind(base::IgnoreResult(base::ClosePlatformFile), fd));
    533   pending_translations_.erase(entry);
    534 }
    535 
    536 ///////////////////
    537 
    538 void PnaclHost::RendererClosing(int render_process_id) {
    539   DCHECK(thread_checker_.CalledOnValidThread());
    540   if (cache_state_ != CacheReady)
    541     return;
    542   for (PendingTranslationMap::iterator it = pending_translations_.begin();
    543        it != pending_translations_.end();) {
    544     PendingTranslationMap::iterator to_erase(it++);
    545     if (to_erase->first.first == render_process_id) {
    546       // Clean up the open files.
    547       BrowserThread::PostBlockingPoolTask(
    548           FROM_HERE,
    549           base::Bind(base::IgnoreResult(base::ClosePlatformFile),
    550                      to_erase->second.nexe_fd));
    551       std::string key(to_erase->second.cache_key);
    552       bool may_be_cached = TranslationMayBeCached(to_erase);
    553       pending_translations_.erase(to_erase);
    554       // No translations will be waiting for entries that will not be stored.
    555       if (may_be_cached)
    556         RequeryMatchingTranslations(key);
    557     }
    558   }
    559   BrowserThread::PostTask(
    560       BrowserThread::IO,
    561       FROM_HERE,
    562       base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
    563 }
    564 
    565 ////////////////// Cache data removal
    566 void PnaclHost::ClearTranslationCacheEntriesBetween(
    567     base::Time initial_time,
    568     base::Time end_time,
    569     const base::Closure& callback) {
    570   DCHECK(thread_checker_.CalledOnValidThread());
    571   if (cache_state_ == CacheUninitialized) {
    572     Init();
    573   }
    574   if (cache_state_ == CacheInitializing) {
    575     // If the backend hasn't yet initialized, try the request again later.
    576     BrowserThread::PostDelayedTask(
    577         BrowserThread::IO,
    578         FROM_HERE,
    579         base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween,
    580                    weak_factory_.GetWeakPtr(),
    581                    initial_time,
    582                    end_time,
    583                    callback),
    584         base::TimeDelta::FromMilliseconds(
    585             kTranslationCacheInitializationDelayMs));
    586     return;
    587   }
    588   pending_backend_operations_++;
    589   int rv = disk_cache_->DoomEntriesBetween(
    590       initial_time,
    591       end_time,
    592       base::Bind(
    593           &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback));
    594   if (rv != net::ERR_IO_PENDING)
    595     OnEntriesDoomed(callback, rv);
    596 }
    597 
    598 void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) {
    599   DCHECK(thread_checker_.CalledOnValidThread());
    600   BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback);
    601   pending_backend_operations_--;
    602   // When clearing the cache, the UI is blocked on all the cache-clearing
    603   // operations, and freeing the backend actually blocks the IO thread. So
    604   // instead of calling DeInitIfSafe directly, post it for later.
    605   BrowserThread::PostTask(
    606       BrowserThread::IO,
    607       FROM_HERE,
    608       base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
    609 }
    610 
    611 // Destroying the cache backend causes it to post tasks to the cache thread to
    612 // flush to disk. Because PnaclHost is a singleton, it does not get destroyed
    613 // until all the browser threads have gone away and it's too late to post
    614 // anything (attempting to do so hangs shutdown).  So we make sure to destroy it
    615 // when we no longer have any outstanding operations that need it. These include
    616 // pending translations, cache clear requests, and requests to read or write
    617 // translated nexes.  We check when renderers close, when cache clear requests
    618 // finish, and when backend operations complete.
    619 
    620 // It is not safe to delete the backend while it is initializing, nor if it has
    621 // outstanding entry open requests; it is in theory safe to delete it with
    622 // outstanding read/write requests, but because that distinction is hidden
    623 // inside PnaclTranslationCache, we do not delete the backend if there are any
    624 // backend requests in flight.  As a last resort in the destructor, we just leak
    625 // the backend to avoid hanging shutdown.
    626 void PnaclHost::DeInitIfSafe() {
    627   DCHECK(pending_backend_operations_ >= 0);
    628   if (pending_translations_.empty() && pending_backend_operations_ <= 0) {
    629     cache_state_ = CacheUninitialized;
    630     disk_cache_.reset();
    631   }
    632 }
    633 
    634 }  // namespace pnacl
    635