sqlite: Ensure that only one SQLiteBatch is writing to db at a time

A SQLiteBatch need to wait for any other batch to finish writing before
it can begin writing, otherwise db txn state may be incorrectly
modified. To enforce this, each SQLiteDatabase has a semaphore which
acts as a lock and is acquired by a batch when it begins a write, erase,
or a transaction, and is released by it when it is done.

To avoid deadlocking on itself for writing during a transaction,
SQLiteBatch also keeps track of whether it has begun a transaction.
This commit is contained in:
Ava Chow
2023-12-18 17:06:04 -05:00
parent 6f7395b3ff
commit 395bcd2454
2 changed files with 48 additions and 5 deletions

View File

@@ -58,6 +58,18 @@ private:
sqlite3_stmt* m_delete_stmt{nullptr};
sqlite3_stmt* m_delete_prefix_stmt{nullptr};
/** Whether this batch has started a database transaction and whether it owns SQLiteDatabase::m_write_semaphore.
* If the batch starts a db tx, it acquires the semaphore and sets this to true, keeping the semaphore
* until the transaction ends to prevent other batch objects from writing to the database.
*
* If this batch did not start a transaction, the semaphore is acquired transiently when writing and m_txn
* is not set.
*
* m_txn is different from HasActiveTxn() as it is only true when this batch has started the transaction,
* not just when any batch has started a transaction.
*/
bool m_txn{false};
void SetupSQLStatements();
bool ExecStatement(sqlite3_stmt* stmt, Span<const std::byte> blob);
@@ -115,6 +127,10 @@ public:
~SQLiteDatabase();
// Batches must acquire this semaphore on writing, and release when done writing.
// This ensures that only one batch is modifying the database at a time.
CSemaphore m_write_semaphore;
bool Verify(bilingual_str& error);
/** Open the database if it is not already opened */