| 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 |