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
This commit is contained in:
Pekka Enberg
2025-10-21 11:53:49 +03:00
committed by GitHub
10 changed files with 66 additions and 15 deletions

View File

@@ -90,7 +90,7 @@ fallible-iterator = "0.3.0"
criterion = "0.5"
chrono = { version = "0.4.42", default-features = false }
hex = "0.4"
antithesis_sdk = "0.2"
antithesis_sdk = { version = "0.2", default-features = false }
cfg-if = "1.0.0"
tracing-appender = "0.2.3"
env_logger = { version = "0.11.6", default-features = false }

View File

@@ -153,14 +153,14 @@ impl Builder {
match vfs_choice {
"memory" => Ok(Arc::new(turso_core::MemoryIO::new())),
"syscall" => {
#[cfg(target_family = "unix")]
#[cfg(all(target_family = "unix", not(miri)))]
{
Ok(Arc::new(
turso_core::UnixIO::new()
.map_err(|e| Error::SqlExecutionFailure(e.to_string()))?,
))
}
#[cfg(not(target_family = "unix"))]
#[cfg(any(not(target_family = "unix"), miri))]
{
Ok(Arc::new(
turso_core::PlatformIO::new()
@@ -168,12 +168,12 @@ impl Builder {
))
}
}
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(miri)))]
"io_uring" => Ok(Arc::new(
turso_core::UringIO::new()
.map_err(|e| Error::SqlExecutionFailure(e.to_string()))?,
)),
#[cfg(not(target_os = "linux"))]
#[cfg(any(not(target_os = "linux"), miri))]
"io_uring" => Err(Error::SqlExecutionFailure(
"io_uring is only available on Linux targets".to_string(),
)),

View File

@@ -15,7 +15,7 @@ path = "lib.rs"
[features]
default = ["fs", "uuid", "time", "json", "series", "encryption"]
antithesis = ["dep:antithesis_sdk"]
antithesis = ["dep:antithesis_sdk", "antithesis_sdk?/full"]
tracing_release = ["tracing/release_max_level_info"]
conn_raw_api = []
fs = ["turso_ext/vfs"]

View File

@@ -2857,7 +2857,8 @@ impl BTreeCursor {
// load sibling pages
// start loading right page first
let mut pgno: u32 = unsafe { right_pointer.cast::<u32>().read().swap_bytes() };
let mut pgno: u32 =
unsafe { right_pointer.cast::<u32>().read_unaligned().swap_bytes() };
let current_sibling = sibling_pointer;
let mut group = CompletionGroup::new(|_| {});
for i in (0..=current_sibling).rev() {

31
simulator/run-miri.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
ARGS=("$@")
# Intercept the seed if it's passed
while [[ $# -gt 0 ]]; do
case $1 in
-s=*|--seed=*)
seed="${1#*=}"
shift
;;
-s|--seed)
seed="$2"
shift 2
;;
*)
shift
;;
esac
done
# Otherwise make one up
if [ -z "$seed" ]; then
# Dump 8 bytes of /dev/random as decimal u64
seed=$(od -An -N8 -tu8 /dev/random | tr -d ' ')
ARGS+=("--seed" "${seed}")
echo "Generated seed for Miri and simulator: ${seed}"
else
echo "Intercepted simulator seed to pass to Miri: ${seed}"
fi
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-stacked-borrows -Zmiri-seed=${seed}" cargo +nightly miri run --bin limbo_sim -- "${ARGS[@]}"

View File

@@ -30,7 +30,7 @@ pub struct SimulatorCLI {
short = 'n',
long,
help = "change the maximum size of the randomly generated sequence of interactions",
default_value_t = 5000,
default_value_t = normal_or_miri(5000, 50),
value_parser = clap::value_parser!(u32).range(1..)
)]
pub maximum_tests: u32,
@@ -38,7 +38,7 @@ pub struct SimulatorCLI {
short = 'k',
long,
help = "change the minimum size of the randomly generated sequence of interactions",
default_value_t = 1000,
default_value_t = normal_or_miri(1000, 10),
value_parser = clap::value_parser!(u32).range(1..)
)]
pub minimum_tests: u32,
@@ -149,7 +149,8 @@ pub struct SimulatorCLI {
pub keep_files: bool,
#[clap(
long,
help = "Disable the SQLite integrity check at the end of a simulation"
help = "Disable the SQLite integrity check at the end of a simulation",
default_value_t = normal_or_miri(false, true)
)]
pub disable_integrity_check: bool,
#[clap(
@@ -279,3 +280,7 @@ impl ValueParserFactory for ProfileType {
ProfileTypeParser
}
}
const fn normal_or_miri<T: Copy>(normal_val: T, miri_val: T) -> T {
if cfg!(miri) { miri_val } else { normal_val }
}

View File

@@ -16,7 +16,7 @@ path = "main.rs"
[features]
default = ["experimental_indexes"]
antithesis = ["turso/antithesis"]
antithesis = ["turso/antithesis", "antithesis_sdk/full"]
experimental_indexes = []
[dependencies]

View File

@@ -642,9 +642,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("Done. SQL statements written to {}", opts.log_file);
println!("Database file: {db_file}");
println!("Running SQLite Integrity check");
integrity_check(std::path::Path::new(&db_file))?;
#[cfg(not(miri))]
{
println!("Running SQLite Integrity check");
integrity_check(std::path::Path::new(&db_file))?;
}
Ok(())
}

View File

@@ -21,7 +21,7 @@ pub struct Opts {
short = 'i',
long,
help = "the number of iterations",
default_value_t = 100000
default_value_t = normal_or_miri(100_000, 1000)
)]
pub nr_iterations: usize,
@@ -75,3 +75,11 @@ pub struct Opts {
)]
pub busy_timeout: u64,
}
const fn normal_or_miri<T: Copy>(normal_val: T, miri_val: T) -> T {
if cfg!(miri) {
miri_val
} else {
normal_val
}
}

4
stress/run-miri.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-stacked-borrows" cargo +nightly miri run -p turso_stress -- "$@"