Fix a rare crash with asm.js caching.

This commit is contained in:
wolfbeast 2018-02-09 08:11:38 +01:00 • committed by Roy Tam
commit 03fe589f41
2 changed files with 316 additions and 116 deletions

View file

@ -69,6 +69,8 @@ namespace asmjscache {
namespace { namespace {
class ParentRunnable;
// Anything smaller should compile fast enough that caching will just add // Anything smaller should compile fast enough that caching will just add
// overhead. // overhead.
static const size_t sMinCachedModuleLength = 10000; 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. // The number of characters to hash into the Metadata::Entry::mFastHash.
static const unsigned sNumFastHashChars = 4096; static const unsigned sNumFastHashChars = 4096;
// Track all live parent actors.
typedef nsTArray<const ParentRunnable*> ParentActorArray;
StaticAutoPtr<ParentActorArray> sLiveParentActors;
nsresult nsresult
WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata) 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 owns a file descriptor and its memory mapping.
// FileDescriptorHolder is derived by two runnable classes (that is, // FileDescriptorHolder is derived by two runnable classes (that is,
// (Parent|Child)Runnable. // (Parent|Child)Runnable.
@ -897,6 +991,13 @@ ParentRunnable::FinishOnOwningThread()
FileDescriptorHolder::Finish(); FileDescriptorHolder::Finish();
mDirectoryLock = nullptr; mDirectoryLock = nullptr;
MOZ_ASSERT(sLiveParentActors);
sLiveParentActors->RemoveElement(this);
if (sLiveParentActors->IsEmpty()) {
sLiveParentActors = nullptr;
}
} }
NS_IMETHODIMP NS_IMETHODIMP
@ -1140,6 +1241,10 @@ AllocEntryParent(OpenMode aOpenMode,
{ {
AssertIsOnBackgroundThread(); AssertIsOnBackgroundThread();
if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) {
return nullptr;
}
if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) { if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
MOZ_ASSERT(false); MOZ_ASSERT(false);
return nullptr; return nullptr;
@ -1148,6 +1253,12 @@ AllocEntryParent(OpenMode aOpenMode,
RefPtr<ParentRunnable> runnable = RefPtr<ParentRunnable> runnable =
new ParentRunnable(aPrincipalInfo, aOpenMode, aWriteParams); new ParentRunnable(aPrincipalInfo, aOpenMode, aWriteParams);
if (!sLiveParentActors) {
sLiveParentActors = new ParentActorArray();
}
sLiveParentActors->AppendElement(runnable);
nsresult rv = NS_DispatchToMainThread(runnable); nsresult rv = NS_DispatchToMainThread(runnable);
NS_ENSURE_SUCCESS(rv, nullptr); NS_ENSURE_SUCCESS(rv, nullptr);
@ -1712,29 +1823,41 @@ 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: sInstance = this;
NS_IMETHOD_(MozExternalRefCountType) }
AddRef() override;
NS_IMETHOD_(MozExternalRefCountType) Client::~Client()
Release() override; {
AssertIsOnBackgroundThread();
MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
virtual Type sInstance = nullptr;
GetType() override }
Client::Type
Client::GetType()
{ {
return ASMJS; return ASMJS;
} }
virtual nsresult nsresult
InitOrigin(PersistenceType aPersistenceType, Client::InitOrigin(PersistenceType aPersistenceType,
const nsACString& aGroup, const nsACString& aGroup,
const nsACString& aOrigin, const nsACString& aOrigin,
const AtomicBool& aCanceled, const AtomicBool& aCanceled,
UsageInfo* aUsageInfo) override UsageInfo* aUsageInfo)
{ {
if (!aUsageInfo) { if (!aUsageInfo) {
return NS_OK; return NS_OK;
@ -1746,12 +1869,12 @@ public:
aUsageInfo); aUsageInfo);
} }
virtual nsresult nsresult
GetUsageForOrigin(PersistenceType aPersistenceType, Client::GetUsageForOrigin(PersistenceType aPersistenceType,
const nsACString& aGroup, const nsACString& aGroup,
const nsACString& aOrigin, const nsACString& aOrigin,
const AtomicBool& aCanceled, const AtomicBool& aCanceled,
UsageInfo* aUsageInfo) override UsageInfo* aUsageInfo)
{ {
QuotaManager* qm = QuotaManager::Get(); QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm, "We were being called by the QuotaManager"); MOZ_ASSERT(qm, "We were being called by the QuotaManager");
@ -1759,32 +1882,45 @@ public:
nsCOMPtr<nsIFile> directory; nsCOMPtr<nsIFile> directory;
nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin, nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
getter_AddRefs(directory)); getter_AddRefs(directory));
NS_ENSURE_SUCCESS(rv, rv); if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(directory, "We're here because the origin directory exists"); MOZ_ASSERT(directory, "We're here because the origin directory exists");
rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME)); rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
NS_ENSURE_SUCCESS(rv, rv); if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
DebugOnly<bool> exists; DebugOnly<bool> exists;
MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists); MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
nsCOMPtr<nsISimpleEnumerator> entries; nsCOMPtr<nsISimpleEnumerator> entries;
rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
NS_ENSURE_SUCCESS(rv, rv); if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool hasMore; bool hasMore;
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
hasMore && !aCanceled) { hasMore && !aCanceled) {
nsCOMPtr<nsISupports> entry; nsCOMPtr<nsISupports> entry;
rv = entries->GetNext(getter_AddRefs(entry)); rv = entries->GetNext(getter_AddRefs(entry));
NS_ENSURE_SUCCESS(rv, rv); if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIFile> file = do_QueryInterface(entry); nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
NS_ENSURE_TRUE(file, NS_NOINTERFACE); if (NS_WARN_IF(!file)) {
return NS_NOINTERFACE;
}
int64_t fileSize; int64_t fileSize;
rv = file->GetFileSize(&fileSize); rv = file->GetFileSize(&fileSize);
NS_ENSURE_SUCCESS(rv, rv); if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(fileSize >= 0, "Negative size?!"); MOZ_ASSERT(fileSize >= 0, "Negative size?!");
@ -1792,48 +1928,58 @@ public:
// usage which represents implicit storage allocation. // usage which represents implicit storage allocation.
aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
} }
NS_ENSURE_SUCCESS(rv, rv); if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK; return NS_OK;
} }
virtual void void
OnOriginClearCompleted(PersistenceType aPersistenceType, Client::OnOriginClearCompleted(PersistenceType aPersistenceType,
const nsACString& aOrigin) const nsACString& aOrigin)
override {
{ } }
virtual void void
ReleaseIOThreadObjects() override Client::ReleaseIOThreadObjects()
{ } {
}
virtual void void
AbortOperations(const nsACString& aOrigin) override Client::AbortOperations(const nsACString& aOrigin)
{ } {
}
virtual void void
AbortOperationsForProcess(ContentParentId aContentParentId) override Client::AbortOperationsForProcess(ContentParentId aContentParentId)
{ } {
}
virtual void void
StartIdleMaintenance() override Client::StartIdleMaintenance()
{ } {
}
virtual void void
StopIdleMaintenance() override Client::StopIdleMaintenance()
{ } {
}
virtual void void
ShutdownWorkThreads() override Client::ShutdownWorkThreads()
{ } {
AssertIsOnBackgroundThread();
private: if (sLiveParentActors) {
nsAutoRefCnt mRefCnt; nsIThread* currentThread = NS_GetCurrentThread();
NS_DECL_OWNINGTHREAD MOZ_ASSERT(currentThread);
};
NS_IMPL_ADDREF(asmjscache::Client) do {
NS_IMPL_RELEASE(asmjscache::Client) MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
} while (!sLiveParentActors);
}
}
quota::Client* quota::Client*
CreateClient() CreateClient()

