mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-30 22:44:21 +01:00
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:
@@ -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 }
|
||||
|
||||
@@ -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(),
|
||||
)),
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
31
simulator/run-miri.sh
Executable 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[@]}"
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ path = "main.rs"
|
||||
|
||||
[features]
|
||||
default = ["experimental_indexes"]
|
||||
antithesis = ["turso/antithesis"]
|
||||
antithesis = ["turso/antithesis", "antithesis_sdk/full"]
|
||||
experimental_indexes = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
4
stress/run-miri.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-stacked-borrows" cargo +nightly miri run -p turso_stress -- "$@"
|
||||
Reference in New Issue
Block a user