The way that we have configured SQLite to run means that only one database transaction can be open at a time. Typically, each individual read and write operation will be its own transaction that is opened and committed automatically by SQLite. However, sometimes we want these operations to be batched into a multi-statement transaction, so SQLiteBatch::TxnBegin
, SQLiteBatch::TxnCommit
, and SQLiteBatch::TxnAbort
are used to manage the transaction of the database.
However, once a db transaction is begun with one SQLiteBatch
, any operations performed by another SQLiteBatch
will also occur within the same transaction. Furthermore, those other SQLiteBatch
s will not be expecting a transaction to be active, and will abort it once the SQLiteBatch
is destructed. This is problematic as it will prevent some data from being written, and also cause the SQLiteBatch
that opened the transaction in the first place to be in an unexpected state and throw an error.
To avoid this situation, we need to prevent the multiple batches from writing at the same time. To do so, I’ve implemented added a CSemaphore
within SQLiteDatabase
which will be used by any SQLiteBatch
trying to do a write operation. wait()
is called by TxnBegin
, and at the beginning of WriteKey
, EraseKey
, and ErasePrefix
. post()
is called in TxnCommit
, TxnAbort
and at the end of WriteKey
, EraseKey
, and ErasePrefix
. To avoid deadlocking on TxnBegin()
followed by a WriteKey()
, `SQLiteBatch will now also track whether a transaction is in progress so that it knows whether to use the semaphore.
This issue is not a problem for BDB wallets since BDB uses WAL and provides transaction objects that must be used if an operation is to occur within a transaction. Specifically, we either pass a transaction pointer, or a nullptr, to all BDB operations, and this allows for concurrent transactions so it doesn’t have this problem.
Fixes #29110