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