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