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