View file

@ -18185,11 +18185,25 @@ QuotaClient::ShutdownWorkThreads()
mShutdownRequested = true; 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) { if (mMaintenanceThreadPool) {
mMaintenanceThreadPool->Shutdown(); mMaintenanceThreadPool->Shutdown();
mMaintenanceThreadPool = nullptr; 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> connectionPool = gConnectionPool.get(); RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
if (connectionPool) { if (connectionPool) {
connectionPool->Shutdown(); connectionPool->Shutdown();
@ -18409,7 +18423,8 @@ Maintenance::Start()
AssertIsOnBackgroundThread(); AssertIsOnBackgroundThread();
MOZ_ASSERT(mState == State::Initial); MOZ_ASSERT(mState == State::Initial);
if (IsAborted()) { if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
IsAborted()) {
return NS_ERROR_ABORT; return NS_ERROR_ABORT;
} }
@ -18433,7 +18448,8 @@ Maintenance::CreateIndexedDatabaseManager()
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager); MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager);
if (IsAborted()) { if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
IsAborted()) {
return NS_ERROR_ABORT; return NS_ERROR_ABORT;
} }
@ -18458,7 +18474,8 @@ Maintenance::OpenDirectory()
MOZ_ASSERT(!mDirectoryLock); MOZ_ASSERT(!mDirectoryLock);
MOZ_ASSERT(QuotaManager::Get()); MOZ_ASSERT(QuotaManager::Get());
if (IsAborted()) { if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
IsAborted()) {
return NS_ERROR_ABORT; return NS_ERROR_ABORT;
} }
@ -18482,7 +18499,8 @@ Maintenance::DirectoryOpen()
MOZ_ASSERT(mState == State::DirectoryOpenPending); MOZ_ASSERT(mState == State::DirectoryOpenPending);
MOZ_ASSERT(mDirectoryLock); MOZ_ASSERT(mDirectoryLock);
if (IsAborted()) { if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
IsAborted()) {
return NS_ERROR_ABORT; return NS_ERROR_ABORT;
} }
@ -18512,7 +18530,8 @@ Maintenance::DirectoryWork()
// We have to find all database files that match any persistence type and any // We have to find all database files that match any persistence type and any
// origin. We ignore anything out of the ordinary for now. // origin. We ignore anything out of the ordinary for now.
if (IsAborted()) { if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
IsAborted()) {
return NS_ERROR_ABORT; return NS_ERROR_ABORT;
} }
@ -18699,8 +18718,10 @@ Maintenance::DirectoryWork()
continue; continue;
} }
nsCString suffix;
nsCString group; nsCString group;
nsCString origin; nsCString origin;
bool isApp;
nsTArray<nsString> databasePaths; nsTArray<nsString> databasePaths;
while (true) { while (true) {
@ -18756,19 +18777,18 @@ Maintenance::DirectoryWork()
// Found a database. // Found a database.
if (databasePaths.IsEmpty()) { if (databasePaths.IsEmpty()) {
MOZ_ASSERT(suffix.IsEmpty());
MOZ_ASSERT(group.IsEmpty()); MOZ_ASSERT(group.IsEmpty());
MOZ_ASSERT(origin.IsEmpty()); MOZ_ASSERT(origin.IsEmpty());
int64_t dummyTimeStamp; int64_t dummyTimeStamp;
nsCString dummySuffix;
bool dummyIsApp;
if (NS_WARN_IF(NS_FAILED( if (NS_WARN_IF(NS_FAILED(
quotaManager->GetDirectoryMetadata2(originDir, quotaManager->GetDirectoryMetadata2(originDir,
&dummyTimeStamp, &dummyTimeStamp,
dummySuffix, suffix,
group, group,
origin, origin,
&dummyIsApp)))) { &isApp)))) {
// Not much we can do here... // Not much we can do here...
continue; continue;
} }
@ -18784,6 +18804,22 @@ Maintenance::DirectoryWork()
group, group,
origin, origin,
Move(databasePaths))); Move(databasePaths)));
nsCOMPtr<nsIFile> 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<nsThreadPool> threadPool; RefPtr<nsThreadPool> threadPool;
for (DirectoryInfo& directoryInfo : mDirectoryInfos) { for (DirectoryInfo& directoryInfo : mDirectoryInfos) {
@ -19021,6 +19062,11 @@ DatabaseMaintenance::PerformMaintenanceOnDatabase()
} }
}; };
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
mMaintenance->IsAborted()) {
return;
}
nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath); nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath);
MOZ_ASSERT(databaseFile); MOZ_ASSERT(databaseFile);
@ -19037,10 +19083,6 @@ DatabaseMaintenance::PerformMaintenanceOnDatabase()
AutoClose autoClose(connection); AutoClose autoClose(connection);
if (mMaintenance->IsAborted()) {
return;
}
AutoProgressHandler progressHandler(mMaintenance); AutoProgressHandler progressHandler(mMaintenance);
if (NS_WARN_IF(NS_FAILED(progressHandler.Register(connection)))) { if (NS_WARN_IF(NS_FAILED(progressHandler.Register(connection)))) {
return; return;
@ -19059,20 +19101,12 @@ DatabaseMaintenance::PerformMaintenanceOnDatabase()
return; return;
} }
if (mMaintenance->IsAborted()) {
return;
}
MaintenanceAction maintenanceAction; MaintenanceAction maintenanceAction;
rv = DetermineMaintenanceAction(connection, databaseFile, &maintenanceAction); rv = DetermineMaintenanceAction(connection, databaseFile, &maintenanceAction);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
return; return;
} }
if (mMaintenance->IsAborted()) {
return;
}
switch (maintenanceAction) { switch (maintenanceAction) {
case MaintenanceAction::Nothing: case MaintenanceAction::Nothing:
break; break;
@ -19099,6 +19133,11 @@ DatabaseMaintenance::CheckIntegrity(mozIStorageConnection* aConnection,
MOZ_ASSERT(aConnection); MOZ_ASSERT(aConnection);
MOZ_ASSERT(aOk); MOZ_ASSERT(aOk);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
mMaintenance->IsAborted()) {
return NS_ERROR_ABORT;
}
nsresult rv; nsresult rv;
// First do a full integrity_check. Scope statements tightly here because // First do a full integrity_check. Scope statements tightly here because
@ -19216,6 +19255,11 @@ DatabaseMaintenance::DetermineMaintenanceAction(
MOZ_ASSERT(aDatabaseFile); MOZ_ASSERT(aDatabaseFile);
MOZ_ASSERT(aMaintenanceAction); MOZ_ASSERT(aMaintenanceAction);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
mMaintenance->IsAborted()) {
return NS_ERROR_ABORT;
}
int32_t schemaVersion; int32_t schemaVersion;
nsresult rv = aConnection->GetSchemaVersion(&schemaVersion); nsresult rv = aConnection->GetSchemaVersion(&schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
@ -19425,6 +19469,11 @@ DatabaseMaintenance::IncrementalVacuum(mozIStorageConnection* aConnection)
MOZ_ASSERT(!IsOnBackgroundThread()); MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(aConnection); MOZ_ASSERT(aConnection);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
mMaintenance->IsAborted()) {
return;
}
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"PRAGMA incremental_vacuum;" "PRAGMA incremental_vacuum;"
)); ));
@ -19442,6 +19491,11 @@ DatabaseMaintenance::FullVacuum(mozIStorageConnection* aConnection,
MOZ_ASSERT(aConnection); MOZ_ASSERT(aConnection);
MOZ_ASSERT(aDatabaseFile); MOZ_ASSERT(aDatabaseFile);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
mMaintenance->IsAborted()) {
return;
}
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"VACUUM;" "VACUUM;"
)); ));