Commit Graph

1591 Commits

Author SHA1 Message Date
Pekka Enberg
1aad1b224a Merge 'core/io: Make random generation deterministically simulated' from Pedro Muniz
Depends on #3584 to use the most up-to-date implementation of
`ThreadRng`
- Add `fill_bytes` method to `IO`
- use `thread_rng` instead of `getrandom`, as `getrandom` is much slower
and `thread_rng` offers enough security
- modify `exec_randomblob`, `exec_random` and random_rowid generation to
use methods from IO for determinism
- modified simulator IO to implement `fill_bytes`
This the PRNG for sqlite if someone is curious. It is similar to
`thread_rng`:
```c
/* Initialize the state of the random number generator once,
  ** the first time this routine is called.
  */
  if( wsdPrng.s[0]==0 ){
    sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
    static const u32 chacha20_init[] = {
      0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
    };
    memcpy(&wsdPrng.s[0], chacha20_init, 16);
    if( NEVER(pVfs==0) ){
      memset(&wsdPrng.s[4], 0, 44);
    }else{
      sqlite3OsRandomness(pVfs, 44, (char*)&wsdPrng.s[4]);
    }
    wsdPrng.s[15] = wsdPrng.s[12];
    wsdPrng.s[12] = 0;
    wsdPrng.n = 0;
  }

  assert( N>0 );
  while( 1 /* exit by break */ ){
    if( N<=wsdPrng.n ){
      memcpy(zBuf, &wsdPrng.out[wsdPrng.n-N], N);
      wsdPrng.n -= N;
      break;
    }
    if( wsdPrng.n>0 ){
      memcpy(zBuf, wsdPrng.out, wsdPrng.n);
      N -= wsdPrng.n;
      zBuf += wsdPrng.n;
    }
    wsdPrng.s[12]++;
    chacha_block((u32*)wsdPrng.out, wsdPrng.s);
    wsdPrng.n = 64;
  }
  sqlite3_mutex_leave(mutex);
```

Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>

Closes #3799
2025-10-22 09:10:36 +03:00
Pere Diaz Bou
3227caaa1d Merge 'core: move BTreeCursor under MVCC cursor' from Pere Diaz Bou
Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3756
2025-10-21 19:20:49 +02:00
pedrocarlo
8501bc930a use workspace rand version 2025-10-21 14:10:05 -03:00
Pere Diaz Bou
ea04e9033a core/mvcc: add btree_cursor under MVCC cursor 2025-10-21 18:22:37 +02:00
Nikita Sivukhin
00e382a7c7 avoid unnecessary allocations 2025-10-21 20:13:39 +04:00
Pekka Enberg
f764f3061d Merge 'Add Miri support for turso_stress, with bash scripts to run' from Bob Peterson
It was mentioned in https://github.com/tursodatabase/turso/pull/3720
that adding Miri support for `turso_stress` would be useful. And, that a
bash script to start Miri with the right config would be a big help.
Notable changes:
- `antithesis_sdk`'s default features are disabled at the workspace
level, and only enabled as needed with the `antithesis` feature flag in
the various turso crates. Miri needs the noop version of
`antithesis_sdk` to run `turso_stress`, and feature unification
previously prevented this. I'm not able to ensure locally that all the
Antithesis stuff is still happy with these changes.
- Bash script to run `turso_stress` - this is barebones for now, see
below
- Bash script to run `simulator` - this passes any args to the `cargo
run` invocation inside, intercepting `--seed` if it's present, and
generating one from `/dev/random` if it's not. The seed is passed to
both Miri and the simulator to keep the overall execution reproducible.
(I checked this with a simple case)
- A `const fn`, `normal_or_miri` to supply different defaults in things
like CLI args for normal operation and Miri, since it's so slow. (An
idea I stole from tokio.) Right now the relevant values are 100x smaller
for Miri, although Miri is probably 1000 to 10,000x slower overall from
a rough estimation.
Caught UB from running `turso_stress` with Miri:
- An unsafe cast of a `*u8` to `*u32` inside the BTree implementation
resulted in the `*u32` making an unaligned read: `read()` ->
`read_unaligned()` fixes this
Future work - Making `turso_stress` reproducible under Miri:
- Right now `turso_stress` is plugged in to Antithesis, which is great!
But, `antithesis_sdk`'s noop mode (`default-features = false`) turns
`antithesis_sdk::random::get_random()` into `rand::random<u64>()`, which
isn't seedable/reproducible. It's more work than I wanted to take on in
this PR, but I'd like to instead conditionally replace `get_random` with
a seedable `ChaCha8Rng` like in the simulator, if Miri is being used.
Comment:
- On a machine without all necessary dependencies, running the bash
scripts fails in a way that cargo prompts you through installing the
nightly toolchain, Miri, etc. until it works
- Below is a snippet of the output from Miri on the Btree alignment
issue. Because turso_stress isn't yet deterministic/reproducible under
Miri, I can't always reproduce it. (It doesn't always happen like the
ones in my last MR)
```
error: Undefined Behavior: accessing memory based on pointer with alignment 1, but alignment 4 is required
    --> /home/rwp/git/turso/core/storage/btree.rs:2860:50
     |
2860 |                     let mut pgno: u32 = unsafe { right_pointer.cast::<u32>().read().swap_bytes() };
     |                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
     |
     = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
     = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

```

