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