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.
5 #include "components/nacl/browser/pnacl_host.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/numerics/safe_math.h"
13 #include "base/task_runner_util.h"
14 #include "base/threading/sequenced_worker_pool.h"
15 #include "components/nacl/browser/nacl_browser.h"
16 #include "components/nacl/browser/pnacl_translation_cache.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "net/base/io_buffer.h"
19 #include "net/base/net_errors.h"
21 using content::BrowserThread
;
25 static const base::FilePath::CharType kTranslationCacheDirectoryName
[] =
26 FILE_PATH_LITERAL("PnaclTranslationCache");
27 // Delay to wait for initialization of the cache backend
28 static const int kTranslationCacheInitializationDelayMs
= 20;
30 void CloseBaseFile(base::File auto_file_closer
) {
33 void CloseScopedFile(scoped_ptr
<base::File
> auto_file_closer
) {
42 FileProxy(scoped_ptr
<base::File
> file
, base::WeakPtr
<pnacl::PnaclHost
> host
);
43 int Write(scoped_refptr
<net::DrainableIOBuffer
> buffer
);
44 void WriteDone(const PnaclHost::TranslationID
& id
, int result
);
47 scoped_ptr
<base::File
> file_
;
48 base::WeakPtr
<pnacl::PnaclHost
> host_
;
51 FileProxy::FileProxy(scoped_ptr
<base::File
> file
,
52 base::WeakPtr
<pnacl::PnaclHost
> host
)
57 int FileProxy::Write(scoped_refptr
<net::DrainableIOBuffer
> buffer
) {
58 int rv
= file_
->Write(0, buffer
->data(), buffer
->size());
60 PLOG(ERROR
) << "FileProxy::Write error";
64 void FileProxy::WriteDone(const PnaclHost::TranslationID
& id
, int result
) {
66 host_
->OnBufferCopiedToTempFile(id
, file_
.Pass(), result
);
68 BrowserThread::PostBlockingPoolTask(
70 base::Bind(CloseScopedFile
, Passed(&file_
)));
74 PnaclHost::PnaclHost()
75 : pending_backend_operations_(0),
76 cache_state_(CacheUninitialized
),
77 weak_factory_(this) {}
79 PnaclHost::~PnaclHost() {
80 // When PnaclHost is destroyed, it's too late to post anything to the cache
81 // thread (it will hang shutdown). So just leak the cache backend.
82 pnacl::PnaclTranslationCache
* cache
= disk_cache_
.release();
86 PnaclHost
* PnaclHost::GetInstance() {
87 return base::Singleton
<PnaclHost
>::get();
90 PnaclHost::PendingTranslation::PendingTranslation()
91 : process_handle(base::kNullProcessHandle
),
95 got_cache_reply(false),
98 callback(NexeFdCallback()),
99 cache_info(nacl::PnaclCacheInfo()) {
102 PnaclHost::PendingTranslation::~PendingTranslation() {
107 bool PnaclHost::TranslationMayBeCached(
108 const PendingTranslationMap::iterator
& entry
) {
109 return !entry
->second
.is_incognito
&&
110 !entry
->second
.cache_info
.has_no_store_header
;
113 /////////////////////////////////////// Initialization
115 static base::FilePath
GetCachePath() {
116 NaClBrowserDelegate
* browser_delegate
= nacl::NaClBrowser::GetDelegate();
117 // Determine where the translation cache resides in the file system. It
118 // exists in Chrome's cache directory and is not tied to any specific
119 // profile. If we fail, return an empty path.
120 // Start by finding the user data directory.
121 base::FilePath user_data_dir
;
122 if (!browser_delegate
||
123 !browser_delegate
->GetUserDirectory(&user_data_dir
)) {
124 return base::FilePath();
126 // The cache directory may or may not be the user data directory.
127 base::FilePath cache_file_path
;
128 browser_delegate
->GetCacheDirectory(&cache_file_path
);
130 // Append the base file name to the cache directory.
131 return cache_file_path
.Append(kTranslationCacheDirectoryName
);
134 void PnaclHost::OnCacheInitialized(int net_error
) {
135 DCHECK(thread_checker_
.CalledOnValidThread());
136 // If the cache was cleared before the load completed, ignore.
137 if (cache_state_
== CacheReady
)
139 if (net_error
!= net::OK
) {
140 // This will cause the cache to attempt to re-init on the next call to
142 cache_state_
= CacheUninitialized
;
144 cache_state_
= CacheReady
;
148 void PnaclHost::Init() {
149 // Extra check that we're on the real IO thread since this version of
150 // Init isn't used in unit tests.
151 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
152 DCHECK(thread_checker_
.CalledOnValidThread());
153 base::FilePath
cache_path(GetCachePath());
154 if (cache_path
.empty() || cache_state_
!= CacheUninitialized
)
156 disk_cache_
.reset(new pnacl::PnaclTranslationCache());
157 cache_state_
= CacheInitializing
;
158 int rv
= disk_cache_
->InitOnDisk(
160 base::Bind(&PnaclHost::OnCacheInitialized
, weak_factory_
.GetWeakPtr()));
161 if (rv
!= net::ERR_IO_PENDING
)
162 OnCacheInitialized(rv
);
165 // Initialize for testing, optionally using the in-memory backend, and manually
166 // setting the temporary file directory instead of using the system directory.
167 void PnaclHost::InitForTest(base::FilePath temp_dir
, bool in_memory
) {
168 DCHECK(thread_checker_
.CalledOnValidThread());
169 disk_cache_
.reset(new pnacl::PnaclTranslationCache());
170 cache_state_
= CacheInitializing
;
171 temp_dir_
= temp_dir
;
174 rv
= disk_cache_
->InitInMemory(
175 base::Bind(&PnaclHost::OnCacheInitialized
, weak_factory_
.GetWeakPtr()));
177 rv
= disk_cache_
->InitOnDisk(
179 base::Bind(&PnaclHost::OnCacheInitialized
, weak_factory_
.GetWeakPtr()));
181 if (rv
!= net::ERR_IO_PENDING
)
182 OnCacheInitialized(rv
);
185 ///////////////////////////////////////// Temp files
187 // Create a temporary file on the blocking pool
189 void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir
,
190 TempFileCallback cb
) {
191 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
193 base::FilePath file_path
;
195 bool rv
= temp_dir
.empty()
196 ? base::CreateTemporaryFile(&file_path
)
197 : base::CreateTemporaryFileInDir(temp_dir
, &file_path
);
199 PLOG(ERROR
) << "Temp file creation failed.";
203 base::File::FLAG_CREATE_ALWAYS
| base::File::FLAG_READ
|
204 base::File::FLAG_WRITE
| base::File::FLAG_TEMPORARY
|
205 base::File::FLAG_DELETE_ON_CLOSE
);
208 PLOG(ERROR
) << "Temp file open failed: " << file
.error_details();
210 BrowserThread::PostTask(
211 BrowserThread::IO
, FROM_HERE
, base::Bind(cb
, Passed(file
.Pass())));
214 void PnaclHost::CreateTemporaryFile(TempFileCallback cb
) {
215 if (!BrowserThread::PostBlockingPoolSequencedTask(
216 "PnaclHostCreateTempFile",
218 base::Bind(&PnaclHost::DoCreateTemporaryFile
, temp_dir_
, cb
))) {
219 DCHECK(thread_checker_
.CalledOnValidThread());
220 cb
.Run(base::File());
224 ///////////////////////////////////////// GetNexeFd implementation
225 ////////////////////// Common steps
227 void PnaclHost::GetNexeFd(int render_process_id
,
231 const nacl::PnaclCacheInfo
& cache_info
,
232 const NexeFdCallback
& cb
) {
233 DCHECK(thread_checker_
.CalledOnValidThread());
234 if (cache_state_
== CacheUninitialized
) {
237 if (cache_state_
!= CacheReady
) {
238 // If the backend hasn't yet initialized, try the request again later.
239 BrowserThread::PostDelayedTask(BrowserThread::IO
,
241 base::Bind(&PnaclHost::GetNexeFd
,
242 weak_factory_
.GetWeakPtr(),
249 base::TimeDelta::FromMilliseconds(
250 kTranslationCacheInitializationDelayMs
));
254 TranslationID
id(render_process_id
, pp_instance
);
255 PendingTranslationMap::iterator entry
= pending_translations_
.find(id
);
256 if (entry
!= pending_translations_
.end()) {
257 // Existing translation must have been abandonded. Clean it up.
258 LOG(ERROR
) << "GetNexeFd for already-pending translation";
259 pending_translations_
.erase(entry
);
262 std::string
cache_key(disk_cache_
->GetKey(cache_info
));
263 if (cache_key
.empty()) {
264 LOG(ERROR
) << "GetNexeFd: Invalid cache info";
265 cb
.Run(base::File(), false);
269 PendingTranslation pt
;
270 pt
.render_view_id
= render_view_id
;
272 pt
.cache_info
= cache_info
;
273 pt
.cache_key
= cache_key
;
274 pt
.is_incognito
= is_incognito
;
275 pending_translations_
[id
] = pt
;
276 SendCacheQueryAndTempFileRequest(cache_key
, id
);
279 // Dispatch the cache read request and the temp file creation request
280 // simultaneously; currently we need a temp file regardless of whether the
282 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string
& cache_key
,
283 const TranslationID
& id
) {
284 pending_backend_operations_
++;
285 disk_cache_
->GetNexe(
288 &PnaclHost::OnCacheQueryReturn
, weak_factory_
.GetWeakPtr(), id
));
291 base::Bind(&PnaclHost::OnTempFileReturn
, weak_factory_
.GetWeakPtr(), id
));
294 // Callback from the translation cache query. |id| is bound from
295 // SendCacheQueryAndTempFileRequest, |net_error| is a net::Error code (which for
296 // our purposes means a hit if it's net::OK (i.e. 0). |buffer| is allocated
297 // by PnaclTranslationCache and now belongs to PnaclHost.
298 // (Bound callbacks must re-lookup the TranslationID because the translation
299 // could be cancelled before they get called).
300 void PnaclHost::OnCacheQueryReturn(
301 const TranslationID
& id
,
303 scoped_refptr
<net::DrainableIOBuffer
> buffer
) {
304 DCHECK(thread_checker_
.CalledOnValidThread());
305 pending_backend_operations_
--;
306 PendingTranslationMap::iterator
entry(pending_translations_
.find(id
));
307 if (entry
== pending_translations_
.end()) {
308 LOG(ERROR
) << "OnCacheQueryReturn: id not found";
312 PendingTranslation
* pt
= &entry
->second
;
313 pt
->got_cache_reply
= true;
314 pt
->got_cache_hit
= (net_error
== net::OK
);
315 if (pt
->got_cache_hit
)
316 pt
->nexe_read_buffer
= buffer
;
317 CheckCacheQueryReady(entry
);
320 // Callback from temp file creation. |id| is bound from
321 // SendCacheQueryAndTempFileRequest, and |file| is the created file.
322 // If there was an error, file is invalid.
323 // (Bound callbacks must re-lookup the TranslationID because the translation
324 // could be cancelled before they get called).
325 void PnaclHost::OnTempFileReturn(const TranslationID
& id
,
327 DCHECK(thread_checker_
.CalledOnValidThread());
328 PendingTranslationMap::iterator
entry(pending_translations_
.find(id
));
329 if (entry
== pending_translations_
.end()) {
330 // The renderer may have signaled an error or closed while the temp
331 // file was being created.
332 LOG(ERROR
) << "OnTempFileReturn: id not found";
333 BrowserThread::PostBlockingPoolTask(
334 FROM_HERE
, base::Bind(CloseBaseFile
, Passed(file
.Pass())));
337 if (!file
.IsValid()) {
338 // This translation will fail, but we need to retry any translation
339 // waiting for its result.
340 LOG(ERROR
) << "OnTempFileReturn: temp file creation failed";
341 std::string
key(entry
->second
.cache_key
);
342 entry
->second
.callback
.Run(base::File(), false);
343 bool may_be_cached
= TranslationMayBeCached(entry
);
344 pending_translations_
.erase(entry
);
345 // No translations will be waiting for entries that will not be stored.
347 RequeryMatchingTranslations(key
);
350 PendingTranslation
* pt
= &entry
->second
;
351 pt
->got_nexe_fd
= true;
352 pt
->nexe_fd
= new base::File(file
.Pass());
353 CheckCacheQueryReady(entry
);
356 // Check whether both the cache query and the temp file have returned, and check
357 // whether we actually got a hit or not.
358 void PnaclHost::CheckCacheQueryReady(
359 const PendingTranslationMap::iterator
& entry
) {
360 PendingTranslation
* pt
= &entry
->second
;
361 if (!(pt
->got_cache_reply
&& pt
->got_nexe_fd
))
363 if (!pt
->got_cache_hit
) {
364 // Check if there is already a pending translation for this file. If there
365 // is, we will wait for it to come back, to avoid redundant translations.
366 for (PendingTranslationMap::iterator it
= pending_translations_
.begin();
367 it
!= pending_translations_
.end();
369 // Another translation matches if it's a request for the same file,
370 if (it
->second
.cache_key
== entry
->second
.cache_key
&&
371 // and it's not this translation,
372 it
->first
!= entry
->first
&&
373 // and it can be stored in the cache,
374 TranslationMayBeCached(it
) &&
375 // and it's already gotten past this check and returned the miss.
376 it
->second
.got_cache_reply
&&
377 it
->second
.got_nexe_fd
) {
385 scoped_ptr
<base::File
> file(pt
->nexe_fd
);
387 pt
->got_nexe_fd
= false;
388 FileProxy
* proxy(new FileProxy(file
.Pass(), weak_factory_
.GetWeakPtr()));
390 if (!base::PostTaskAndReplyWithResult(
391 BrowserThread::GetBlockingPool(),
393 base::Bind(&FileProxy::Write
, base::Unretained(proxy
),
394 pt
->nexe_read_buffer
),
395 base::Bind(&FileProxy::WriteDone
, base::Owned(proxy
),
397 pt
->callback
.Run(base::File(), false);
401 //////////////////// GetNexeFd miss path
402 // Return the temp fd to the renderer, reporting a miss.
403 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator
& entry
) {
405 PendingTranslation
* pt
= &entry
->second
;
406 NexeFdCallback
cb(pt
->callback
);
407 cb
.Run(*pt
->nexe_fd
, false);
408 if (!pt
->nexe_fd
->IsValid()) {
409 // Bad FD is unrecoverable, so clear out the entry.
410 pending_translations_
.erase(entry
);
414 // On error, just return a null refptr.
416 scoped_refptr
<net::DrainableIOBuffer
> PnaclHost::CopyFileToBuffer(
417 scoped_ptr
<base::File
> file
) {
418 base::File::Info info
;
419 scoped_refptr
<net::DrainableIOBuffer
> buffer
;
422 // TODO(eroman): Maximum size should be changed to size_t once that is
423 // what IOBuffer requires. crbug.com/488553. Also I don't think the
424 // max size should be inclusive here...
425 if (!file
->GetInfo(&info
) ||
426 info
.size
>= std::numeric_limits
<int>::max()) {
427 PLOG(ERROR
) << "File::GetInfo failed";
430 buffer
= new net::DrainableIOBuffer(
431 new net::IOBuffer(base::CheckedNumeric
<size_t>(info
.size
).ValueOrDie()),
432 base::CheckedNumeric
<size_t>(info
.size
).ValueOrDie());
433 if (file
->Read(0, buffer
->data(), buffer
->size()) != info
.size
) {
434 PLOG(ERROR
) << "CopyFileToBuffer file read failed";
444 // Called by the renderer in the miss path to report a finished translation
445 void PnaclHost::TranslationFinished(int render_process_id
,
448 DCHECK(thread_checker_
.CalledOnValidThread());
449 if (cache_state_
!= CacheReady
)
451 TranslationID
id(render_process_id
, pp_instance
);
452 PendingTranslationMap::iterator
entry(pending_translations_
.find(id
));
453 if (entry
== pending_translations_
.end()) {
454 LOG(ERROR
) << "TranslationFinished: TranslationID " << render_process_id
455 << "," << pp_instance
<< " not found.";
458 bool store_nexe
= true;
459 // If this is a premature response (i.e. we haven't returned a temp file
460 // yet) or if it's an unsuccessful translation, or if we are incognito,
461 // don't store in the cache.
462 // TODO(dschuff): use a separate in-memory cache for incognito
464 if (!entry
->second
.got_nexe_fd
|| !entry
->second
.got_cache_reply
||
465 !success
|| !TranslationMayBeCached(entry
)) {
468 scoped_ptr
<base::File
> file(entry
->second
.nexe_fd
);
469 entry
->second
.nexe_fd
= NULL
;
470 entry
->second
.got_nexe_fd
= false;
472 if (!base::PostTaskAndReplyWithResult(
473 BrowserThread::GetBlockingPool(),
475 base::Bind(&PnaclHost::CopyFileToBuffer
, Passed(&file
)),
476 base::Bind(&PnaclHost::StoreTranslatedNexe
,
477 weak_factory_
.GetWeakPtr(),
484 // If store_nexe is true, the fd will be closed by CopyFileToBuffer.
485 if (entry
->second
.got_nexe_fd
) {
486 scoped_ptr
<base::File
> file(entry
->second
.nexe_fd
);
487 entry
->second
.nexe_fd
= NULL
;
488 BrowserThread::PostBlockingPoolTask(
490 base::Bind(CloseScopedFile
, Passed(&file
)));
492 pending_translations_
.erase(entry
);
496 // Store the translated nexe in the translation cache. Called back with the
497 // TranslationID from the host and the result of CopyFileToBuffer.
498 // (Bound callbacks must re-lookup the TranslationID because the translation
499 // could be cancelled before they get called).
500 void PnaclHost::StoreTranslatedNexe(
502 scoped_refptr
<net::DrainableIOBuffer
> buffer
) {
503 DCHECK(thread_checker_
.CalledOnValidThread());
504 if (cache_state_
!= CacheReady
)
506 PendingTranslationMap::iterator
it(pending_translations_
.find(id
));
507 if (it
== pending_translations_
.end()) {
508 LOG(ERROR
) << "StoreTranslatedNexe: TranslationID " << id
.first
<< ","
509 << id
.second
<< " not found.";
513 if (buffer
.get() == NULL
) {
514 LOG(ERROR
) << "Error reading translated nexe";
517 pending_backend_operations_
++;
518 disk_cache_
->StoreNexe(it
->second
.cache_key
,
520 base::Bind(&PnaclHost::OnTranslatedNexeStored
,
521 weak_factory_
.GetWeakPtr(),
525 // After we know the nexe has been stored, we can clean up, and unblock any
526 // outstanding requests for the same file.
527 // (Bound callbacks must re-lookup the TranslationID because the translation
528 // could be cancelled before they get called).
529 void PnaclHost::OnTranslatedNexeStored(const TranslationID
& id
, int net_error
) {
530 PendingTranslationMap::iterator
entry(pending_translations_
.find(id
));
531 pending_backend_operations_
--;
532 if (entry
== pending_translations_
.end()) {
533 // If the renderer closed while we were storing the nexe, we land here.
534 // Make sure we try to de-init.
538 std::string
key(entry
->second
.cache_key
);
539 pending_translations_
.erase(entry
);
540 RequeryMatchingTranslations(key
);
543 // Check if any pending translations match |key|. If so, re-issue the cache
544 // query. In the overlapped miss case, we expect a hit this time, but a miss
545 // is also possible in case of an error.
546 void PnaclHost::RequeryMatchingTranslations(const std::string
& key
) {
547 // Check for outstanding misses to this same file
548 for (PendingTranslationMap::iterator it
= pending_translations_
.begin();
549 it
!= pending_translations_
.end();
551 if (it
->second
.cache_key
== key
) {
552 // Re-send the cache read request. This time we expect a hit, but if
553 // something goes wrong, it will just handle it like a miss.
554 it
->second
.got_cache_reply
= false;
555 pending_backend_operations_
++;
556 disk_cache_
->GetNexe(key
,
557 base::Bind(&PnaclHost::OnCacheQueryReturn
,
558 weak_factory_
.GetWeakPtr(),
564 //////////////////// GetNexeFd hit path
566 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID
& id
,
567 scoped_ptr
<base::File
> file
,
569 DCHECK(thread_checker_
.CalledOnValidThread());
570 PendingTranslationMap::iterator
entry(pending_translations_
.find(id
));
571 if (entry
== pending_translations_
.end()) {
572 BrowserThread::PostBlockingPoolTask(
574 base::Bind(CloseScopedFile
, Passed(&file
)));
577 if (file_error
== -1) {
578 // Write error on the temp file. Request a new file and start over.
579 BrowserThread::PostBlockingPoolTask(
581 base::Bind(CloseScopedFile
, Passed(&file
)));
582 CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn
,
583 weak_factory_
.GetWeakPtr(),
587 entry
->second
.callback
.Run(*file
.get(), true);
588 BrowserThread::PostBlockingPoolTask(
590 base::Bind(CloseScopedFile
, Passed(&file
)));
591 pending_translations_
.erase(entry
);
596 void PnaclHost::RendererClosing(int render_process_id
) {
597 DCHECK(thread_checker_
.CalledOnValidThread());
598 if (cache_state_
!= CacheReady
)
600 for (PendingTranslationMap::iterator it
= pending_translations_
.begin();
601 it
!= pending_translations_
.end();) {
602 PendingTranslationMap::iterator
to_erase(it
++);
603 if (to_erase
->first
.first
== render_process_id
) {
604 // Clean up the open files.
605 scoped_ptr
<base::File
> file(to_erase
->second
.nexe_fd
);
606 to_erase
->second
.nexe_fd
= NULL
;
607 BrowserThread::PostBlockingPoolTask(
609 base::Bind(CloseScopedFile
, Passed(&file
)));
610 std::string
key(to_erase
->second
.cache_key
);
611 bool may_be_cached
= TranslationMayBeCached(to_erase
);
612 pending_translations_
.erase(to_erase
);
613 // No translations will be waiting for entries that will not be stored.
615 RequeryMatchingTranslations(key
);
618 BrowserThread::PostTask(
621 base::Bind(&PnaclHost::DeInitIfSafe
, weak_factory_
.GetWeakPtr()));
624 ////////////////// Cache data removal
625 void PnaclHost::ClearTranslationCacheEntriesBetween(
626 base::Time initial_time
,
628 const base::Closure
& callback
) {
629 DCHECK(thread_checker_
.CalledOnValidThread());
630 if (cache_state_
== CacheUninitialized
) {
633 if (cache_state_
== CacheInitializing
) {
634 // If the backend hasn't yet initialized, try the request again later.
635 BrowserThread::PostDelayedTask(
638 base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween
,
639 weak_factory_
.GetWeakPtr(),
643 base::TimeDelta::FromMilliseconds(
644 kTranslationCacheInitializationDelayMs
));
647 pending_backend_operations_
++;
648 int rv
= disk_cache_
->DoomEntriesBetween(
652 &PnaclHost::OnEntriesDoomed
, weak_factory_
.GetWeakPtr(), callback
));
653 if (rv
!= net::ERR_IO_PENDING
)
654 OnEntriesDoomed(callback
, rv
);
657 void PnaclHost::OnEntriesDoomed(const base::Closure
& callback
, int net_error
) {
658 DCHECK(thread_checker_
.CalledOnValidThread());
659 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
, callback
);
660 pending_backend_operations_
--;
661 // When clearing the cache, the UI is blocked on all the cache-clearing
662 // operations, and freeing the backend actually blocks the IO thread. So
663 // instead of calling DeInitIfSafe directly, post it for later.
664 BrowserThread::PostTask(
667 base::Bind(&PnaclHost::DeInitIfSafe
, weak_factory_
.GetWeakPtr()));
670 // Destroying the cache backend causes it to post tasks to the cache thread to
671 // flush to disk. Because PnaclHost is a singleton, it does not get destroyed
672 // until all the browser threads have gone away and it's too late to post
673 // anything (attempting to do so hangs shutdown). So we make sure to destroy it
674 // when we no longer have any outstanding operations that need it. These include
675 // pending translations, cache clear requests, and requests to read or write
676 // translated nexes. We check when renderers close, when cache clear requests
677 // finish, and when backend operations complete.
679 // It is not safe to delete the backend while it is initializing, nor if it has
680 // outstanding entry open requests; it is in theory safe to delete it with
681 // outstanding read/write requests, but because that distinction is hidden
682 // inside PnaclTranslationCache, we do not delete the backend if there are any
683 // backend requests in flight. As a last resort in the destructor, we just leak
684 // the backend to avoid hanging shutdown.
685 void PnaclHost::DeInitIfSafe() {
686 DCHECK(pending_backend_operations_
>= 0);
687 if (pending_translations_
.empty() &&
688 pending_backend_operations_
<= 0 &&
689 cache_state_
== CacheReady
) {
690 cache_state_
= CacheUninitialized
;