Bug 1914599 - Fix a potential race between OpenDatabaseOp and GetDatabasesOp; r=dom-storage-reviewers,jari a=RyanVM

Differential Revision: https://phabricator.services.mozilla.com/D224463
This commit is contained in:
Jan Varga 2024-10-09 18:19:22 +00:00
parent e9fd0f9f25
commit 8bd20bdeea
3 changed files with 106 additions and 17 deletions

View File

@ -3002,10 +3002,15 @@ class FactoryOp
// Waiting to do/doing work on the "work thread". This involves waiting for
// the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
// different implementation) to do its work. Eventually the state will
// transition to SendingResults.
// different implementation) to do its work. If the VersionChangeOp is
// OpenDatabaseOp and it succeeded then the next state is
// DatabaseWorkVersionUpdate. Otherwise the next step is SendingResults.
DatabaseWorkVersionChange,
// Waiting to do/doing finalization work on the QuotaManager IO thread.
// Eventually the state will transition to SendingResults.
DatabaseWorkVersionUpdate,
// Waiting to send/sending results on the PBackground thread. Next step is
// Completed.
SendingResults,
@ -3127,6 +3132,8 @@ class FactoryOp
virtual nsresult DispatchToWorkThread() = 0;
virtual nsresult DoVersionUpdate() = 0;
// Should only be called by Run().
virtual void SendResults() = 0;
@ -3206,6 +3213,8 @@ class OpenDatabaseOp final : public FactoryRequestOp {
// cycles.
VersionChangeOp* mVersionChangeOp;
MoveOnlyFunction<void()> mCompleteCallback;
uint32_t mTelemetryId;
public:
@ -3250,6 +3259,8 @@ class OpenDatabaseOp final : public FactoryRequestOp {
nsresult DispatchToWorkThread() override;
nsresult DoVersionUpdate() override;
void SendResults() override;
static nsresult UpdateLocaleAwareIndex(mozIStorageConnection& aConnection,
@ -3321,6 +3332,8 @@ class DeleteDatabaseOp final : public FactoryRequestOp {
nsresult DispatchToWorkThread() override;
nsresult DoVersionUpdate() override;
void SendResults() override;
};
@ -3381,6 +3394,8 @@ class GetDatabasesOp final : public FactoryOp {
nsresult DispatchToWorkThread() override;
nsresult DoVersionUpdate() override;
void SendResults() override;
};
@ -10815,6 +10830,7 @@ void VersionChangeTransaction::UpdateMetadata(nsresult aResult) {
void VersionChangeTransaction::SendCompleteNotification(nsresult aResult) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(mOpenDatabaseOp);
MOZ_ASSERT(!mOpenDatabaseOp->mCompleteCallback);
MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->HasFailed());
MOZ_ASSERT_IF(!mActorWasAlive, mOpenDatabaseOp->mState >
OpenDatabaseOp::State::SendingResults);
@ -10825,21 +10841,40 @@ void VersionChangeTransaction::SendCompleteNotification(nsresult aResult) {
return;
}
openDatabaseOp->mCompleteCallback =
[self = SafeRefPtr{this, AcquireStrongRefFromRawPtr{}}, aResult]() {
if (!self->IsActorDestroyed()) {
Unused << self->SendComplete(aResult);
}
};
auto handleError = [openDatabaseOp](const nsresult rv) {
openDatabaseOp->SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
openDatabaseOp->mState = OpenDatabaseOp::State::SendingResults;
MOZ_ALWAYS_SUCCEEDS(openDatabaseOp->Run());
};
if (NS_FAILED(aResult)) {
// 3.3.1 Opening a database:
// "If the upgrade transaction was aborted, run the steps for closing a
// database connection with connection, create and return a new AbortError
// exception and abort these steps."
openDatabaseOp->SetFailureCodeIfUnset(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
handleError(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR);
return;
}
openDatabaseOp->mState = OpenDatabaseOp::State::SendingResults;
openDatabaseOp->mState = OpenDatabaseOp::State::DatabaseWorkVersionUpdate;
if (!IsActorDestroyed()) {
Unused << SendComplete(aResult);
}
QuotaManager* const quotaManager = QuotaManager::Get();
MOZ_ASSERT(quotaManager);
MOZ_ALWAYS_SUCCEEDS(openDatabaseOp->Run());
QM_TRY(MOZ_TO_RESULT(quotaManager->IOThread()->Dispatch(openDatabaseOp,
NS_DISPATCH_NORMAL))
.mapErr(
[](const auto) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; }),
QM_VOID, handleError);
}
void VersionChangeTransaction::ActorDestroy(ActorDestroyReason aWhy) {
@ -11652,7 +11687,20 @@ DatabaseFileManager::DatabaseFileManager(
mEnforcingQuota(aEnforcingQuota),
mIsInPrivateBrowsingMode(aIsInPrivateBrowsingMode) {}
uint64_t DatabaseFileManager::DatabaseVersion() const {
AssertIsOnIOThread();
return mDatabaseVersion;
}
void DatabaseFileManager::UpdateDatabaseVersion(uint64_t aDatabaseVersion) {
AssertIsOnIOThread();
mDatabaseVersion = aDatabaseVersion;
}
nsresult DatabaseFileManager::Init(nsIFile* aDirectory,
const uint64_t aDatabaseVersion,
mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
MOZ_ASSERT(aDirectory);
@ -11687,6 +11735,8 @@ nsresult DatabaseFileManager::Init(nsIFile* aDirectory,
mJournalDirectoryPath.init(std::move(path));
}
mDatabaseVersion = aDatabaseVersion;
QM_TRY_INSPECT(const auto& stmt,
MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
nsCOMPtr<mozIStorageStatement>, aConnection,
@ -15049,6 +15099,10 @@ FactoryOp::Run() {
QM_WARNONLY_TRY(MOZ_TO_RESULT(DispatchToWorkThread()), handleError);
break;
case State::DatabaseWorkVersionUpdate:
QM_WARNONLY_TRY(MOZ_TO_RESULT(DoVersionUpdate()), handleError);
break;
case State::SendingResults:
SendResults();
break;
@ -15294,7 +15348,8 @@ nsresult OpenDatabaseOp::DoDatabaseWork() {
NS_ERROR_DOM_INDEXEDDB_VERSION_ERR);
if (!fileManager->Initialized()) {
QM_TRY(MOZ_TO_RESULT(fileManager->Init(fmDirectory, *connection)));
QM_TRY(MOZ_TO_RESULT(fileManager->Init(
fmDirectory, mMetadata->mCommonMetadata.version(), *connection)));
idm->AddFileManager(fileManager.clonePtr());
}
@ -15832,12 +15887,40 @@ nsresult OpenDatabaseOp::SendUpgradeNeeded() {
return NS_OK;
}
nsresult OpenDatabaseOp::DoVersionUpdate() {
AssertIsOnIOThread();
MOZ_ASSERT(mState == State::DatabaseWorkVersionUpdate);
MOZ_ASSERT(!HasFailed());
AUTO_PROFILER_LABEL("OpenDatabaseOp::DoVersionUpdate", DOM);
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
!OperationMayProceed()) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
mFileManager->UpdateDatabaseVersion(mRequestedVersion);
mState = State::SendingResults;
QM_TRY(MOZ_TO_RESULT(
DispatchThisAfterProcessingCurrentEvent(mOwningEventTarget)));
return NS_OK;
}
void OpenDatabaseOp::SendResults() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::SendingResults);
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
MOZ_ASSERT_IF(!HasFailed(), !mVersionChangeTransaction);
if (mCompleteCallback) {
auto completeCallback = std::move(mCompleteCallback);
completeCallback();
}
DebugOnly<DatabaseActorInfo*> info = nullptr;
MOZ_ASSERT_IF(mDatabaseId.isSome() && gLiveDatabaseHashtable &&
gLiveDatabaseHashtable->Get(mDatabaseId.ref(), &info),
@ -15860,8 +15943,6 @@ void OpenDatabaseOp::SendResults() {
// need to update the version in our metadata.
mMetadata->mCommonMetadata.version() = mRequestedVersion;
mFileManager->UpdateDatabaseVersion(mRequestedVersion);
nsresult rv = EnsureDatabaseActorIsAlive();
if (NS_SUCCEEDED(rv)) {
// We successfully opened a database so use its actor as the success
@ -16483,6 +16564,10 @@ void DeleteDatabaseOp::SendBlockedNotification() {
}
}
nsresult DeleteDatabaseOp::DoVersionUpdate() {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
void DeleteDatabaseOp::SendResults() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::SendingResults);
@ -16853,6 +16938,10 @@ nsresult GetDatabasesOp::DispatchToWorkThread() {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
nsresult GetDatabasesOp::DoVersionUpdate() {
MOZ_CRASH("Not implemented because this should be unreachable.");
}
void GetDatabasesOp::SendResults() {
AssertIsOnOwningThread();
MOZ_ASSERT(mState == State::SendingResults);

View File

@ -89,11 +89,9 @@ class DatabaseFileManager final
const nsAString& DatabaseFilePath() const { return mDatabaseFilePath; }
uint64_t DatabaseVersion() const { return mDatabaseVersion; }
uint64_t DatabaseVersion() const;
void UpdateDatabaseVersion(uint64_t aDatabaseVersion) {
mDatabaseVersion = aDatabaseVersion;
}
void UpdateDatabaseVersion(uint64_t aDatabaseVersion);
IndexedDBCipherKeyManager& MutableCipherKeyManagerRef() const {
MOZ_ASSERT(mIsInPrivateBrowsingMode);
@ -108,7 +106,8 @@ class DatabaseFileManager final
bool Initialized() const { return mInitialized; }
nsresult Init(nsIFile* aDirectory, mozIStorageConnection& aConnection);
nsresult Init(nsIFile* aDirectory, const uint64_t aDatabaseVersion,
mozIStorageConnection& aConnection);
[[nodiscard]] nsCOMPtr<nsIFile> GetDirectory();

View File

@ -2866,7 +2866,8 @@ nsresult UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory,
/* aDatabaseFilePath */ u""_ns, /* aEnforcingQuota */ false,
/* aIsInPrivateBrowsingMode */ false);
nsresult rv = fileManager->Init(aFMDirectory, aConnection);
nsresult rv =
fileManager->Init(aFMDirectory, /* aDatabaseVersion */ 0, aConnection);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}