Closes #3790
2025-10-21 11:53:49 +03:00
Bob Peterson
2cb0a9b34b Use read_unaligned with *u8 cast to *u32
Avoids undefined behavior due to unaligned read caught with Miri
2025-10-20 22:50:57 -05:00
pedrocarlo
ba9a1ebbef add mutable scoped locking for SharedWalFile 2025-10-20 10:45:14 -03:00
pedrocarlo
b00a276960 add scoped locking for SharedWalFile to avoid holding locks for longer than needed 2025-10-20 10:45:14 -03:00
Pekka Enberg
e03f6dbf94 core/storage: Reduce logging level 2025-10-17 20:09:00 +03:00
Jussi Saurio
2ca388d78d WAL: don't hold shared lock across IO operations
Without this change and running:

```
cd stress
cargo run -- --nr-threads=4 -i 1000 --verbose --busy-timeout=0
```

I can produce a deadlock quite reliably.

With this change, I can't.

Even with 5 second busy timeout (the default), the run makes progress although it is slow as hell because of the busy timeout.
2025-10-16 22:00:01 +03:00
Pekka Enberg
afa89c66c0 Merge 'Replace io_yield_many with completion groups' from Pekka Enberg
Reviewed-by: Pedro Muniz (@pedrocarlo)

Closes #3703
2025-10-16 17:17:43 +03:00
Pere Diaz Bou
57eb63cee0 core/bree: remove duplicated code in BTreeCursor 2025-10-16 14:50:08 +02:00
Pekka Enberg
bf5de920f2 core: Unsafe Send and Sync pushdown
This patch pushes unsafe Send and Sync to individual components instead
of doing it at Database level. This makes it easier for us to
incrementally fix thread-safety, but avoid developers adding more thread
unsafe code.
2025-10-16 11:26:50 +03:00
Pekka Enberg
af3a90bf4b core: Kill Many variant from IOCompletions enum 2025-10-15 11:48:24 +03:00
Pekka Enberg
840d6a0df5 core/storage/btree: Replace io_yield_many with completion group in B-Tree 2025-10-15 11:48:24 +03:00
Pekka Enberg
986faa42da core/storage/pager: Replace io_yield_many with completion groups 2025-10-15 11:48:24 +03:00
Pekka Enberg
7cf51e74ca Merge 'core/mvcc: implement CursorTrait on MVCC cursor' from Pere Diaz Bou
Closes #3714
2025-10-14 10:24:42 +03:00
Pekka Enberg
9a1bd2112d Merge 'Run simulator under Miri' from Bob Peterson
This adds support for running the simulator under Miri to detect UB.
There are a few things to note about Miri and its limitations
- It has limited `libc` coverage, so it's not really possible to have
Miri help with `UringIO`/`UringFile` or `UnixIO`/`UnixFile`. That's a
big gap ☹️
- It **can** work for `GenericIO`/`GenericFile`, which only uses `std`
- It can't call external C libraries, so even using `sqlite` is out
(hence adding `--disable-integrity-check` to the simulator for Miri use)
- It runs on nightly, consequently there are a few new lints that don't
exist on turso's pinned version of rustc
Some questions I have about this MR
- I made `GenericFile::{lock_file,unlock_file}` noops so I could use
`GenericIO`. This isn't great, but if/when you update from Rust 1.88.0
to 1.89.0, `std::File::{lock,lock_shared,unlock}` will be stabilized and
available. Should I note that as a TODO or something?
- Previously, the sim runner shelled out to `git` to get stuff like the
current git hash and the repo directory. For Miri, that's out, and so is
`git2`. Unfortunately, `gix` is also out since it has a required
dependency that uses inline assembly, which Miri doesn't like. I wrote a
hacky shim that uses only std to look for `.git` and find the hash that
HEAD is pointing to. It doesn't deal with stuff like packed-refs or the
repo being a secondary one made with `git worktree`. I'm happy to
support that, but wanted to hear from maintainers before doing more
work.
Two UB occurrences I already found:
- `TursoRwLock::read` used `AtomicU64::compare_exchange_weak`, which is
(evidently) [allowed to spuriously fail](https://doc.rust-lang.org/std/s
ync/atomic/struct.AtomicU64.html#method.compare_exchange_weak) in
exchange for perf. Miri forces this behavior, which triggers trivial
read deadlocks even with zero readers/writers. I changed it to
`compare_exchange`, but I'm not an atomics expert.
- Uninitialized read in non-Unix
`core::storage::buffer_pool::arena::alloc`. This is a simple one,
resolved by using `std::alloc::alloc_zeroed` instead of
`std::alloc::alloc`
Moving forward, I'd be interested in potentially getting the tests to
run in Miri, too. `tokio` looks like a good example of a project with
partial coverage that runs it where they can. They have some extra test
config to allow as many as possible to run under Miri, with
appropriately scaled-down parameter values since Miri is super slow

Closes #3720
2025-10-14 09:26:55 +03:00
pedrocarlo
0ef5ec007c remove cfg for MAP_ANONYMOUS 2025-10-13 18:05:18 -03:00
Bob Peterson
dfc77b0350 Non-Unix arena: use zeroed alloc to avoid UB
Reads to the arena were flagged by Miri as UB since it contained
uninitialized memory
2025-10-13 14:54:16 -05:00
Bob Peterson
74ef9ad5ca Drop weak in TursoRwLock::read's compare_exchange
compare_exchange_weak can spuriously fail, which Miri obliges us with,
causing a read deadlock
2025-10-13 14:54:16 -05:00
Bob Peterson
cd56f52bd6 Add cfg attributes for running under Miri 2025-10-13 14:54:16 -05:00
Pere Diaz Bou
bc05497d99 core/mvcc: implement CursorTrait on MVCC cursor 2025-10-13 19:26:18 +02:00
Pekka Enberg
77492641db Merge 'Move all checksum tests behind the feature flag' from Avinash Sajjanshetty
Closes #3704
2025-10-13 11:46:56 +03:00
Avinash Sajjanshetty
4a29694475 rename checksums tests appropriately 2025-10-13 13:48:07 +05:30
Avinash Sajjanshetty
ee479d2e52 Move all checksum tests behind the feature flag 2025-10-13 13:47:25 +05:30
Pekka Enberg
4af61d8049 Merge 'core/btree: try to introduce trait for cursors' from Pere Diaz Bou
I've added a trait called `CursorTrait`. I know it's not a good name for
now, but I didn't know what tto change then enum `Cursor` to. This trait
wraps all common functionality, and some functionality that is yet too
specific that needs to be fixed.
This is needed in order to have layered cursors where for example,
MvccCursor will need a fallback BTreeCursor.

Closes #3660
2025-10-10 19:25:39 +03:00
Pere Diaz Bou
d0d6db301b core/btree: CursorTrait 2025-10-10 15:04:15 +02:00
Jussi Saurio
acb3c97fea Merge 'When pwritev fails, clear the dirty pages' from Pedro Muniz
If we don't clear the dirty pages, we will initiate a rollback. In the
rollback, we will attempt to clear the whole page cache, but it will
then panic because there will still be dirty pages from the failed
writev

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #3189
2025-10-09 10:38:47 +03:00
Pekka Enberg
13566e5cad Merge 'Integrity check enhancements' from Jussi Saurio
- add index root pages to list of root pages to check
- check for dangling (unused) pages
```sql
$ cargo run wut.db 
turso> .mode list
turso> pragma integrity_check;
Page 3: never used
Page 4: never used
Page 7: never used
Page 8: never used
```
```sql
$ sqlite3 wut.db 'pragma integrity_check;'
*** in database main ***
Page 3: never used
Page 4: never used
Page 7: never used
Page 8: never used
```

Closes #3613
2025-10-08 08:57:18 +03:00
Levy A.
cf53ecb7e3 refactor: remove TextRef and RawSlice and fix tests 2025-10-07 10:43:45 -03:00
Levy A.
77a412f6af refactor: remove unsafe reference semantics from RefValue
also renames `RefValue` to `ValueRef`, to align with rusqlite and other
crates
2025-10-07 10:43:44 -03:00
Pere Diaz Bou
3e508a4b42 core/io: remove new_dummy in place of new_yield
Yield is a completion that does not allocate any inner state. By design
it is completed from the start and has no errors. This allows lightly
yield without allocating any locks nor heap allocate inner state.
2025-10-07 12:00:33 +02:00
Jussi Saurio
5941c03a4f integrity check: check for dangling (unused) pages 2025-10-07 11:35:38 +03:00
Pekka Enberg
a72b07e949 Merge 'Fix VDBE program abort' from Nikita Sivukhin
This PR add proper program abort in case of unfinished statement reset
and interruption.
Also, this PR makes rollback methods non-failing because otherwise of
their callers usually unclear (if rollback failed - what is the state of
statement/connection/transaction?)

Reviewed-by: Preston Thorpe <preston@turso.tech>

Closes #3591
2025-10-07 09:07:07 +03:00
pedrocarlo
5a7390735d rename Completion functions 2025-10-06 11:07:06 -03:00
Nikita Sivukhin
8dae601fac make rollback non-failing method 2025-10-06 13:21:45 +04:00
Nikita Sivukhin
38d2630969 remove unnecessary SchemaLocked error
- lock() return error in case when another thread panicked while holding the same lock
- we better to just panic too in any such case
2025-10-06 12:15:15 +04:00
Pekka Enberg
be6f3d09ea core/storage: Switch checkpoint_inner() to completion group 2025-10-06 07:33:31 +03:00
pedrocarlo
911b6791b9 when pwritev fails, clear the dirty pages
add flag to `clear_page_cache`
2025-10-05 20:02:21 -03:00
pedrocarlo
f3dc0bef5d remove some explicit Arc<dyn File> references 2025-10-03 16:39:57 -03:00
pedrocarlo
e93add6c80 remove dyn DatabaseStorage and replace it with DatabaseFile 2025-10-03 14:14:15 -03:00
Pere Diaz Bou
9c9d4d147e core/btree: fuzz tests force page 1 allocation with a transaction 2025-10-03 13:28:28 +02:00
Pere Diaz Bou
8f103f7c35 core/wal: introduce transaction_count, same as iChange in sqlite 2025-10-03 13:02:47 +02:00
Pekka Enberg
c98bf9b593 Merge 'core/wal: check index header on begin_write_tx' from Pere Diaz Bou
Fixes a page cache staleness issue where connections could incorrectly
believe the database hasn't changed after checkpointing. This can happen
when writes following a checkpoint resulted in the same `max_frame
value`, causing connections to miss updates since they only checked
`max_frame` to detect changes.

Closes #3502
2025-10-03 13:51:22 +03:00
Pere Diaz Bou
b5a969933c core/wal: remove dbg! 2025-10-03 12:17:35 +02:00
Pekka Enberg
b11246278f Merge 'Enable encryption properly in Rust bindings, whopper, and throughput tests' from Avinash Sajjanshetty
This is a follow up from PR - #3457 which requires users to opt in to
enable encryption. This patch
- Makes appropriate changes to Whopper and Encryption throughput tests
- Updated Rust bindings to pass the encryption options properly
- Added a test for rust bindings
To use encryption in Rust bindings, one needs to do:
```rust
let opts = EncryptionOpts {
    hexkey: "b1bbfda...02a5669fc76327".to_string(),
    cipher: "aegis256".to_string(),
};

let builder = Builder::new_local(&db_file).experimental_encryption(true).with_encryption(opts.clone());
let db = builder.build().await.unwrap();
```
We will remove the `experimental_encryption` once the feature is stable.

Closes #3532
2025-10-02 18:32:06 +03:00
Avinash Sajjanshetty
3653c1a853 clear page cache when the encryption context is set 2025-10-02 19:50:12 +05:30
Avinash Sajjanshetty
09ba4615ba return appropriate error if checksum was not compiled 2025-10-02 16:11:18 +05:30