From 8cd777888a40e987ad536ab68421068f5c06d83b Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Fri, 9 Feb 2018 08:11:38 +0100 Subject: Fix a rare crash with asm.js caching. --- dom/asmjscache/AsmJSCache.cpp | 336 +++++++++++++++++++++++++++++------------ dom/indexedDB/ActorsParent.cpp | 96 +++++++++--- 2 files changed, 316 insertions(+), 116 deletions(-) (limited to 'dom') diff --git a/dom/asmjscache/AsmJSCache.cpp b/dom/asmjscache/AsmJSCache.cpp index 4e1912f23..be54987a1 100644 --- a/dom/asmjscache/AsmJSCache.cpp +++ b/dom/asmjscache/AsmJSCache.cpp @@ -69,6 +69,8 @@ namespace asmjscache { namespace { +class ParentRunnable; + // Anything smaller should compile fast enough that caching will just add // overhead. static const size_t sMinCachedModuleLength = 10000; @@ -76,6 +78,10 @@ static const size_t sMinCachedModuleLength = 10000; // The number of characters to hash into the Metadata::Entry::mFastHash. static const unsigned sNumFastHashChars = 4096; +// Track all live parent actors. +typedef nsTArray ParentActorArray; +StaticAutoPtr sLiveParentActors; + nsresult WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata) { @@ -236,6 +242,94 @@ EvictEntries(nsIFile* aDirectory, const nsACString& aGroup, } } +/******************************************************************************* + * Client + ******************************************************************************/ + +class Client + : public quota::Client +{ + static Client* sInstance; + + bool mShutdownRequested; + +public: + Client(); + + static bool + IsShuttingDownOnBackgroundThread() + { + AssertIsOnBackgroundThread(); + + if (sInstance) { + return sInstance->IsShuttingDown(); + } + + return QuotaManager::IsShuttingDown(); + } + + static bool + IsShuttingDownOnNonBackgroundThread() + { + MOZ_ASSERT(!IsOnBackgroundThread()); + + return QuotaManager::IsShuttingDown(); + } + + bool + IsShuttingDown() const + { + AssertIsOnBackgroundThread(); + + return mShutdownRequested; + } + + NS_INLINE_DECL_REFCOUNTING(Client, override) + + virtual Type + GetType() override; + + virtual nsresult + InitOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const AtomicBool& aCanceled, + UsageInfo* aUsageInfo) override; + + virtual nsresult + GetUsageForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const AtomicBool& aCanceled, + UsageInfo* aUsageInfo) override; + + virtual void + OnOriginClearCompleted(PersistenceType aPersistenceType, + const nsACString& aOrigin) + override; + + virtual void + ReleaseIOThreadObjects() override; + + virtual void + AbortOperations(const nsACString& aOrigin) override; + + virtual void + AbortOperationsForProcess(ContentParentId aContentParentId) override; + + virtual void + StartIdleMaintenance() override; + + virtual void + StopIdleMaintenance() override; + + virtual void + ShutdownWorkThreads() override; + +private: + ~Client(); +}; + // FileDescriptorHolder owns a file descriptor and its memory mapping. // FileDescriptorHolder is derived by two runnable classes (that is, // (Parent|Child)Runnable. @@ -897,6 +991,13 @@ ParentRunnable::FinishOnOwningThread() FileDescriptorHolder::Finish(); mDirectoryLock = nullptr; + + MOZ_ASSERT(sLiveParentActors); + sLiveParentActors->RemoveElement(this); + + if (sLiveParentActors->IsEmpty()) { + sLiveParentActors = nullptr; + } } NS_IMETHODIMP @@ -1140,6 +1241,10 @@ AllocEntryParent(OpenMode aOpenMode, { AssertIsOnBackgroundThread(); + if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) { + return nullptr; + } + if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) { MOZ_ASSERT(false); return nullptr; @@ -1148,6 +1253,12 @@ AllocEntryParent(OpenMode aOpenMode, RefPtr runnable = new ParentRunnable(aPrincipalInfo, aOpenMode, aWriteParams); + if (!sLiveParentActors) { + sLiveParentActors = new ParentActorArray(); + } + + sLiveParentActors->AppendElement(runnable); + nsresult rv = NS_DispatchToMainThread(runnable); NS_ENSURE_SUCCESS(rv, nullptr); @@ -1712,128 +1823,163 @@ CloseEntryForWrite(size_t aSize, } } -class Client : public quota::Client +/******************************************************************************* + * Client + ******************************************************************************/ + +Client* Client::sInstance = nullptr; + +Client::Client() + : mShutdownRequested(false) { - ~Client() {} + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!sInstance, "We expect this to be a singleton!"); -public: - NS_IMETHOD_(MozExternalRefCountType) - AddRef() override; + sInstance = this; +} - NS_IMETHOD_(MozExternalRefCountType) - Release() override; +Client::~Client() +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!"); - virtual Type - GetType() override - { - return ASMJS; - } + sInstance = nullptr; +} - virtual nsresult - InitOrigin(PersistenceType aPersistenceType, - const nsACString& aGroup, - const nsACString& aOrigin, - const AtomicBool& aCanceled, - UsageInfo* aUsageInfo) override - { - if (!aUsageInfo) { - return NS_OK; - } - return GetUsageForOrigin(aPersistenceType, - aGroup, - aOrigin, - aCanceled, - aUsageInfo); - } +Client::Type +Client::GetType() +{ + return ASMJS; +} - virtual nsresult - GetUsageForOrigin(PersistenceType aPersistenceType, - const nsACString& aGroup, - const nsACString& aOrigin, - const AtomicBool& aCanceled, - UsageInfo* aUsageInfo) override - { - QuotaManager* qm = QuotaManager::Get(); - MOZ_ASSERT(qm, "We were being called by the QuotaManager"); +nsresult +Client::InitOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const AtomicBool& aCanceled, + UsageInfo* aUsageInfo) +{ + if (!aUsageInfo) { + return NS_OK; + } + return GetUsageForOrigin(aPersistenceType, + aGroup, + aOrigin, + aCanceled, + aUsageInfo); +} - nsCOMPtr directory; - nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin, - getter_AddRefs(directory)); - NS_ENSURE_SUCCESS(rv, rv); - MOZ_ASSERT(directory, "We're here because the origin directory exists"); +nsresult +Client::GetUsageForOrigin(PersistenceType aPersistenceType, + const nsACString& aGroup, + const nsACString& aOrigin, + const AtomicBool& aCanceled, + UsageInfo* aUsageInfo) +{ + QuotaManager* qm = QuotaManager::Get(); + MOZ_ASSERT(qm, "We were being called by the QuotaManager"); - rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME)); - NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr directory; + nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin, + getter_AddRefs(directory)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } - DebugOnly exists; - MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists); + MOZ_ASSERT(directory, "We're here because the origin directory exists"); - nsCOMPtr entries; - rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); - NS_ENSURE_SUCCESS(rv, rv); + rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } - bool hasMore; - while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && - hasMore && !aCanceled) { - nsCOMPtr entry; - rv = entries->GetNext(getter_AddRefs(entry)); - NS_ENSURE_SUCCESS(rv, rv); + DebugOnly exists; + MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists); - nsCOMPtr file = do_QueryInterface(entry); - NS_ENSURE_TRUE(file, NS_NOINTERFACE); + nsCOMPtr entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } - int64_t fileSize; - rv = file->GetFileSize(&fileSize); - NS_ENSURE_SUCCESS(rv, rv); + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && + hasMore && !aCanceled) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } - MOZ_ASSERT(fileSize >= 0, "Negative size?!"); + nsCOMPtr file = do_QueryInterface(entry); + if (NS_WARN_IF(!file)) { + return NS_NOINTERFACE; + } - // Since the client is not explicitly storing files, append to database - // usage which represents implicit storage allocation. - aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); + int64_t fileSize; + rv = file->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; + MOZ_ASSERT(fileSize >= 0, "Negative size?!"); + + // Since the client is not explicitly storing files, append to database + // usage which represents implicit storage allocation. + aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } - virtual void - OnOriginClearCompleted(PersistenceType aPersistenceType, - const nsACString& aOrigin) - override - { } + return NS_OK; +} - virtual void - ReleaseIOThreadObjects() override - { } +void +Client::OnOriginClearCompleted(PersistenceType aPersistenceType, + const nsACString& aOrigin) +{ +} - virtual void - AbortOperations(const nsACString& aOrigin) override - { } +void +Client::ReleaseIOThreadObjects() +{ +} - virtual void - AbortOperationsForProcess(ContentParentId aContentParentId) override - { } +void +Client::AbortOperations(const nsACString& aOrigin) +{ +} - virtual void - StartIdleMaintenance() override - { } +void +Client::AbortOperationsForProcess(ContentParentId aContentParentId) +{ +} - virtual void - StopIdleMaintenance() override - { } +void +Client::StartIdleMaintenance() +{ +} - virtual void - ShutdownWorkThreads() override - { } +void +Client::StopIdleMaintenance() +{ +} -private: - nsAutoRefCnt mRefCnt; - NS_DECL_OWNINGTHREAD -}; +void +Client::ShutdownWorkThreads() +{ + AssertIsOnBackgroundThread(); + + if (sLiveParentActors) { + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); -NS_IMPL_ADDREF(asmjscache::Client) -NS_IMPL_RELEASE(asmjscache::Client) + do { + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread)); + } while (!sLiveParentActors); + } +} quota::Client* CreateClient() diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index c0cb69149..9d4e72f2f 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -18185,11 +18185,25 @@ QuotaClient::ShutdownWorkThreads() mShutdownRequested = true; + // Shutdown maintenance thread pool (this spins the event loop until all + // threads are gone). This should release any maintenance related quota + // objects. if (mMaintenanceThreadPool) { mMaintenanceThreadPool->Shutdown(); mMaintenanceThreadPool = nullptr; } + // Let any runnables dispatched from dying maintenance threads to be + // processed. This should release any maintenance related directory locks. + if (mCurrentMaintenance) { + nsIThread* currentThread = NS_GetCurrentThread(); + MOZ_ASSERT(currentThread); + + do { + MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread)); + } while (!mCurrentMaintenance); + } + RefPtr connectionPool = gConnectionPool.get(); if (connectionPool) { connectionPool->Shutdown(); @@ -18409,7 +18423,8 @@ Maintenance::Start() AssertIsOnBackgroundThread(); MOZ_ASSERT(mState == State::Initial); - if (IsAborted()) { + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsAborted()) { return NS_ERROR_ABORT; } @@ -18433,7 +18448,8 @@ Maintenance::CreateIndexedDatabaseManager() MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager); - if (IsAborted()) { + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + IsAborted()) { return NS_ERROR_ABORT; } @@ -18458,7 +18474,8 @@ Maintenance::OpenDirectory() MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(QuotaManager::Get()); - if (IsAborted()) { + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsAborted()) { return NS_ERROR_ABORT; } @@ -18482,7 +18499,8 @@ Maintenance::DirectoryOpen() MOZ_ASSERT(mState == State::DirectoryOpenPending); MOZ_ASSERT(mDirectoryLock); - if (IsAborted()) { + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsAborted()) { return NS_ERROR_ABORT; } @@ -18512,7 +18530,8 @@ Maintenance::DirectoryWork() // We have to find all database files that match any persistence type and any // origin. We ignore anything out of the ordinary for now. - if (IsAborted()) { + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + IsAborted()) { return NS_ERROR_ABORT; } @@ -18699,8 +18718,10 @@ Maintenance::DirectoryWork() continue; } + nsCString suffix; nsCString group; nsCString origin; + bool isApp; nsTArray databasePaths; while (true) { @@ -18756,19 +18777,18 @@ Maintenance::DirectoryWork() // Found a database. if (databasePaths.IsEmpty()) { + MOZ_ASSERT(suffix.IsEmpty()); MOZ_ASSERT(group.IsEmpty()); MOZ_ASSERT(origin.IsEmpty()); int64_t dummyTimeStamp; - nsCString dummySuffix; - bool dummyIsApp; if (NS_WARN_IF(NS_FAILED( quotaManager->GetDirectoryMetadata2(originDir, &dummyTimeStamp, - dummySuffix, + suffix, group, origin, - &dummyIsApp)))) { + &isApp)))) { // Not much we can do here... continue; } @@ -18784,6 +18804,22 @@ Maintenance::DirectoryWork() group, origin, Move(databasePaths))); + + nsCOMPtr directory; + + // Idle maintenance may occur before origin is initailized. + // Ensure origin is initialized first. It will initialize all origins + // for temporary storage including IDB origins. + rv = quotaManager->EnsureOriginIsInitialized(persistenceType, + suffix, + group, + origin, + isApp, + getter_AddRefs(directory)); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } } } } @@ -18835,6 +18871,11 @@ Maintenance::BeginDatabaseMaintenance() } }; + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) || + IsAborted()) { + return NS_ERROR_ABORT; + } + RefPtr threadPool; for (DirectoryInfo& directoryInfo : mDirectoryInfos) { @@ -19021,6 +19062,11 @@ DatabaseMaintenance::PerformMaintenanceOnDatabase() } }; + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + mMaintenance->IsAborted()) { + return; + } + nsCOMPtr databaseFile = GetFileForPath(mDatabasePath); MOZ_ASSERT(databaseFile); @@ -19037,10 +19083,6 @@ DatabaseMaintenance::PerformMaintenanceOnDatabase() AutoClose autoClose(connection); - if (mMaintenance->IsAborted()) { - return; - } - AutoProgressHandler progressHandler(mMaintenance); if (NS_WARN_IF(NS_FAILED(progressHandler.Register(connection)))) { return; @@ -19059,20 +19101,12 @@ DatabaseMaintenance::PerformMaintenanceOnDatabase() return; } - if (mMaintenance->IsAborted()) { - return; - } - MaintenanceAction maintenanceAction; rv = DetermineMaintenanceAction(connection, databaseFile, &maintenanceAction); if (NS_WARN_IF(NS_FAILED(rv))) { return; } - if (mMaintenance->IsAborted()) { - return; - } - switch (maintenanceAction) { case MaintenanceAction::Nothing: break; @@ -19099,6 +19133,11 @@ DatabaseMaintenance::CheckIntegrity(mozIStorageConnection* aConnection, MOZ_ASSERT(aConnection); MOZ_ASSERT(aOk); + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + mMaintenance->IsAborted()) { + return NS_ERROR_ABORT; + } + nsresult rv; // First do a full integrity_check. Scope statements tightly here because @@ -19216,6 +19255,11 @@ DatabaseMaintenance::DetermineMaintenanceAction( MOZ_ASSERT(aDatabaseFile); MOZ_ASSERT(aMaintenanceAction); + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + mMaintenance->IsAborted()) { + return NS_ERROR_ABORT; + } + int32_t schemaVersion; nsresult rv = aConnection->GetSchemaVersion(&schemaVersion); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -19425,6 +19469,11 @@ DatabaseMaintenance::IncrementalVacuum(mozIStorageConnection* aConnection) MOZ_ASSERT(!IsOnBackgroundThread()); MOZ_ASSERT(aConnection); + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + mMaintenance->IsAborted()) { + return; + } + nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "PRAGMA incremental_vacuum;" )); @@ -19442,6 +19491,11 @@ DatabaseMaintenance::FullVacuum(mozIStorageConnection* aConnection, MOZ_ASSERT(aConnection); MOZ_ASSERT(aDatabaseFile); + if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) || + mMaintenance->IsAborted()) { + return; + } + nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "VACUUM;" )); -- cgit v1.2.3