[−][src]Module places::db::tx::coop_transaction
This implements "cooperative transactions" for places. It relies on our decision to have exactly 1 general purpose "writer" connection and exactly one "sync writer" - ie, exactly 2 write connections.
We'll describe the implementation and strategy, but note that most callers
should use PlacesDb::begin_transaction()
, which will do the right thing
for your db type.
The idea is that anything that uses the sync connection should use
chunked_coop_trransaction
. Code using this should regularly call
maybe_commit()
, and every second, will commit the transaction and start
a new one.
This means that in theory the other writable connection can start transactions and should block for a max of 1 second - well under the 5 seconds before that other writer will fail with a SQLITE_BUSY or similar error.
However, in practice we see the writer thread being starved - even though it's waiting for a lock, the sync thread still manages to re-get the lock. In other words, the locks used by sqlite aren't "fair".
So we mitigate this with a simple approach that works fine within our "exactly 2 writers" constraints:
- Each database connection shares a mutex.
- Before starting a transaction, each connection locks the mutex.
- It then starts an "immediate" transaction - because sqlite now holds a lock on our behalf, we release the lock on the mutex.
In other words, the lock is held only while obtaining the DB lock, then immediately released.
The end result here is that if either connection is waiting for the database lock because the other already holds it, the waiting one is guaranteed to get the database lock next.
One additional wrinkle here is that even if there was exactly one writer, there's still a possibility of SQLITE_BUSY if the database is being checkpointed. So we handle that case and perform exactly 1 retry.
Structs
ChunkedCoopTransaction | This transaction is suitable for when a transaction is used purely for performance reasons rather than for data-integrity reasons, or when it's used for integrity but held longer than strictly necessary for performance reasons (ie, when it could be multiple transactions and still guarantee integrity.) Examples of this might be for performance when updating a larger number of rows, but data integrity concerns could be addressed by using multiple, smaller transactions. |
Functions
get_tx_with_retry_on_locked |