Skip to content

Commit 8302d22

Browse files
authored
Improve comment on write_lock() (#4134)
1 parent 034cde9 commit 8302d22

File tree

1 file changed

+37
-25
lines changed

1 file changed

+37
-25
lines changed

src/sql.rs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -306,37 +306,49 @@ impl Sql {
306306
}
307307
}
308308

309-
/// Locks the write transactions mutex.
310-
/// We do not make all transactions
311-
/// [IMMEDIATE](https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions)
312-
/// for more parallelism -- at least read transactions can be made DEFERRED to run in parallel
313-
/// w/o any drawbacks. But if we make write transactions DEFERRED also w/o any external locking,
314-
/// then they are upgraded from read to write ones on the first write statement. This has some
315-
/// drawbacks:
316-
/// - If there are other write transactions, we block the thread and the db connection until
317-
/// upgraded. Also if some reader comes then, it has to get next, less used connection with a
318-
/// worse per-connection page cache.
319-
/// - If a transaction is blocked for more than busy_timeout, it fails with SQLITE_BUSY.
320-
/// - Configuring busy_timeout is not the best way to manage transaction timeouts, we would
321-
/// prefer it to be integrated with Rust/tokio asyncs. Moreover, SQLite implements waiting
322-
/// using sleeps.
323-
/// - If upon a successful upgrade to a write transaction the db has been modified by another
324-
/// one, the transaction has to be rolled back and retried. It is an extra work in terms of
309+
/// Locks the write transactions mutex in order to make sure that there never are
310+
/// multiple write transactions at once.
311+
///
312+
/// Doing the locking ourselves instead of relying on SQLite has these reasons:
313+
///
314+
/// - SQLite's locking mechanism is non-async, blocking a thread
315+
/// - SQLite's locking mechanism just sleeps in a loop, which is really inefficient
316+
///
317+
/// ---
318+
///
319+
/// More considerations on alternatives to the current approach:
320+
///
321+
/// We use [DEFERRED](https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions) transactions.
322+
///
323+
/// In order to never get concurrency issues, we could make all transactions IMMEDIATE,
324+
/// but this would mean that there can never be two simultaneous transactions.
325+
///
326+
/// Read transactions can simply be made DEFERRED to run in parallel w/o any drawbacks.
327+
///
328+
/// DEFERRED write transactions without doing the locking ourselves would have these drawbacks:
329+
///
330+
/// 1. As mentioned above, SQLite's locking mechanism is non-async and sleeps in a loop.
331+
/// 2. If there are other write transactions, we block the db connection until
332+
/// upgraded. If some reader comes then, it has to get the next, less used connection with a
333+
/// worse per-connection page cache (SQLite allows one write and any number of reads in parallel).
334+
/// 3. If a transaction is blocked for more than `busy_timeout`, it fails with SQLITE_BUSY.
335+
/// 4. If upon a successful upgrade to a write transaction the db has been modified,
336+
/// the transaction has to be rolled back and retried, which means extra work in terms of
325337
/// CPU/battery.
326-
/// - Maybe minor, but we lose some fairness in servicing write transactions, i.e. we service
327-
/// them in the order of the first write statement, not in the order they come.
328-
/// The only pro of making write transactions DEFERRED w/o the external locking is some
329-
/// parallelism between them. Also we have an option to make write transactions IMMEDIATE, also
330-
/// w/o the external locking. But then the most of cons above are still valid. Instead, if we
331-
/// perform all write transactions under an async mutex, the only cons is losing some
332-
/// parallelism for write transactions.
338+
///
339+
/// The only pro of making write transactions DEFERRED w/o the external locking would be some
340+
/// parallelism between them.
341+
///
342+
/// Another option would be to make write transactions IMMEDIATE, also
343+
/// w/o the external locking. But then cons 1. - 3. above would still be valid.
333344
pub async fn write_lock(&self) -> MutexGuard<'_, ()> {
334345
self.write_mtx.lock().await
335346
}
336347

337348
/// Allocates a connection and calls `function` with the connection. If `function` does write
338-
/// queries, either a lock must be taken first using `write_lock()` or `call_write()` used
339-
/// instead.
349+
/// queries,
350+
/// - either first take a lock using `write_lock()`
351+
/// - or use `call_write()` instead.
340352
///
341353
/// Returns the result of the function.
342354
async fn call<'a, F, R>(&'a self, function: F) -> Result<R>

0 commit comments

Comments
 (0)