Index: chrome/browser/nacl_host/pnacl_host.cc |
diff --git a/chrome/browser/nacl_host/pnacl_host.cc b/chrome/browser/nacl_host/pnacl_host.cc |
index 924f67911feb24f2193f0b8f4f8aa28deb2f2de8..8230c60bf5500ce127b8921f9c17185c9dea6f0b 100644 |
--- a/chrome/browser/nacl_host/pnacl_host.cc |
+++ b/chrome/browser/nacl_host/pnacl_host.cc |
@@ -27,9 +27,16 @@ static const int kTranslationCacheInitializationDelayMs = 20; |
} |
PnaclHost::PnaclHost() |
- : cache_state_(CacheUninitialized), weak_factory_(this) {} |
- |
-PnaclHost::~PnaclHost() {} |
+ : pending_backend_operations_(0), |
+ cache_state_(CacheUninitialized), |
+ weak_factory_(this) {} |
+ |
+PnaclHost::~PnaclHost() { |
+ // When PnaclHost is destroyed, it's too late to post anything to the cache |
+ // thread (it will hang shutdown). So just leak the cache backend. |
+ pnacl::PnaclTranslationCache* cache = disk_cache_.release(); |
+ (void)cache; |
+} |
PnaclHost* PnaclHost::GetInstance() { return Singleton<PnaclHost>::get(); } |
@@ -96,9 +103,11 @@ void PnaclHost::Init() { |
return; |
disk_cache_.reset(new pnacl::PnaclTranslationCache()); |
cache_state_ = CacheInitializing; |
- disk_cache_->InitOnDisk( |
+ int rv = disk_cache_->InitOnDisk( |
cache_path, |
base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); |
+ if (rv != net::ERR_IO_PENDING) |
+ OnCacheInitialized(rv); |
} |
// Initialize using the in-memory backend, and manually set the temporary file |
@@ -108,8 +117,10 @@ void PnaclHost::InitForTest(base::FilePath temp_dir) { |
disk_cache_.reset(new pnacl::PnaclTranslationCache()); |
cache_state_ = CacheInitializing; |
temp_dir_ = temp_dir; |
- disk_cache_->InitInMemory( |
+ int rv = disk_cache_->InitInMemory( |
base::Bind(&PnaclHost::OnCacheInitialized, weak_factory_.GetWeakPtr())); |
+ if (rv != net::ERR_IO_PENDING) |
+ OnCacheInitialized(rv); |
} |
///////////////////////////////////////// Temp files |
@@ -216,6 +227,7 @@ void PnaclHost::GetNexeFd(int render_process_id, |
// request hits. |
void PnaclHost::SendCacheQueryAndTempFileRequest(const std::string& cache_key, |
const TranslationID& id) { |
+ pending_backend_operations_++; |
disk_cache_->GetNexe( |
cache_key, |
base::Bind( |
@@ -236,9 +248,11 @@ void PnaclHost::OnCacheQueryReturn( |
int net_error, |
scoped_refptr<net::DrainableIOBuffer> buffer) { |
DCHECK(thread_checker_.CalledOnValidThread()); |
+ pending_backend_operations_--; |
PendingTranslationMap::iterator entry(pending_translations_.find(id)); |
if (entry == pending_translations_.end()) { |
LOG(ERROR) << "OnCacheQueryReturn: id not found"; |
+ DeInitIfSafe(); |
return; |
} |
PendingTranslation* pt = &entry->second; |
@@ -433,7 +447,7 @@ void PnaclHost::StoreTranslatedNexe( |
LOG(ERROR) << "Error reading translated nexe"; |
return; |
} |
- |
+ pending_backend_operations_++; |
disk_cache_->StoreNexe(it->second.cache_key, |
buffer, |
base::Bind(&PnaclHost::OnTranslatedNexeStored, |
@@ -447,7 +461,11 @@ void PnaclHost::StoreTranslatedNexe( |
// could be cancelled before they get called). |
void PnaclHost::OnTranslatedNexeStored(const TranslationID& id, int net_error) { |
PendingTranslationMap::iterator entry(pending_translations_.find(id)); |
+ pending_backend_operations_--; |
if (entry == pending_translations_.end()) { |
+ // If the renderer closed while we were storing the nexe, we land here. |
+ // Make sure we try to de-init. |
+ DeInitIfSafe(); |
return; |
} |
std::string key(entry->second.cache_key); |
@@ -467,6 +485,7 @@ void PnaclHost::RequeryMatchingTranslations(const std::string& key) { |
// Re-send the cache read request. This time we expect a hit, but if |
// something goes wrong, it will just handle it like a miss. |
it->second.got_cache_reply = false; |
+ pending_backend_operations_++; |
disk_cache_->GetNexe(key, |
base::Bind(&PnaclHost::OnCacheQueryReturn, |
weak_factory_.GetWeakPtr(), |
@@ -535,12 +554,10 @@ void PnaclHost::RendererClosing(int render_process_id) { |
RequeryMatchingTranslations(key); |
} |
} |
- if (pending_translations_.empty()) { |
- cache_state_ = CacheUninitialized; |
- // Freeing the backend causes it to flush to disk, so do it when the |
- // last renderer closes rather than on destruction. |
- disk_cache_.reset(); |
- } |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr())); |
} |
////////////////// Cache data removal |
@@ -566,15 +583,48 @@ void PnaclHost::ClearTranslationCacheEntriesBetween( |
kTranslationCacheInitializationDelayMs)); |
return; |
} |
+ pending_backend_operations_++; |
int rv = disk_cache_->DoomEntriesBetween( |
initial_time, |
end_time, |
- base::Bind(&PnaclHost::OnEntriesDoomed, callback)); |
+ base::Bind( |
+ &PnaclHost::OnEntriesDoomed, weak_factory_.GetWeakPtr(), callback)); |
if (rv != net::ERR_IO_PENDING) |
OnEntriesDoomed(callback, rv); |
} |
-// static |
void PnaclHost::OnEntriesDoomed(const base::Closure& callback, int net_error) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, callback); |
+ pending_backend_operations_--; |
+ // When clearing the cache, the UI is blocked on all the cache-clearing |
+ // operations, and freeing the backend actually blocks the IO thread. So |
+ // instead of calling DeInitIfSafe directly, post it for later. |
+ BrowserThread::PostTask( |
+ BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind(&PnaclHost::DeInitIfSafe, weak_factory_.GetWeakPtr())); |
+} |
+ |
+// Destroying the cache backend causes it to post tasks to the cache thread to |
+// flush to disk. Because PnaclHost is a singleton, it does not get destroyed |
+// until all the browser threads have gone away and it's too late to post |
+// anything (attempting to do so hangs shutdown). So we make sure to destroy it |
+// when we no longer have any outstanding operations that need it. These include |
+// pending translations, cache clear requests, and requests to read or write |
+// translated nexes. We check when renderers close, when cache clear requests |
+// finish, and when backend operations complete. |
+ |
+// It is not safe to delete the backend while it is initializing, nor if it has |
+// outstanding entry open requests; it is in theory safe to delete it with |
+// outstanding read/write requests, but because that distinction is hidden |
+// inside PnaclTranslationCache, we do not delete the backend if there are any |
+// backend requests in flight. As a last resort in the destructor, we just leak |
+// the backend to avoid hanging shutdown. |
+void PnaclHost::DeInitIfSafe() { |
+ DCHECK(pending_backend_operations_ >= 0); |
+ if (pending_translations_.empty() && pending_backend_operations_ <= 0) { |
+ cache_state_ = CacheUninitialized; |
+ disk_cache_.reset(); |
+ } |
} |