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