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/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" 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 29 void CloseBaseFile(base::File auto_file_closer) { 30 } 31 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 50 FileProxy::FileProxy(scoped_ptr<base::File> file, 51 base::WeakPtr<pnacl::PnaclHost> host) 52 : file_(file.Pass()), 53 host_(host) { 54 } 55 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 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 73 PnaclHost::PnaclHost() 74 : pending_backend_operations_(0), 75 cache_state_(CacheUninitialized), 76 weak_factory_(this) {} 77 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 85 PnaclHost* PnaclHost::GetInstance() { 86 return Singleton<PnaclHost>::get(); 87 } 88 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 101 PnaclHost::PendingTranslation::~PendingTranslation() { 102 if (nexe_fd) 103 delete nexe_fd; 104 } 105 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 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 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 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. 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 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 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 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. 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). 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). 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. 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. 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 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 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). 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.get(), 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). 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. 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 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 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 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 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. 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