I searched using deepwiki how SQLite implements their busy handler. They
use a callback system with exponential backoff, where it stores the
callback in the pager and in the database. I confess I found this
slightly confusing, so I just implemented a simple exponential backoff
directly in the `Statement` struct. I imagine SQLite does this in a more
convoluted manner, as they do not have a concept of yielding as we do.
https://deepwiki.com/search/where-is-the-code-for-the-
busy_4a5ed006-4eed-479f-80c3-dd038832831b
I also fixed the rust bindings so that it yields when we return
`StepResult::IO`, instead of just blocking the async function. To
achieve this I implemented the `Stream` trait for `Rows` struct, which
unfortunately came with a slight change to the function signature of
`rows.next()` to `rows.try_next()`.
EDIT:
~test `test_multiple_connections_fuzz` timeouts because now it has the
busy handler "slowing" things down (this test generates a lot of busy
transactions), so it takes a lot longer for the test to run. Not sure if
it is acceptable for us to reduce the number of operations so the test
is shorter.~
EDIT:
Adjusted the API to be more in line with
https://www.sqlite.org/c3ref/busy_timeout.html.
Sets maximum total accumulated timeout. If the duration is None or Zero,
we unset the busy handler for this Connection.
This api defers slightly from SQLite as instead of sleeping for linear
amount of time specified by the user, we will sleep in phases until the
the total amount of time requested is reached. This means we first sleep
of 1ms, then if we still return busy, we sleep for 2 ms, and repeat
until a maximum of 100 ms per phase or we reached the total timeout.
Example:
1. Set duration to 5ms
2. Step through query -> returns Busy -> sleep/yield for 1 ms
3. Step through query -> returns Busy -> sleep/yield for 2 ms
4. Step through query -> returns Busy -> sleep/yield for 2 ms (totaling
5 ms of sleep)
5. Step through query -> returns Busy -> return Busy to user
This slight api change demonstrated a better throughtput in
`perf/throughput/turso` benchmark
```sh
cargo run -p write-throughput --release -- -t 2
Running write throughput benchmark with 2 threads, 100 batch size, 10 iterations, mode: Legacy
Database created at: write_throughput_test.db
Thread 1: 1000 inserts in 0.04s (23438.42 inserts/sec)
Thread 0: 1000 inserts in 0.08s (12385.64 inserts/sec)
=== BENCHMARK RESULTS ===
Total inserts: 2000
Total time: 0.08s
Overall throughput: 24762.60 inserts/sec
Threads: 2
Batch size: 100
Iterations per thread: 10
Database file exists: true
Database file size: 4096 bytes
```
Depends on #3102Closes#3067Closes#3074
1. commit state machine was assuming that begin_write_tx() cannot fail,
but it can fail if there is another tx that is not using BEGIN
CONCURRENT.
2. if a brand new non-CONCURRENT transaction attempts to start exclusive
transaction but fails with Busy, we must end the read pager read tx it
just started, because otherwise the next time it attempts to do
something it will panic with: `"cannot start a new read tx without
ending an existing one"`
Closes#3125
1. commit state machine was assuming that begin_write_tx() cannot
fail, but it can fail if there is another tx that is not using
BEGIN CONCURRENT.
2. if a brand new non-CONCURRENT transaction attempts to start
exclusive transaction but fails with Busy, we must end the read
pager read tx it just started, because otherwise the next time
it attempts to do something it will panic with:
"cannot start a new read tx without ending an existing one"
**test/fuzz: introduce fuzzoptions to tx isolation test**
this makes it significantly easier to tweak the tx isolation test
parameters, and also makes it much easier to run the MVCC version of the
test without manually tweaking code inline to make it work.
introduces default options for the non-mvcc and mvcc test variants.
---
**test/fuzz: improve error handling in tx isolation fuzz test**
- extract out common behavior for checking acceptable errors
- add functionality to check which errors require rolling back
a transaction
Closes#3118
this makes it significantly easier to tweak the tx isolation test parameters,
and also makes it much easier to run the MVCC version of the test without
manually tweaking code inline to make it work.
introduces default options for the non-mvcc and mvcc test variants.
based on #3110closes#3111closes#3113closes#3114
all discovered using `test_multiple_connections_fuzz_mvcc`, so no
separate tests. i can add regression unit/integration tests tomorrow
mvcc: properly remove mutations of rolled back tx
mvstore was not removing deletions made by a tx that rolled back.
deletions are removed by clearing the `end` mark from the row
version.
---
mvcc: properly clear tx states when mvcc tx rolls back
---
mvcc: don't double-rollback on write-write-conflict
handle_program_error() already rolls back if this error happens.
double rollback causes a crash.
Closes#3115
on the main branch, mvcc allows concurrent inserts from multiple
txns even without BEGIN CONCURRENT, and then always hangs whenever
one of the txns tries to commit.
this commit fixes that issue.
…e connection
With BEGIN CONCURRENT, we cannot use the same connection all the time
because there's no way for the transaction manager to know which
transaction belongs to what "session" -- they're all individual
statements executed in the context of the one connection.
Fixes#3093
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>
Closes#3094
With BEGIN CONCURRENT, we cannot use the same connection all the time
because there's no way for the transaction manager to know which
transaction belongs to what "session" -- they're all individual
statements executed in the context of the one connection.
Fixes#3093
This patch adds checksums to Turso DB. You may check the design here in
the [RFC](https://github.com/tursodatabase/turso/issues/2178).
1. We use reserved bytes (8 bytes) to store the checksums. On every IO
read, we verify that the checksum matches.
2. We use twox hash for checksums.
3. Checksum works only on 4K pages now. It's a small change to enable
for all other sizes, I will send another PR.
4. Right now, it's not possible to switch to different algorithm or turn
off altogether. That will be added in the future PRs.
5. Checksums can be enabled only for new dbs. For existing DBs, we will
disable it.
6. To add checksums for existing DBs, we need vacuum since it would
require rewrite of whole db.
Closes#2840