mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 18:04:46 +00:00
Bug 1694231 - Add support for nested mozStorageTransaction using savepoints; r=dom-storage-reviewers,sg
The nesting level is tracked on the storage connection. The thread safety is ensured by holding a lock while a transaction is being started/commited/rolled back. For these purposes, the sharedDBMutex has been exposed on mozIStorageConnection interface and additional helper methods have been added to the interface as well. Differential Revision: https://phabricator.services.mozilla.com/D106019
This commit is contained in:
parent
1fe22b7cde
commit
117488daa2
19
dom/cache/Connection.cpp
vendored
19
dom/cache/Connection.cpp
vendored
@ -251,4 +251,23 @@ Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject,
|
||||
return mBase->GetQuotaObjects(aDatabaseQuotaObject, aJournalQuotaObject);
|
||||
}
|
||||
|
||||
mozilla::storage::SQLiteMutex& Connection::GetSharedDBMutex() {
|
||||
return mBase->GetSharedDBMutex();
|
||||
}
|
||||
|
||||
uint32_t Connection::GetTransactionNestingLevel(
|
||||
const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) {
|
||||
return mBase->GetTransactionNestingLevel(aProofOfLock);
|
||||
}
|
||||
|
||||
uint32_t Connection::IncreaseTransactionNestingLevel(
|
||||
const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) {
|
||||
return mBase->IncreaseTransactionNestingLevel(aProofOfLock);
|
||||
}
|
||||
|
||||
uint32_t Connection::DecreaseTransactionNestingLevel(
|
||||
const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) {
|
||||
return mBase->DecreaseTransactionNestingLevel(aProofOfLock);
|
||||
}
|
||||
|
||||
} // namespace mozilla::dom::cache
|
||||
|
@ -48,6 +48,7 @@ EXPORTS.mozilla.storage += [
|
||||
"mozStorageAsyncStatementParams.h",
|
||||
"mozStorageStatementParams.h",
|
||||
"mozStorageStatementRow.h",
|
||||
"SQLiteMutex.h",
|
||||
"StatementCache.h",
|
||||
"Variant.h",
|
||||
"Variant_inl.h",
|
||||
|
@ -7,17 +7,20 @@
|
||||
#include "mozIStorageAsyncConnection.idl"
|
||||
|
||||
%{C++
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace quota {
|
||||
namespace mozilla::dom::quota {
|
||||
class QuotaObject;
|
||||
}
|
||||
}
|
||||
|
||||
namespace mozilla::storage {
|
||||
class SQLiteMutex;
|
||||
class SQLiteMutexAutoLock;
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
[ptr] native QuotaObject(mozilla::dom::quota::QuotaObject);
|
||||
native SQLiteMutex(mozilla::storage::SQLiteMutex&);
|
||||
native SQLiteMutexAutoLock(const mozilla::storage::SQLiteMutexAutoLock&);
|
||||
|
||||
interface mozIStorageAggregateFunction;
|
||||
interface mozIStorageCompletionCallback;
|
||||
@ -41,7 +44,7 @@ interface nsIFile;
|
||||
*
|
||||
* @threadsafe
|
||||
*/
|
||||
[scriptable, uuid(4aa2ac47-8d24-4004-9b31-ec0bd85f0cc3)]
|
||||
[scriptable, builtinclass, uuid(4aa2ac47-8d24-4004-9b31-ec0bd85f0cc3)]
|
||||
interface mozIStorageConnection : mozIStorageAsyncConnection {
|
||||
/**
|
||||
* Closes a database connection. Callers must finalize all statements created
|
||||
@ -256,4 +259,25 @@ interface mozIStorageConnection : mozIStorageAsyncConnection {
|
||||
*/
|
||||
[noscript] void getQuotaObjects(out QuotaObject aDatabaseQuotaObject,
|
||||
out QuotaObject aJournalQuotaObject);
|
||||
|
||||
/**
|
||||
* The mutex used for protection of operations (BEGIN/COMMIT/ROLLBACK) in
|
||||
* mozStorageTransaction. The lock must be held in a way that spans whole
|
||||
* operation, not just when accessing the nesting level.
|
||||
*/
|
||||
[notxpcom, nostdcall] readonly attribute SQLiteMutex sharedDBMutex;
|
||||
|
||||
/**
|
||||
* Helper methods for managing the transaction nesting level. The methods
|
||||
* must be called with a proof of lock. Currently only used by
|
||||
* mozStorageTransaction.
|
||||
*/
|
||||
[notxpcom, nostdcall] unsigned long getTransactionNestingLevel(
|
||||
in SQLiteMutexAutoLock aProofOfLock);
|
||||
|
||||
[notxpcom, nostdcall] unsigned long increaseTransactionNestingLevel(
|
||||
in SQLiteMutexAutoLock aProofOfLock);
|
||||
|
||||
[notxpcom, nostdcall] unsigned long decreaseTransactionNestingLevel(
|
||||
in SQLiteMutexAutoLock aProofOfLock);
|
||||
};
|
||||
|
@ -444,7 +444,8 @@ Connection::Connection(Service* aService, int aFlags,
|
||||
mFlags(aFlags),
|
||||
mIgnoreLockingMode(aIgnoreLockingMode),
|
||||
mStorageService(aService),
|
||||
mSupportedOperations(aSupportedOperations) {
|
||||
mSupportedOperations(aSupportedOperations),
|
||||
mTransactionNestingLevel(0) {
|
||||
MOZ_ASSERT(!mIgnoreLockingMode || mFlags & SQLITE_OPEN_READONLY,
|
||||
"Can't ignore locking for a non-readonly connection!");
|
||||
mStorageService->registerConnection(this);
|
||||
@ -2344,4 +2345,21 @@ Connection::GetQuotaObjects(QuotaObject** aDatabaseQuotaObject,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
SQLiteMutex& Connection::GetSharedDBMutex() { return sharedDBMutex; }
|
||||
|
||||
uint32_t Connection::GetTransactionNestingLevel(
|
||||
const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) {
|
||||
return mTransactionNestingLevel;
|
||||
}
|
||||
|
||||
uint32_t Connection::IncreaseTransactionNestingLevel(
|
||||
const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) {
|
||||
return ++mTransactionNestingLevel;
|
||||
}
|
||||
|
||||
uint32_t Connection::DecreaseTransactionNestingLevel(
|
||||
const mozilla::storage::SQLiteMutexAutoLock& aProofOfLock) {
|
||||
return --mTransactionNestingLevel;
|
||||
}
|
||||
|
||||
} // namespace mozilla::storage
|
||||
|
@ -472,6 +472,8 @@ class Connection final : public mozIStorageConnection,
|
||||
const ConnectionOperation mSupportedOperations;
|
||||
|
||||
nsresult synchronousClose();
|
||||
|
||||
uint32_t mTransactionNestingLevel;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "nsString.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
#include "mozilla/storage/SQLiteMutex.h"
|
||||
#include "mozIStorageConnection.h"
|
||||
#include "mozIStorageStatement.h"
|
||||
#include "mozIStoragePendingStatement.h"
|
||||
@ -24,10 +25,11 @@
|
||||
* (rollback), then call Commit() on this object manually when your function
|
||||
* completes successfully.
|
||||
*
|
||||
* @note nested transactions are not supported by Sqlite, so if a transaction
|
||||
* is already in progress, this object does nothing. Note that in this case,
|
||||
* you may not get the transaction type you asked for, and you won't be able
|
||||
* to rollback.
|
||||
* @note nested transactions are not supported by Sqlite, only nested
|
||||
* savepoints, so if a transaction is already in progress, this object creates
|
||||
* a nested savepoint to the existing transaction which is considered as
|
||||
* anonymous savepoint itself. However, aType and aAsyncCommit are ignored
|
||||
* in the case of nested savepoints.
|
||||
*
|
||||
* @param aConnection
|
||||
* The connection to create the transaction on.
|
||||
@ -55,18 +57,31 @@
|
||||
* solution and avoided completely if possible.
|
||||
*/
|
||||
class mozStorageTransaction {
|
||||
using SQLiteMutexAutoLock = mozilla::storage::SQLiteMutexAutoLock;
|
||||
|
||||
public:
|
||||
mozStorageTransaction(
|
||||
mozIStorageConnection* aConnection, bool aCommitOnComplete,
|
||||
int32_t aType = mozIStorageConnection::TRANSACTION_DEFAULT,
|
||||
bool aAsyncCommit = false)
|
||||
: mConnection(aConnection),
|
||||
mNestingLevel(0),
|
||||
mHasTransaction(false),
|
||||
mCommitOnComplete(aCommitOnComplete),
|
||||
mCompleted(false),
|
||||
mAsyncCommit(aAsyncCommit) {
|
||||
if (mConnection) {
|
||||
nsAutoCString query("BEGIN");
|
||||
SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
|
||||
|
||||
// We nee to speculatively set the nesting level to be able to decide
|
||||
// if this is a top level transaction and to be able to generate the
|
||||
// savepoint name.
|
||||
TransactionStarted(lock);
|
||||
|
||||
nsAutoCString query;
|
||||
|
||||
if (TopLevelTransaction(lock)) {
|
||||
query.Assign("BEGIN");
|
||||
int32_t type = aType;
|
||||
if (type == mozIStorageConnection::TRANSACTION_DEFAULT) {
|
||||
MOZ_ALWAYS_SUCCEEDS(mConnection->GetDefaultTransactionType(&type));
|
||||
@ -84,9 +99,15 @@ class mozStorageTransaction {
|
||||
default:
|
||||
MOZ_ASSERT(false, "Unknown transaction type");
|
||||
}
|
||||
// If a transaction is already in progress, this will fail, since Sqlite
|
||||
// doesn't support nested transactions.
|
||||
mHasTransaction = NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(query));
|
||||
} else {
|
||||
query.Assign("SAVEPOINT sp"_ns + IntToCString(mNestingLevel));
|
||||
}
|
||||
|
||||
// If the query fails to execute we need to revert the speculatively set
|
||||
// nesting level on the connection.
|
||||
if (NS_FAILED(mConnection->ExecuteSimpleSQL(query))) {
|
||||
TransactionFinished(lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,11 +132,24 @@ class mozStorageTransaction {
|
||||
*/
|
||||
nsresult Commit() {
|
||||
if (!mConnection || mCompleted || !mHasTransaction) return NS_OK;
|
||||
|
||||
SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
|
||||
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
MOZ_DIAGNOSTIC_ASSERT(CurrentTransaction(lock));
|
||||
#else
|
||||
if (!CurrentTransaction(lock)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
#endif
|
||||
|
||||
mCompleted = true;
|
||||
|
||||
// TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't handle
|
||||
// it, thus the transaction might stay open until the next COMMIT.
|
||||
nsresult rv;
|
||||
|
||||
if (TopLevelTransaction(lock)) {
|
||||
// TODO (bug 559659): this might fail with SQLITE_BUSY, but we don't
|
||||
// handle it, thus the transaction might stay open until the next COMMIT.
|
||||
if (mAsyncCommit) {
|
||||
nsCOMPtr<mozIStoragePendingStatement> ps;
|
||||
rv = mConnection->ExecuteSimpleSQLAsync("COMMIT"_ns, nullptr,
|
||||
@ -123,8 +157,14 @@ class mozStorageTransaction {
|
||||
} else {
|
||||
rv = mConnection->ExecuteSimpleSQL("COMMIT"_ns);
|
||||
}
|
||||
} else {
|
||||
rv = mConnection->ExecuteSimpleSQL("RELEASE sp"_ns +
|
||||
IntToCString(mNestingLevel));
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv)) mHasTransaction = false;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
TransactionFinished(lock);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
@ -136,23 +176,79 @@ class mozStorageTransaction {
|
||||
*/
|
||||
nsresult Rollback() {
|
||||
if (!mConnection || mCompleted || !mHasTransaction) return NS_OK;
|
||||
|
||||
SQLiteMutexAutoLock lock(mConnection->GetSharedDBMutex());
|
||||
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
MOZ_DIAGNOSTIC_ASSERT(CurrentTransaction(lock));
|
||||
#else
|
||||
if (!CurrentTransaction(lock)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
#endif
|
||||
|
||||
mCompleted = true;
|
||||
|
||||
nsresult rv;
|
||||
|
||||
if (TopLevelTransaction(lock)) {
|
||||
// TODO (bug 1062823): from Sqlite 3.7.11 on, rollback won't ever return
|
||||
// a busy error, so this handling can be removed.
|
||||
nsresult rv;
|
||||
do {
|
||||
rv = mConnection->ExecuteSimpleSQL("ROLLBACK"_ns);
|
||||
if (rv == NS_ERROR_STORAGE_BUSY) (void)PR_Sleep(PR_INTERVAL_NO_WAIT);
|
||||
} while (rv == NS_ERROR_STORAGE_BUSY);
|
||||
} else {
|
||||
const auto nestingLevelCString = IntToCString(mNestingLevel);
|
||||
rv = mConnection->ExecuteSimpleSQL(
|
||||
"ROLLBACK TO sp"_ns + nestingLevelCString + "; RELEASE sp"_ns +
|
||||
nestingLevelCString);
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv)) mHasTransaction = false;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
TransactionFinished(lock);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
protected:
|
||||
void TransactionStarted(const SQLiteMutexAutoLock& aProofOfLock) {
|
||||
MOZ_ASSERT(mConnection);
|
||||
MOZ_ASSERT(!mHasTransaction);
|
||||
MOZ_ASSERT(mNestingLevel == 0);
|
||||
mHasTransaction = true;
|
||||
mNestingLevel = mConnection->IncreaseTransactionNestingLevel(aProofOfLock);
|
||||
}
|
||||
|
||||
bool CurrentTransaction(const SQLiteMutexAutoLock& aProofOfLock) const {
|
||||
MOZ_ASSERT(mConnection);
|
||||
MOZ_ASSERT(mHasTransaction);
|
||||
MOZ_ASSERT(mNestingLevel > 0);
|
||||
return mNestingLevel ==
|
||||
mConnection->GetTransactionNestingLevel(aProofOfLock);
|
||||
}
|
||||
|
||||
bool TopLevelTransaction(const SQLiteMutexAutoLock& aProofOfLock) const {
|
||||
MOZ_ASSERT(mConnection);
|
||||
MOZ_ASSERT(mHasTransaction);
|
||||
MOZ_ASSERT(mNestingLevel > 0);
|
||||
MOZ_ASSERT(CurrentTransaction(aProofOfLock));
|
||||
return mNestingLevel == 1;
|
||||
}
|
||||
|
||||
void TransactionFinished(const SQLiteMutexAutoLock& aProofOfLock) {
|
||||
MOZ_ASSERT(mConnection);
|
||||
MOZ_ASSERT(mHasTransaction);
|
||||
MOZ_ASSERT(mNestingLevel > 0);
|
||||
MOZ_ASSERT(CurrentTransaction(aProofOfLock));
|
||||
mConnection->DecreaseTransactionNestingLevel(aProofOfLock);
|
||||
mNestingLevel = 0;
|
||||
mHasTransaction = false;
|
||||
}
|
||||
|
||||
nsCOMPtr<mozIStorageConnection> mConnection;
|
||||
uint32_t mNestingLevel;
|
||||
bool mHasTransaction;
|
||||
bool mCommitOnComplete;
|
||||
bool mCompleted;
|
||||
|
@ -139,3 +139,38 @@ TEST(storage_transaction_helper, async_Commit)
|
||||
|
||||
blocking_async_close(db);
|
||||
}
|
||||
|
||||
TEST(storage_transaction_helper, Nesting)
|
||||
{
|
||||
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
|
||||
|
||||
{
|
||||
mozStorageTransaction transaction(db, false);
|
||||
do_check_true(has_transaction(db));
|
||||
do_check_success(
|
||||
db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns));
|
||||
|
||||
{
|
||||
mozStorageTransaction nestedTransaction(db, false);
|
||||
do_check_true(has_transaction(db));
|
||||
do_check_success(db->ExecuteSimpleSQL(
|
||||
"CREATE TABLE nested_test (id INTEGER PRIMARY KEY)"_ns));
|
||||
|
||||
#ifndef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
do_check_true(transaction.Commit() == NS_ERROR_NOT_AVAILABLE);
|
||||
do_check_true(transaction.Rollback() == NS_ERROR_NOT_AVAILABLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool exists = false;
|
||||
do_check_success(db->TableExists("nested_test"_ns, &exists));
|
||||
do_check_false(exists);
|
||||
|
||||
(void)transaction.Commit();
|
||||
}
|
||||
do_check_false(has_transaction(db));
|
||||
|
||||
bool exists = false;
|
||||
do_check_success(db->TableExists("test"_ns, &exists));
|
||||
do_check_true(exists);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user