• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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;
28 
CloseBaseFile(base::File auto_file_closer)29 void CloseBaseFile(base::File auto_file_closer) {
30 }
31 
CloseScopedFile(scoped_ptr<base::File> auto_file_closer)32 void CloseScopedFile(scoped_ptr<base::File> auto_file_closer) {
33 }
34 
35 }  // namespace
36 
37 namespace pnacl {
38 
39 class FileProxy {
40  public:
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);
44 
45  private:
46   scoped_ptr<base::File> file_;
47   base::WeakPtr<pnacl::PnaclHost> host_;
48 };
49 
FileProxy(scoped_ptr<base::File> file,base::WeakPtr<pnacl::PnaclHost> host)50 FileProxy::FileProxy(scoped_ptr<base::File> file,
51                      base::WeakPtr<pnacl::PnaclHost> host)
52     : file_(file.Pass()),
53       host_(host) {
54 }
55 
Write(scoped_refptr<net::DrainableIOBuffer> buffer)56 int FileProxy::Write(scoped_refptr<net::DrainableIOBuffer> buffer) {
57   int rv = file_->Write(0, buffer->data(), buffer->size());
58   if (rv == -1)
59     PLOG(ERROR) << "FileProxy::Write error";
60   return rv;
61 }
62 
WriteDone(const PnaclHost::TranslationID & id,int result)63 void FileProxy::WriteDone(const PnaclHost::TranslationID& id, int result) {
64   if (host_) {
65     host_->OnBufferCopiedToTempFile(id, file_.Pass(), result);
66   } else {
67     BrowserThread::PostBlockingPoolTask(
68         FROM_HERE,
69         base::Bind(CloseScopedFile, Passed(&file_)));
70   }
71 }
72 
PnaclHost()73 PnaclHost::PnaclHost()
74     : pending_backend_operations_(0),
75       cache_state_(CacheUninitialized),
76       weak_factory_(this) {}
77 
~PnaclHost()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();
82   (void)cache;
83 }
84 
GetInstance()85 PnaclHost* PnaclHost::GetInstance() {
86   return Singleton<PnaclHost>::get();
87 }
88 
PendingTranslation()89 PnaclHost::PendingTranslation::PendingTranslation()
90     : process_handle(base::kNullProcessHandle),
91       render_view_id(0),
92       nexe_fd(NULL),
93       got_nexe_fd(false),
94       got_cache_reply(false),
95       got_cache_hit(false),
96       is_incognito(false),
97       callback(NexeFdCallback()),
98       cache_info(nacl::PnaclCacheInfo()) {
99 }
100 
~PendingTranslation()101 PnaclHost::PendingTranslation::~PendingTranslation() {
102   if (nexe_fd)
103     delete nexe_fd;
104 }
105 
TranslationMayBeCached(const PendingTranslationMap::iterator & entry)106 bool PnaclHost::TranslationMayBeCached(
107     const PendingTranslationMap::iterator& entry) {
108   return !entry->second.is_incognito &&
109          !entry->second.cache_info.has_no_store_header;
110 }
111 
112 /////////////////////////////////////// Initialization
113 
GetCachePath()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();
124   }
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);
128 
129   // Append the base file name to the cache directory.
130   return cache_file_path.Append(kTranslationCacheDirectoryName);
131 }
132 
OnCacheInitialized(int net_error)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)
137     return;
138   if (net_error != net::OK) {
139     // This will cause the cache to attempt to re-init on the next call to
140     // GetNexeFd.
141     cache_state_ = CacheUninitialized;
142   } else {
143     cache_state_ = CacheReady;
144   }
145 }
146 
Init()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(BrowserThread::CurrentlyOn(BrowserThread::IO));
151   DCHECK(thread_checker_.CalledOnValidThread());
152   base::FilePath cache_path(GetCachePath());
153   if (cache_path.empty() || cache_state_ != CacheUninitialized)
154     return;
155   disk_cache_.reset(new pnacl::PnaclTranslationCache());
156   cache_state_ = CacheInitializing;
157   int rv = disk_cache_->InitOnDisk(
158       cache_path,
159       base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
160   if (rv != net::ERR_IO_PENDING)
161     OnCacheInitialized(rv);
162 }
163 
164 // Initialize for testing, optionally using the in-memory backend, and manually
165 // setting the temporary file directory instead of using the system directory.
InitForTest(base::FilePath temp_dir,bool in_memory)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;
171   int rv;
172   if (in_memory) {
173     rv = disk_cache_->InitInMemory(
174       base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
175   } else {
176     rv = disk_cache_->InitOnDisk(
177       temp_dir,
178       base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr()));
179   }
180   if (rv != net::ERR_IO_PENDING)
181     OnCacheInitialized(rv);
182 }
183 
184 ///////////////////////////////////////// Temp files
185 
186 // Create a temporary file on the blocking pool
187 // static
DoCreateTemporaryFile(base::FilePath temp_dir,TempFileCallback cb)188 void PnaclHost::DoCreateTemporaryFile(base::FilePath temp_dir,
189                                       TempFileCallback cb) {
190   DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
191 
192   base::FilePath file_path;
193   base::File file;
194   bool rv = temp_dir.empty()
195                 ? base::CreateTemporaryFile(&file_path)
196                 : base::CreateTemporaryFileInDir(temp_dir, &file_path);
197   if (!rv) {
198     PLOG(ERROR) << "Temp file creation failed.";
199   } else {
200     file.Initialize(
201         file_path,
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);
205 
206     if (!file.IsValid())
207       PLOG(ERROR) << "Temp file open failed: " << file.error_details();
208   }
209   BrowserThread::PostTask(
210       BrowserThread::IO, FROM_HERE, base::Bind(cb, Passed(file.Pass())));
211 }
212 
CreateTemporaryFile(TempFileCallback cb)213 void PnaclHost::CreateTemporaryFile(TempFileCallback cb) {
214   if (!BrowserThread::PostBlockingPoolSequencedTask(
215            "PnaclHostCreateTempFile",
216            FROM_HERE,
217            base::Bind(&PnaclHost::DoCreateTemporaryFile, temp_dir_, cb))) {
218     DCHECK(thread_checker_.CalledOnValidThread());
219     cb.Run(base::File());
220   }
221 }
222 
223 ///////////////////////////////////////// GetNexeFd implementation
224 ////////////////////// Common steps
225 
GetNexeFd(int render_process_id,int render_view_id,int pp_instance,bool is_incognito,const nacl::PnaclCacheInfo & cache_info,const NexeFdCallback & cb)226 void PnaclHost::GetNexeFd(int render_process_id,
227                           int render_view_id,
228                           int pp_instance,
229                           bool is_incognito,
230                           const nacl::PnaclCacheInfo& cache_info,
231                           const NexeFdCallback& cb) {
232   DCHECK(thread_checker_.CalledOnValidThread());
233   if (cache_state_ == CacheUninitialized) {
234     Init();
235   }
236   if (cache_state_ != CacheReady) {
237     // If the backend hasn't yet initialized, try the request again later.
238     BrowserThread::PostDelayedTask(BrowserThread::IO,
239                                    FROM_HERE,
240                                    base::Bind(&PnaclHost::GetNexeFd,
241                                               weak_factory_.GetWeakPtr(),
242                                               render_process_id,
243                                               render_view_id,
244                                               pp_instance,
245                                               is_incognito,
246                                               cache_info,
247                                               cb),
248                                    base::TimeDelta::FromMilliseconds(
249                                        kTranslationCacheInitializationDelayMs));
250     return;
251   }
252 
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);
259   }
260 
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);
265     return;
266   }
267 
268   PendingTranslation pt;
269   pt.render_view_id = render_view_id;
270   pt.callback = cb;
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);
276 }
277 
278 // Dispatch the cache read request and the temp file creation request
279 // simultaneously; currently we need a temp file regardless of whether the
280 // request hits.
SendCacheQueryAndTempFileRequest(const std::string & cache_key,const TranslationID & id)281 void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key,
282                                                  const TranslationID& id) {
283   pending_backend_operations_++;
284   disk_cache_->GetNexe(
285       cache_key,
286       base::Bind(
287           &PnaclHost::OnCacheQueryReturn, weak_factory_.GetWeakPtr(), id));
288 
289   CreateTemporaryFile(
290       base::Bind(&PnaclHost::OnTempFileReturn, weak_factory_.GetWeakPtr(), id));
291 }
292 
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).
OnCacheQueryReturn(const TranslationID & id,int net_error,scoped_refptr<net::DrainableIOBuffer> buffer)299 void PnaclHost::OnCacheQueryReturn(
300     const TranslationID& id,
301     int net_error,
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";
308     DeInitIfSafe();
309     return;
310   }
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);
317 }
318 
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).
OnTempFileReturn(const TranslationID & id,base::File file)324 void PnaclHost::OnTempFileReturn(const TranslationID& id,
325                                  base::File file) {
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())));
334     return;
335   }
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.
345     if (may_be_cached)
346       RequeryMatchingTranslations(key);
347     return;
348   }
349   PendingTranslation* pt = &entry->second;
350   pt->got_nexe_fd = true;
351   pt->nexe_fd = new base::File(file.Pass());
352   CheckCacheQueryReady(entry);
353 }
354 
355 // Check whether both the cache query and the temp file have returned, and check
356 // whether we actually got a hit or not.
CheckCacheQueryReady(const PendingTranslationMap::iterator & entry)357 void PnaclHost::CheckCacheQueryReady(
358     const PendingTranslationMap::iterator& entry) {
359   PendingTranslation* pt = &entry->second;
360   if (!(pt->got_cache_reply && pt->got_nexe_fd))
361     return;
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();
367          ++it) {
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) {
377         return;
378       }
379     }
380     ReturnMiss(entry);
381     return;
382   }
383 
384   scoped_ptr<base::File> file(pt->nexe_fd);
385   pt->nexe_fd = NULL;
386   pt->got_nexe_fd = false;
387   FileProxy* proxy(new FileProxy(file.Pass(), weak_factory_.GetWeakPtr()));
388 
389   if (!base::PostTaskAndReplyWithResult(
390            BrowserThread::GetBlockingPool(),
391            FROM_HERE,
392            base::Bind(&FileProxy::Write, base::Unretained(proxy),
393                       pt->nexe_read_buffer),
394            base::Bind(&FileProxy::WriteDone, base::Owned(proxy),
395                       entry->first))) {
396     pt->callback.Run(base::File(), false);
397   }
398 }
399 
400 //////////////////// GetNexeFd miss path
401 // Return the temp fd to the renderer, reporting a miss.
ReturnMiss(const PendingTranslationMap::iterator & entry)402 void PnaclHost::ReturnMiss(const PendingTranslationMap::iterator& entry) {
403   // Return the fd
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);
410   }
411 }
412 
413 // On error, just return a null refptr.
414 // static
CopyFileToBuffer(scoped_ptr<base::File> file)415 scoped_refptr<net::DrainableIOBuffer> PnaclHost::CopyFileToBuffer(
416     scoped_ptr<base::File> file) {
417   base::File::Info info;
418   scoped_refptr<net::DrainableIOBuffer> buffer;
419   bool error = false;
420   if (!file->GetInfo(&info) ||
421       info.size >= std::numeric_limits<int>::max()) {
422     PLOG(ERROR) << "File::GetInfo failed";
423     error = true;
424   } else {
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";
429       error = true;
430     }
431   }
432   if (error) {
433     buffer = NULL;
434   }
435   return buffer;
436 }
437 
438 // Called by the renderer in the miss path to report a finished translation
TranslationFinished(int render_process_id,int pp_instance,bool success)439 void PnaclHost::TranslationFinished(int render_process_id,
440                                     int pp_instance,
441                                     bool success) {
442   DCHECK(thread_checker_.CalledOnValidThread());
443   if (cache_state_ != CacheReady)
444     return;
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.";
450     return;
451   }
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
457   // translations.
458   if (!entry->second.got_nexe_fd || !entry->second.got_cache_reply ||
459       !success || !TranslationMayBeCached(entry)) {
460     store_nexe = false;
461   } else {
462     scoped_ptr<base::File> file(entry->second.nexe_fd);
463     entry->second.nexe_fd = NULL;
464     entry->second.got_nexe_fd = false;
465 
466     if (!base::PostTaskAndReplyWithResult(
467              BrowserThread::GetBlockingPool(),
468              FROM_HERE,
469              base::Bind(&PnaclHost::CopyFileToBuffer, Passed(&file)),
470              base::Bind(&PnaclHost::StoreTranslatedNexe,
471                         weak_factory_.GetWeakPtr(),
472                         id))) {
473       store_nexe = false;
474     }
475   }
476 
477   if (!store_nexe) {
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(
483           FROM_HERE,
484           base::Bind(CloseScopedFile, Passed(&file)));
485     }
486     pending_translations_.erase(entry);
487   }
488 }
489 
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).
StoreTranslatedNexe(TranslationID id,scoped_refptr<net::DrainableIOBuffer> buffer)494 void PnaclHost::StoreTranslatedNexe(
495     TranslationID id,
496     scoped_refptr<net::DrainableIOBuffer> buffer) {
497   DCHECK(thread_checker_.CalledOnValidThread());
498   if (cache_state_ != CacheReady)
499     return;
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.";
504     return;
505   }
506 
507   if (buffer.get() == NULL) {
508     LOG(ERROR) << "Error reading translated nexe";
509     return;
510   }
511   pending_backend_operations_++;
512   disk_cache_->StoreNexe(it->second.cache_key,
513                          buffer,
514                          base::Bind(&PnaclHost::OnTranslatedNexeStored,
515                                     weak_factory_.GetWeakPtr(),
516                                     it->first));
517 }
518 
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).
OnTranslatedNexeStored(const TranslationID & id,int net_error)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.
529     DeInitIfSafe();
530     return;
531   }
532   std::string key(entry->second.cache_key);
533   pending_translations_.erase(entry);
534   RequeryMatchingTranslations(key);
535 }
536 
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.
RequeryMatchingTranslations(const std::string & key)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();
544        ++it) {
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(),
553                                       it->first));
554     }
555   }
556 }
557 
558 //////////////////// GetNexeFd hit path
559 
OnBufferCopiedToTempFile(const TranslationID & id,scoped_ptr<base::File> file,int file_error)560 void PnaclHost::OnBufferCopiedToTempFile(const TranslationID& id,
561                                          scoped_ptr<base::File> file,
562                                          int file_error) {
563   DCHECK(thread_checker_.CalledOnValidThread());
564   PendingTranslationMap::iterator entry(pending_translations_.find(id));
565   if (entry == pending_translations_.end()) {
566     BrowserThread::PostBlockingPoolTask(
567         FROM_HERE,
568         base::Bind(CloseScopedFile, Passed(&file)));
569     return;
570   }
571   if (file_error == -1) {
572     // Write error on the temp file. Request a new file and start over.
573     BrowserThread::PostBlockingPoolTask(
574         FROM_HERE,
575         base::Bind(CloseScopedFile, Passed(&file)));
576     CreateTemporaryFile(base::Bind(&PnaclHost::OnTempFileReturn,
577                                    weak_factory_.GetWeakPtr(),
578                                    entry->first));
579     return;
580   }
581   entry->second.callback.Run(*file.get(), true);
582   BrowserThread::PostBlockingPoolTask(
583       FROM_HERE,
584       base::Bind(CloseScopedFile, Passed(&file)));
585   pending_translations_.erase(entry);
586 }
587 
588 ///////////////////
589 
RendererClosing(int render_process_id)590 void PnaclHost::RendererClosing(int render_process_id) {
591   DCHECK(thread_checker_.CalledOnValidThread());
592   if (cache_state_ != CacheReady)
593     return;
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(
602           FROM_HERE,
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.
608       if (may_be_cached)
609         RequeryMatchingTranslations(key);
610     }
611   }
612   BrowserThread::PostTask(
613       BrowserThread::IO,
614       FROM_HERE,
615       base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
616 }
617 
618 ////////////////// Cache data removal
ClearTranslationCacheEntriesBetween(base::Time initial_time,base::Time end_time,const base::Closure & callback)619 void PnaclHost::ClearTranslationCacheEntriesBetween(
620     base::Time initial_time,
621     base::Time end_time,
622     const base::Closure& callback) {
623   DCHECK(thread_checker_.CalledOnValidThread());
624   if (cache_state_ == CacheUninitialized) {
625     Init();
626   }
627   if (cache_state_ == CacheInitializing) {
628     // If the backend hasn't yet initialized, try the request again later.
629     BrowserThread::PostDelayedTask(
630         BrowserThread::IO,
631         FROM_HERE,
632         base::Bind(&PnaclHost::ClearTranslationCacheEntriesBetween,
633                    weak_factory_.GetWeakPtr(),
634                    initial_time,
635                    end_time,
636                    callback),
637         base::TimeDelta::FromMilliseconds(
638             kTranslationCacheInitializationDelayMs));
639     return;
640   }
641   pending_backend_operations_++;
642   int rv = disk_cache_->DoomEntriesBetween(
643       initial_time,
644       end_time,
645       base::Bind(
646           &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback));
647   if (rv != net::ERR_IO_PENDING)
648     OnEntriesDoomed(callback, rv);
649 }
650 
OnEntriesDoomed(const base::Closure & callback,int net_error)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(
659       BrowserThread::IO,
660       FROM_HERE,
661       base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr()));
662 }
663 
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.
672 
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.
DeInitIfSafe()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;
685     disk_cache_.reset();
686   }
687 }
688 
689 }  // namespace pnacl
690