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/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"
20 using content::BrowserThread
;
24 static const base::FilePath::CharType kTranslationCacheDirectoryName
[] =
25 FILE_PATH_LITERAL("PnaclTranslationCache");
26 // Delay to wait for initialization of the cache backend
27 static const int kTranslationCacheInitializationDelayMs
= 20;
29 void CloseBaseFile(base::File auto_file_closer
) {
32 void CloseScopedFile(scoped_ptr
<base::File
> auto_file_closer
) {
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
);
46 scoped_ptr
<base::File
> file_
;
47 base::WeakPtr
<pnacl::PnaclHost
> host_
;
50 FileProxy::FileProxy(scoped_ptr
<base::File
> file
,
51 base::WeakPtr
<pnacl::PnaclHost
> host
)
56 int FileProxy::Write(scoped_refptr
<net::DrainableIOBuffer
> buffer
) {
57 int rv
= file_
->Write(0, buffer
->data(), buffer
->size());
59 PLOG(ERROR
) << "FileProxy::Write error";
63 void FileProxy::WriteDone(const PnaclHost::TranslationID
& id
, int result
) {
65 host_
->OnBufferCopiedToTempFile(id
, file_
.Pass(), result
);
67 BrowserThread::PostBlockingPoolTask(
69 base::Bind(CloseScopedFile
, Passed(&file_
)));
73 PnaclHost::PnaclHost()
74 : pending_backend_operations_(0),
75 cache_state_(CacheUninitialized
),
76 weak_factory_(this) {}
78 PnaclHost::~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();
85 PnaclHost
* PnaclHost::GetInstance() {
86 return Singleton
<PnaclHost
>::get();
89 PnaclHost::PendingTranslation::PendingTranslation()
90 : process_handle(base::kNullProcessHandle
),
94 got_cache_reply(false),
97 callback(NexeFdCallback()),
98 cache_info(nacl::PnaclCacheInfo()) {
101 PnaclHost::PendingTranslation::~PendingTranslation() {
106 bool PnaclHost::TranslationMayBeCached(
107 const PendingTranslationMap::iterator
& entry
) {
108 return !entry
->second
.is_incognito
&&
109 !entry
->second
.cache_info
.has_no_store_header
;
112 /////////////////////////////////////// Initialization
114 static 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();
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
);
129 // Append the base file name to the cache directory.
130 return cache_file_path
.Append(kTranslationCacheDirectoryName
);
133 void 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
)
138 if (net_error
!= net::OK
) {
139 // This will cause the cache to attempt to re-init on the next call to
141 cache_state_
= CacheUninitialized
;
143 cache_state_
= CacheReady
;
147 void 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_CURRENTLY_ON(BrowserThread::IO
);
151 DCHECK(thread_checker_
.CalledOnValidThread());
152 base::FilePath
cache_path(GetCachePath());
153 if (cache_path
.empty() || cache_state_
!= CacheUninitialized
)
155 disk_cache_
.reset(new pnacl::PnaclTranslationCache());
156 cache_state_
= CacheInitializing
;
157 int rv
= disk_cache_
->InitOnDisk(
159 base::Bind(&PnaclHost::OnCacheInitialized
, weak_factory_
.GetWeakPtr()));
160 if (rv
!= net::ERR_IO_PENDING
)
161 OnCacheInitialized(rv
);
164 // Initialize for testing, optionally using the in-memory backend, and manually
165 // setting the temporary file directory instead of using the system directory.
166 void 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
;
173 rv
= disk_cache_
->InitInMemory(
174 base::Bind(&PnaclHost::OnCacheInitialized
, weak_factory_
.GetWeakPtr()));
176 rv
= disk_cache_
->InitOnDisk(
178 base::Bind(&PnaclHost::OnCacheInitialized
, weak_factory_
.GetWeakPtr()));
180 if (rv
!= net::ERR_IO_PENDING
)
181 OnCacheInitialized(rv
);
184 ///////////////////////////////////////// Temp files
186 // Create a temporary file on the blocking pool
188 void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir
,
189 TempFileCallback cb
) {
190 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
192 base::FilePath file_path
;
194 bool rv
= temp_dir
.empty()
195 ? base::CreateTemporaryFile(&file_path
)
196 : base::CreateTemporaryFileInDir(temp_dir
, &file_path
);
198 PLOG(ERROR
) << "Temp file creation failed.";
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
);
207 PLOG(ERROR
) << "Temp file open failed: " << file
.error_details();
209 BrowserThread::PostTask(
210 BrowserThread::IO
, FROM_HERE
, base::Bind(cb
, Passed(file
.Pass())));
213 void PnaclHost::CreateTemporaryFile(TempFileCallback cb
) {
214 if (!BrowserThread::PostBlockingPoolSequencedTask(
215 "PnaclHostCreateTempFile",
217 base::Bind(&PnaclHost::DoCreateTemporaryFile
, temp_dir_
, cb
))) {
218 DCHECK(thread_checker_
.CalledOnValidThread());
219 cb
.Run(base::File());
223 ///////////////////////////////////////// GetNexeFd implementation
224 ////////////////////// Common steps
226 void PnaclHost::GetNexeFd(int render_process_id
,
230 const nacl::PnaclCacheInfo
& cache_info
,
231 const NexeFdCallback
& cb
) {
232 DCHECK(thread_checker_
.CalledOnValidThread());
233 if (cache_state_
== CacheUninitialized
) {
236 if (cache_state_
!= CacheReady
) {
237 // If the backend hasn't yet initialized, try the request again later.
238 BrowserThread::PostDelayedTask(BrowserThread::IO
,
240 base::Bind(&PnaclHost::GetNexeFd
,
241 weak_factory_
.GetWeakPtr(),
248 base::TimeDelta::FromMilliseconds(
249 kTranslationCacheInitializationDelayMs
));
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
);
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);
268 PendingTranslation pt
;
269 pt
.render_view_id
= render_view_id
;
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
);
278 // Dispatch the cache read request and the temp file creation request
279 // simultaneously; currently we need a temp file regardless of whether the
281 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string
& cache_key
,
282 const TranslationID
& id
) {
283 pending_backend_operations_
++;
284 disk_cache_
->GetNexe(
287 &PnaclHost::OnCacheQueryReturn
, weak_factory_
.GetWeakPtr(), id
));
290 base::Bind(&PnaclHost::OnTempFileReturn
, weak_factory_
.GetWeakPtr(), id
));
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).
299 void PnaclHost::OnCacheQueryReturn(
300 const TranslationID
& id
,
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";
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
);
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).
324 void PnaclHost::OnTempFileReturn(const TranslationID
& id
,
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())));
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.
346 RequeryMatchingTranslations(key
);
349 PendingTranslation
* pt
= &entry
->second
;
350 pt
->got_nexe_fd
= true;
351 pt
->nexe_fd
= new base::File(file
.Pass());
352 CheckCacheQueryReady(entry
);
355 // Check whether both the cache query and the temp file have returned, and check
356 // whether we actually got a hit or not.
357 void PnaclHost::CheckCacheQueryReady(
358 const PendingTranslationMap::iterator
& entry
) {
359 PendingTranslation
* pt
= &entry
->second
;
360 if (!(pt
->got_cache_reply
&& pt
->got_nexe_fd
))
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();
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
) {
384 scoped_ptr
<base::File
> file(pt
->nexe_fd
);
386 pt
->got_nexe_fd
= false;
387 FileProxy
* proxy(new FileProxy(file
.Pass(), weak_factory_
.GetWeakPtr()));
389 if (!base::PostTaskAndReplyWithResult(
390 BrowserThread::GetBlockingPool(),
392 base::Bind(&FileProxy::Write
, base::Unretained(proxy
),
393 pt
->nexe_read_buffer
),
394 base::Bind(&FileProxy::WriteDone
, base::Owned(proxy
),
396 pt
->callback
.Run(base::File(), false);
400 //////////////////// GetNexeFd miss path
401 // Return the temp fd to the renderer, reporting a miss.
402 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator
& entry
) {
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
);
413 // On error, just return a null refptr.
415 scoped_refptr
<net::DrainableIOBuffer
> PnaclHost::CopyFileToBuffer(
416 scoped_ptr
<base::File
> file
) {
417 base::File::Info info
;
418 scoped_refptr
<net::DrainableIOBuffer
> buffer
;
420 if (!file
->GetInfo(&info
) ||
421 info
.size
>= std::numeric_limits
<int>::max()) {
422 PLOG(ERROR
) << "File::GetInfo failed";
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";
438 // Called by the renderer in the miss path to report a finished translation
439 void PnaclHost::TranslationFinished(int render_process_id
,
442 DCHECK(thread_checker_
.CalledOnValidThread());
443 if (cache_state_
!= CacheReady
)
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.";
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
458 if (!entry
->second
.got_nexe_fd
|| !entry
->second
.got_cache_reply
||
459 !success
|| !TranslationMayBeCached(entry
)) {
462 scoped_ptr
<base::File
> file(entry
->second
.nexe_fd
);
463 entry
->second
.nexe_fd
= NULL
;
464 entry
->second
.got_nexe_fd
= false;
466 if (!base::PostTaskAndReplyWithResult(
467 BrowserThread::GetBlockingPool(),
469 base::Bind(&PnaclHost::CopyFileToBuffer
, Passed(&file
)),
470 base::Bind(&PnaclHost::StoreTranslatedNexe
,
471 weak_factory_
.GetWeakPtr(),
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(
484 base::Bind(CloseScopedFile
, Passed(&file
)));
486 pending_translations_
.erase(entry
);
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).
494 void PnaclHost::StoreTranslatedNexe(
496 scoped_refptr
<net::DrainableIOBuffer
> buffer
) {
497 DCHECK(thread_checker_
.CalledOnValidThread());
498 if (cache_state_
!= CacheReady
)
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.";
507 if (buffer
.get() == NULL
) {
508 LOG(ERROR
) << "Error reading translated nexe";
511 pending_backend_operations_
++;
512 disk_cache_
->StoreNexe(it
->second
.cache_key
,
514 base::Bind(&PnaclHost::OnTranslatedNexeStored
,
515 weak_factory_
.GetWeakPtr(),
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).
523 void 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.
532 std::string
key(entry
->second
.cache_key
);
533 pending_translations_
.erase(entry
);
534 RequeryMatchingTranslations(key
);
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.
540 void 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();
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(),
558 //////////////////// GetNexeFd hit path
560 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID
& id
,
561 scoped_ptr
<base::File
> file
,
563 DCHECK(thread_checker_
.CalledOnValidThread());
564 PendingTranslationMap::iterator
entry(pending_translations_
.find(id
));
565 if (entry
== pending_translations_
.end()) {
566 BrowserThread::PostBlockingPoolTask(
568 base::Bind(CloseScopedFile
, Passed(&file
)));
571 if (file_error
== -1) {
572 // Write error on the temp file. Request a new file and start over.
573 BrowserThread::PostBlockingPoolTask(
575 base::Bind(CloseScopedFile
, Passed(&file
)));
576 CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn
,
577 weak_factory_
.GetWeakPtr(),
581 entry
->second
.callback
.Run(*file
.get(), true);
582 BrowserThread::PostBlockingPoolTask(
584 base::Bind(CloseScopedFile
, Passed(&file
)));
585 pending_translations_
.erase(entry
);
590 void PnaclHost::RendererClosing(int render_process_id
) {
591 DCHECK(thread_checker_
.CalledOnValidThread());
592 if (cache_state_
!= CacheReady
)
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(
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.
609 RequeryMatchingTranslations(key
);
612 BrowserThread::PostTask(
615 base::Bind(&PnaclHost::DeInitIfSafe
, weak_factory_
.GetWeakPtr()));
618 ////////////////// Cache data removal
619 void PnaclHost::ClearTranslationCacheEntriesBetween(
620 base::Time initial_time
,
622 const base::Closure
& callback
) {
623 DCHECK(thread_checker_
.CalledOnValidThread());
624 if (cache_state_
== CacheUninitialized
) {
627 if (cache_state_
== CacheInitializing
) {
628 // If the backend hasn't yet initialized, try the request again later.
629 BrowserThread::PostDelayedTask(
632 base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween
,
633 weak_factory_
.GetWeakPtr(),
637 base::TimeDelta::FromMilliseconds(
638 kTranslationCacheInitializationDelayMs
));
641 pending_backend_operations_
++;
642 int rv
= disk_cache_
->DoomEntriesBetween(
646 &PnaclHost::OnEntriesDoomed
, weak_factory_
.GetWeakPtr(), callback
));
647 if (rv
!= net::ERR_IO_PENDING
)
648 OnEntriesDoomed(callback
, rv
);
651 void 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(
661 base::Bind(&PnaclHost::DeInitIfSafe
, weak_factory_
.GetWeakPtr()));
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.
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.
679 void 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
;