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