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