coop_transaction.rs |
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. |
9992 |