@@ -306,37 +306,49 @@ impl Sql {
306
306
}
307
307
}
308
308
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
325
337
/// 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.
333
344
pub async fn write_lock ( & self ) -> MutexGuard < ' _ , ( ) > {
334
345
self . write_mtx . lock ( ) . await
335
346
}
336
347
337
348
/// 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.
340
352
///
341
353
/// Returns the result of the function.
342
354
async fn call < ' a , F , R > ( & ' a self , function : F ) -> Result < R >
0 commit comments