mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-19 01:24:20 +01:00
resolve conflict
This commit is contained in:
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -1800,9 +1800,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.8.0"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
|
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.2",
|
"hashbrown 0.15.2",
|
||||||
@@ -1822,7 +1822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88"
|
checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.10.0",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
"itoa",
|
"itoa",
|
||||||
"log",
|
"log",
|
||||||
@@ -2192,10 +2192,12 @@ dependencies = [
|
|||||||
"env_logger 0.10.2",
|
"env_logger 0.10.2",
|
||||||
"garde",
|
"garde",
|
||||||
"hex",
|
"hex",
|
||||||
|
"indexmap 2.10.0",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"json5",
|
"json5",
|
||||||
"log",
|
"log",
|
||||||
"notify",
|
"notify",
|
||||||
|
"parking_lot",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -2265,9 +2267,9 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.12"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
@@ -2670,9 +2672,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.3"
|
version = "0.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
@@ -2680,9 +2682,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.9.10"
|
version = "0.9.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -2772,7 +2774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d"
|
checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.10.0",
|
||||||
"quick-xml 0.32.0",
|
"quick-xml 0.32.0",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time",
|
||||||
@@ -4062,7 +4064,7 @@ version = "0.8.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.10.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
@@ -4084,7 +4086,7 @@ version = "0.22.26"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.10.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
@@ -4346,7 +4348,6 @@ dependencies = [
|
|||||||
"strum_macros",
|
"strum_macros",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"turso_macros",
|
"turso_macros",
|
||||||
"turso_sqlite3_parser",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4370,7 +4371,7 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"env_logger 0.11.7",
|
"env_logger 0.11.7",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"indexmap 2.8.0",
|
"indexmap 2.10.0",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"miette",
|
"miette",
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ rand = "0.9.2"
|
|||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
schemars = "1.0.4"
|
schemars = "1.0.4"
|
||||||
garde = "0.22"
|
garde = "0.22"
|
||||||
|
parking_lot = "0.12.4"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = "line-tables-only"
|
debug = "line-tables-only"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ libm = "0.2"
|
|||||||
turso_macros = { workspace = true }
|
turso_macros = { workspace = true }
|
||||||
miette = "7.6.0"
|
miette = "7.6.0"
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
parking_lot = "0.12.3"
|
parking_lot = { workspace = true }
|
||||||
crossbeam-skiplist = "0.1.3"
|
crossbeam-skiplist = "0.1.3"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
ryu = "1.0.19"
|
ryu = "1.0.19"
|
||||||
|
|||||||
@@ -2487,8 +2487,15 @@ impl IOCompletions {
|
|||||||
match self {
|
match self {
|
||||||
IOCompletions::Single(c) => io.wait_for_completion(c),
|
IOCompletions::Single(c) => io.wait_for_completion(c),
|
||||||
IOCompletions::Many(completions) => {
|
IOCompletions::Many(completions) => {
|
||||||
for c in completions {
|
let mut completions = completions.into_iter();
|
||||||
io.wait_for_completion(c)?;
|
while let Some(c) = completions.next() {
|
||||||
|
let res = io.wait_for_completion(c);
|
||||||
|
if res.is_err() {
|
||||||
|
for c in completions {
|
||||||
|
c.abort();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ turso_macros = { workspace = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
fallible-iterator = "0.3"
|
fallible-iterator = "0.3"
|
||||||
criterion = { version = "0.5", features = ["html_reports" ] }
|
criterion = { version = "0.5", features = ["html_reports" ] }
|
||||||
turso_sqlite3_parser = { workspace = true }
|
|
||||||
|
|
||||||
[target.'cfg(not(target_family = "windows"))'.dev-dependencies]
|
[target.'cfg(not(target_family = "windows"))'.dev-dependencies]
|
||||||
pprof = { version = "0.14.0", features = ["criterion", "flamegraph"] }
|
pprof = { version = "0.14.0", features = ["criterion", "flamegraph"] }
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
use fallible_iterator::FallibleIterator;
|
|
||||||
use pprof::criterion::{Output, PProfProfiler};
|
use pprof::criterion::{Output, PProfProfiler};
|
||||||
use turso_parser::{lexer::Lexer, parser::Parser};
|
use turso_parser::{lexer::Lexer, parser::Parser};
|
||||||
use turso_sqlite3_parser::lexer::{
|
|
||||||
sql::{Parser as OldParser, Tokenizer},
|
|
||||||
Scanner,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn bench_parser(criterion: &mut Criterion) {
|
fn bench_parser(criterion: &mut Criterion) {
|
||||||
let queries = [
|
let queries = [
|
||||||
@@ -22,12 +17,6 @@ fn bench_parser(criterion: &mut Criterion) {
|
|||||||
b.iter(|| Parser::new(black_box(qb)).next().unwrap());
|
b.iter(|| Parser::new(black_box(qb)).next().unwrap());
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function(BenchmarkId::new("limbo_old_parser_query", ""), |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
OldParser::new(black_box(qb)).next().unwrap().unwrap();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,12 +38,6 @@ fn bench_parser_insert_batch(criterion: &mut Criterion) {
|
|||||||
b.iter(|| Parser::new(black_box(qb)).next().unwrap());
|
b.iter(|| Parser::new(black_box(qb)).next().unwrap());
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function(BenchmarkId::new("limbo_old_parser_insert_batch", ""), |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
OldParser::new(black_box(qb)).next().unwrap().unwrap();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,18 +61,6 @@ fn bench_lexer(criterion: &mut Criterion) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group.bench_function(BenchmarkId::new("limbo_old_lexer_query", ""), |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let tokenizer = Tokenizer::new();
|
|
||||||
let mut scanner = Scanner::new(black_box(tokenizer));
|
|
||||||
loop {
|
|
||||||
if let (_, None, _) = scanner.scan(black_box(qb)).unwrap() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group.finish();
|
group.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
cargo publish -p turso_macros
|
cargo publish -p turso_macros
|
||||||
cargo publish -p turso_ext
|
cargo publish -p turso_ext
|
||||||
cargo publish -p turso_sqlite3_parser
|
|
||||||
cargo publish -p turso_parser
|
cargo publish -p turso_parser
|
||||||
cargo publish -p turso_core
|
cargo publish -p turso_core
|
||||||
cargo publish -p turso
|
cargo publish -p turso
|
||||||
|
|||||||
@@ -42,3 +42,5 @@ schemars = { workspace = true }
|
|||||||
garde = { workspace = true, features = ["derive", "serde"] }
|
garde = { workspace = true, features = ["derive", "serde"] }
|
||||||
json5 = { version = "0.4.1" }
|
json5 = { version = "0.4.1" }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
|
parking_lot = { workspace = true }
|
||||||
|
indexmap = "2.10.0"
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ use crate::{
|
|||||||
SimulatorEnv,
|
SimulatorEnv,
|
||||||
generation::Shadow,
|
generation::Shadow,
|
||||||
model::Query,
|
model::Query,
|
||||||
runner::{
|
runner::env::{SimConnection, SimulationType, SimulatorTables},
|
||||||
env::{SimConnection, SimulationType, SimulatorTables},
|
|
||||||
io::SimulatorIO,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::property::{Property, remaining};
|
use super::property::{Property, remaining};
|
||||||
@@ -452,7 +449,7 @@ impl Shadow for Interaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Interaction {
|
impl Interaction {
|
||||||
pub(crate) fn execute_query(&self, conn: &mut Arc<Connection>, _io: &SimulatorIO) -> ResultSet {
|
pub(crate) fn execute_query(&self, conn: &mut Arc<Connection>) -> ResultSet {
|
||||||
if let Self::Query(query) = self {
|
if let Self::Query(query) = self {
|
||||||
let query_str = query.to_string();
|
let query_str = query.to_string();
|
||||||
let rows = conn.query(&query_str);
|
let rows = conn.query(&query_str);
|
||||||
@@ -611,13 +608,7 @@ impl Interaction {
|
|||||||
out.push(r);
|
out.push(r);
|
||||||
}
|
}
|
||||||
StepResult::IO => {
|
StepResult::IO => {
|
||||||
let syncing = {
|
let syncing = env.io.syncing();
|
||||||
let files = env.io.files.borrow();
|
|
||||||
// TODO: currently assuming we only have 1 file that is syncing
|
|
||||||
files
|
|
||||||
.iter()
|
|
||||||
.any(|file| file.sync_completion.borrow().is_some())
|
|
||||||
};
|
|
||||||
if syncing {
|
if syncing {
|
||||||
reopen_database(env);
|
reopen_database(env);
|
||||||
} else {
|
} else {
|
||||||
@@ -666,12 +657,7 @@ impl Interaction {
|
|||||||
let mut current_prob = 0.05;
|
let mut current_prob = 0.05;
|
||||||
let mut incr = 0.001;
|
let mut incr = 0.001;
|
||||||
loop {
|
loop {
|
||||||
let syncing = {
|
let syncing = env.io.syncing();
|
||||||
let files = env.io.files.borrow();
|
|
||||||
files
|
|
||||||
.iter()
|
|
||||||
.any(|file| file.sync_completion.borrow().is_some())
|
|
||||||
};
|
|
||||||
let inject_fault = env.rng.random_bool(current_prob);
|
let inject_fault = env.rng.random_bool(current_prob);
|
||||||
// TODO: avoid for now injecting faults when syncing
|
// TODO: avoid for now injecting faults when syncing
|
||||||
if inject_fault && !syncing {
|
if inject_fault && !syncing {
|
||||||
@@ -722,7 +708,7 @@ fn reopen_database(env: &mut SimulatorEnv) {
|
|||||||
|
|
||||||
// Clear all open files
|
// Clear all open files
|
||||||
// TODO: for correct reporting of faults we should get all the recorded numbers and transfer to the new file
|
// TODO: for correct reporting of faults we should get all the recorded numbers and transfer to the new file
|
||||||
env.io.files.borrow_mut().clear();
|
env.io.close_files();
|
||||||
|
|
||||||
// 2. Re-open database
|
// 2. Re-open database
|
||||||
match env.type_ {
|
match env.type_ {
|
||||||
|
|||||||
@@ -130,6 +130,12 @@ pub struct SimulatorCLI {
|
|||||||
default_value_t = false
|
default_value_t = false
|
||||||
)]
|
)]
|
||||||
pub keep_files: bool,
|
pub keep_files: bool,
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
help = "Use memory IO for complex simulations",
|
||||||
|
default_value_t = false
|
||||||
|
)]
|
||||||
|
pub memory_io: bool,
|
||||||
#[clap(long, default_value_t = ProfileType::Default)]
|
#[clap(long, default_value_t = ProfileType::Default)]
|
||||||
/// Profile selector for Simulation run
|
/// Profile selector for Simulation run
|
||||||
pub profile: ProfileType,
|
pub profile: ProfileType,
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ use sql_generation::model::table::Table;
|
|||||||
use turso_core::Database;
|
use turso_core::Database;
|
||||||
|
|
||||||
use crate::profiles::Profile;
|
use crate::profiles::Profile;
|
||||||
|
use crate::runner::SimIO;
|
||||||
use crate::runner::io::SimulatorIO;
|
use crate::runner::io::SimulatorIO;
|
||||||
|
use crate::runner::memory::io::MemorySimIO;
|
||||||
|
|
||||||
use super::cli::SimulatorCLI;
|
use super::cli::SimulatorCLI;
|
||||||
|
|
||||||
@@ -63,13 +65,14 @@ pub(crate) struct SimulatorEnv {
|
|||||||
pub(crate) opts: SimulatorOpts,
|
pub(crate) opts: SimulatorOpts,
|
||||||
pub profile: Profile,
|
pub profile: Profile,
|
||||||
pub(crate) connections: Vec<SimConnection>,
|
pub(crate) connections: Vec<SimConnection>,
|
||||||
pub(crate) io: Arc<SimulatorIO>,
|
pub(crate) io: Arc<dyn SimIO>,
|
||||||
pub(crate) db: Option<Arc<Database>>,
|
pub(crate) db: Option<Arc<Database>>,
|
||||||
pub(crate) rng: ChaCha8Rng,
|
pub(crate) rng: ChaCha8Rng,
|
||||||
pub(crate) paths: Paths,
|
pub(crate) paths: Paths,
|
||||||
pub(crate) type_: SimulationType,
|
pub(crate) type_: SimulationType,
|
||||||
pub(crate) phase: SimulationPhase,
|
pub(crate) phase: SimulationPhase,
|
||||||
pub(crate) tables: SimulatorTables,
|
pub(crate) tables: SimulatorTables,
|
||||||
|
pub memory_io: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnwindSafe for SimulatorEnv {}
|
impl UnwindSafe for SimulatorEnv {}
|
||||||
@@ -88,6 +91,7 @@ impl SimulatorEnv {
|
|||||||
paths: self.paths.clone(),
|
paths: self.paths.clone(),
|
||||||
type_: self.type_,
|
type_: self.type_,
|
||||||
phase: self.phase,
|
phase: self.phase,
|
||||||
|
memory_io: self.memory_io,
|
||||||
profile: self.profile.clone(),
|
profile: self.profile.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,16 +103,26 @@ impl SimulatorEnv {
|
|||||||
|
|
||||||
let latency_prof = &self.profile.io.latency;
|
let latency_prof = &self.profile.io.latency;
|
||||||
|
|
||||||
let io = Arc::new(
|
let io: Arc<dyn SimIO> = if self.memory_io {
|
||||||
SimulatorIO::new(
|
Arc::new(MemorySimIO::new(
|
||||||
self.opts.seed,
|
self.opts.seed,
|
||||||
self.opts.page_size,
|
self.opts.page_size,
|
||||||
latency_prof.latency_probability,
|
latency_prof.latency_probability,
|
||||||
latency_prof.min_tick,
|
latency_prof.min_tick,
|
||||||
latency_prof.max_tick,
|
latency_prof.max_tick,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Arc::new(
|
||||||
|
SimulatorIO::new(
|
||||||
|
self.opts.seed,
|
||||||
|
self.opts.page_size,
|
||||||
|
latency_prof.latency_probability,
|
||||||
|
latency_prof.min_tick,
|
||||||
|
latency_prof.max_tick,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
};
|
||||||
);
|
|
||||||
|
|
||||||
// Remove existing database file
|
// Remove existing database file
|
||||||
let db_path = self.get_db_path();
|
let db_path = self.get_db_path();
|
||||||
@@ -282,16 +296,26 @@ impl SimulatorEnv {
|
|||||||
|
|
||||||
let latency_prof = &profile.io.latency;
|
let latency_prof = &profile.io.latency;
|
||||||
|
|
||||||
let io = Arc::new(
|
let io: Arc<dyn SimIO> = if cli_opts.memory_io {
|
||||||
SimulatorIO::new(
|
Arc::new(MemorySimIO::new(
|
||||||
seed,
|
seed,
|
||||||
opts.page_size,
|
opts.page_size,
|
||||||
latency_prof.latency_probability,
|
latency_prof.latency_probability,
|
||||||
latency_prof.min_tick,
|
latency_prof.min_tick,
|
||||||
latency_prof.max_tick,
|
latency_prof.max_tick,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Arc::new(
|
||||||
|
SimulatorIO::new(
|
||||||
|
seed,
|
||||||
|
opts.page_size,
|
||||||
|
latency_prof.latency_probability,
|
||||||
|
latency_prof.min_tick,
|
||||||
|
latency_prof.max_tick,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
};
|
||||||
);
|
|
||||||
|
|
||||||
let db = match Database::open_file(
|
let db = match Database::open_file(
|
||||||
io.clone(),
|
io.clone(),
|
||||||
@@ -319,6 +343,7 @@ impl SimulatorEnv {
|
|||||||
db: Some(db),
|
db: Some(db),
|
||||||
type_: simulation_type,
|
type_: simulation_type,
|
||||||
phase: SimulationPhase::Test,
|
phase: SimulationPhase::Test,
|
||||||
|
memory_io: cli_opts.memory_io,
|
||||||
profile: profile.clone(),
|
profile: profile.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ pub(crate) fn execute_interaction(
|
|||||||
SimConnection::Disconnected => unreachable!(),
|
SimConnection::Disconnected => unreachable!(),
|
||||||
};
|
};
|
||||||
tracing::debug!(?interaction);
|
tracing::debug!(?interaction);
|
||||||
let results = interaction.execute_query(conn, &env.io);
|
let results = interaction.execute_query(conn);
|
||||||
if results.is_err() {
|
if results.is_err() {
|
||||||
tracing::error!(?results);
|
tracing::error!(?results);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use rand::{RngCore, SeedableRng};
|
|||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
use turso_core::{Clock, IO, Instant, OpenFlags, PlatformIO, Result};
|
use turso_core::{Clock, IO, Instant, OpenFlags, PlatformIO, Result};
|
||||||
|
|
||||||
use crate::runner::{clock::SimulatorClock, file::SimulatorFile};
|
use crate::runner::{SimIO, clock::SimulatorClock, file::SimulatorFile};
|
||||||
|
|
||||||
pub(crate) struct SimulatorIO {
|
pub(crate) struct SimulatorIO {
|
||||||
pub(crate) inner: Box<dyn IO>,
|
pub(crate) inner: Box<dyn IO>,
|
||||||
@@ -48,15 +48,17 @@ impl SimulatorIO {
|
|||||||
clock: Arc::new(clock),
|
clock: Arc::new(clock),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn inject_fault(&self, fault: bool) {
|
impl SimIO for SimulatorIO {
|
||||||
|
fn inject_fault(&self, fault: bool) {
|
||||||
self.fault.replace(fault);
|
self.fault.replace(fault);
|
||||||
for file in self.files.borrow().iter() {
|
for file in self.files.borrow().iter() {
|
||||||
file.inject_fault(fault);
|
file.inject_fault(fault);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_stats(&self) {
|
fn print_stats(&self) {
|
||||||
for file in self.files.borrow().iter() {
|
for file in self.files.borrow().iter() {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"\n===========================\n\nPath: {}\n{}",
|
"\n===========================\n\nPath: {}\n{}",
|
||||||
@@ -65,6 +67,18 @@ impl SimulatorIO {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn syncing(&self) -> bool {
|
||||||
|
let files = self.files.borrow();
|
||||||
|
// TODO: currently assuming we only have 1 file that is syncing
|
||||||
|
files
|
||||||
|
.iter()
|
||||||
|
.any(|file| file.sync_completion.borrow().is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_files(&self) {
|
||||||
|
self.files.borrow_mut().clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clock for SimulatorIO {
|
impl Clock for SimulatorIO {
|
||||||
|
|||||||
253
simulator/runner/memory/file.rs
Normal file
253
simulator/runner/memory/file.rs
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
use std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand::{Rng as _, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
use tracing::{Level, instrument};
|
||||||
|
use turso_core::{Completion, File, Result};
|
||||||
|
|
||||||
|
use crate::runner::{
|
||||||
|
clock::SimulatorClock,
|
||||||
|
memory::io::{CallbackQueue, Fd, Operation, OperationType},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Tracks IO calls and faults for each type of I/O operation
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct IOTracker {
|
||||||
|
pread_calls: usize,
|
||||||
|
pread_faults: usize,
|
||||||
|
|
||||||
|
pwrite_calls: usize,
|
||||||
|
pwrite_faults: usize,
|
||||||
|
|
||||||
|
pwritev_calls: usize,
|
||||||
|
pwritev_faults: usize,
|
||||||
|
|
||||||
|
sync_calls: usize,
|
||||||
|
sync_faults: usize,
|
||||||
|
|
||||||
|
truncate_calls: usize,
|
||||||
|
truncate_faults: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IOTracker {
|
||||||
|
fn total_calls(&self) -> usize {
|
||||||
|
self.pread_calls
|
||||||
|
+ self.pwrite_calls
|
||||||
|
+ self.pwritev_calls
|
||||||
|
+ self.sync_calls
|
||||||
|
+ self.truncate_calls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemorySimFile {
|
||||||
|
// TODO: maybe have a pending queue which is fast to append
|
||||||
|
// and then we just do a mem swap the pending with the callback to minimize lock contention on callback queue
|
||||||
|
pub callbacks: CallbackQueue,
|
||||||
|
pub fd: Arc<Fd>,
|
||||||
|
pub buffer: RefCell<Vec<u8>>,
|
||||||
|
// TODO: add fault map later here
|
||||||
|
pub closed: Cell<bool>,
|
||||||
|
io_tracker: RefCell<IOTracker>,
|
||||||
|
pub rng: RefCell<ChaCha8Rng>,
|
||||||
|
pub latency_probability: usize,
|
||||||
|
clock: Arc<SimulatorClock>,
|
||||||
|
fault: Cell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for MemorySimFile {}
|
||||||
|
unsafe impl Sync for MemorySimFile {}
|
||||||
|
|
||||||
|
impl MemorySimFile {
|
||||||
|
pub fn new(
|
||||||
|
callbacks: CallbackQueue,
|
||||||
|
fd: Fd,
|
||||||
|
seed: u64,
|
||||||
|
latency_probability: usize,
|
||||||
|
clock: Arc<SimulatorClock>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
callbacks,
|
||||||
|
fd: Arc::new(fd),
|
||||||
|
buffer: RefCell::new(Vec::new()),
|
||||||
|
closed: Cell::new(false),
|
||||||
|
io_tracker: RefCell::new(IOTracker::default()),
|
||||||
|
rng: RefCell::new(ChaCha8Rng::seed_from_u64(seed)),
|
||||||
|
latency_probability,
|
||||||
|
clock,
|
||||||
|
fault: Cell::new(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inject_fault(&self, fault: bool) {
|
||||||
|
self.fault.set(fault);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stats_table(&self) -> String {
|
||||||
|
let io_tracker = self.io_tracker.borrow();
|
||||||
|
let sum_calls = io_tracker.total_calls();
|
||||||
|
let stats_table = [
|
||||||
|
"op calls faults ".to_string(),
|
||||||
|
"--------- -------- --------".to_string(),
|
||||||
|
format!(
|
||||||
|
"pread {:8} {:8}",
|
||||||
|
io_tracker.pread_calls, io_tracker.pread_faults
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"pwrite {:8} {:8}",
|
||||||
|
io_tracker.pwrite_calls, io_tracker.pwrite_faults
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"pwritev {:8} {:8}",
|
||||||
|
io_tracker.pwritev_calls, io_tracker.pwritev_faults
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"sync {:8} {:8}",
|
||||||
|
io_tracker.sync_calls, io_tracker.sync_faults
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"truncate {:8} {:8}",
|
||||||
|
io_tracker.truncate_calls, io_tracker.truncate_faults
|
||||||
|
),
|
||||||
|
"--------- -------- --------".to_string(),
|
||||||
|
format!("total {sum_calls:8}"),
|
||||||
|
];
|
||||||
|
|
||||||
|
stats_table.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all, level = Level::TRACE)]
|
||||||
|
fn generate_latency(&self) -> Option<turso_core::Instant> {
|
||||||
|
let mut rng = self.rng.borrow_mut();
|
||||||
|
// Chance to introduce some latency
|
||||||
|
rng.random_bool(self.latency_probability as f64 / 100.0)
|
||||||
|
.then(|| {
|
||||||
|
let now = self.clock.now();
|
||||||
|
let sum = now + std::time::Duration::from_millis(rng.random_range(5..20));
|
||||||
|
sum.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_op(&self, op: OperationType) {
|
||||||
|
// FIXME: currently avoid any fsync faults until we correctly define the expected behaviour in the simulator
|
||||||
|
let fault = self.fault.get() && !matches!(op, OperationType::Sync { .. });
|
||||||
|
if fault {
|
||||||
|
let mut io_tracker = self.io_tracker.borrow_mut();
|
||||||
|
match &op {
|
||||||
|
OperationType::Read { .. } => io_tracker.pread_faults += 1,
|
||||||
|
OperationType::Write { .. } => io_tracker.pwrite_faults += 1,
|
||||||
|
OperationType::WriteV { .. } => io_tracker.pwritev_faults += 1,
|
||||||
|
OperationType::Sync { .. } => io_tracker.sync_faults += 1,
|
||||||
|
OperationType::Truncate { .. } => io_tracker.truncate_faults += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.callbacks.lock().push(Operation {
|
||||||
|
time: self.generate_latency(),
|
||||||
|
op,
|
||||||
|
fault,
|
||||||
|
fd: self.fd.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_buf(&self, buf: &[u8], offset: usize) -> usize {
|
||||||
|
let mut file_buf = self.buffer.borrow_mut();
|
||||||
|
let more_space = if file_buf.len() < offset {
|
||||||
|
(offset + buf.len()) - file_buf.len()
|
||||||
|
} else {
|
||||||
|
buf.len().saturating_sub(file_buf.len() - offset)
|
||||||
|
};
|
||||||
|
if more_space > 0 {
|
||||||
|
file_buf.reserve(more_space);
|
||||||
|
for _ in 0..more_space {
|
||||||
|
file_buf.push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_buf[offset..][0..buf.len()].copy_from_slice(buf);
|
||||||
|
buf.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File for MemorySimFile {
|
||||||
|
fn lock_file(&self, _exclusive: bool) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unlock_file(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pread(&self, pos: u64, c: Completion) -> Result<Completion> {
|
||||||
|
self.io_tracker.borrow_mut().pread_calls += 1;
|
||||||
|
|
||||||
|
let op = OperationType::Read {
|
||||||
|
completion: c.clone(),
|
||||||
|
offset: pos as usize,
|
||||||
|
};
|
||||||
|
self.insert_op(op);
|
||||||
|
Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pwrite(
|
||||||
|
&self,
|
||||||
|
pos: u64,
|
||||||
|
buffer: Arc<turso_core::Buffer>,
|
||||||
|
c: Completion,
|
||||||
|
) -> Result<Completion> {
|
||||||
|
self.io_tracker.borrow_mut().pwrite_calls += 1;
|
||||||
|
let op = OperationType::Write {
|
||||||
|
buffer,
|
||||||
|
completion: c.clone(),
|
||||||
|
offset: pos as usize,
|
||||||
|
};
|
||||||
|
self.insert_op(op);
|
||||||
|
Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pwritev(
|
||||||
|
&self,
|
||||||
|
pos: u64,
|
||||||
|
buffers: Vec<Arc<turso_core::Buffer>>,
|
||||||
|
c: Completion,
|
||||||
|
) -> Result<Completion> {
|
||||||
|
if buffers.len() == 1 {
|
||||||
|
return self.pwrite(pos, buffers[0].clone(), c);
|
||||||
|
}
|
||||||
|
self.io_tracker.borrow_mut().pwritev_calls += 1;
|
||||||
|
let op = OperationType::WriteV {
|
||||||
|
buffers,
|
||||||
|
completion: c.clone(),
|
||||||
|
offset: pos as usize,
|
||||||
|
};
|
||||||
|
self.insert_op(op);
|
||||||
|
Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync(&self, c: Completion) -> Result<Completion> {
|
||||||
|
self.io_tracker.borrow_mut().sync_calls += 1;
|
||||||
|
let op = OperationType::Sync {
|
||||||
|
completion: c.clone(),
|
||||||
|
};
|
||||||
|
self.insert_op(op);
|
||||||
|
Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Result<u64> {
|
||||||
|
// TODO: size operation should also be scheduled. But this requires a change in how we
|
||||||
|
// Use this function internally in Turso
|
||||||
|
Ok(self.buffer.borrow().len() as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(&self, len: u64, c: Completion) -> Result<Completion> {
|
||||||
|
self.io_tracker.borrow_mut().truncate_calls += 1;
|
||||||
|
let op = OperationType::Truncate {
|
||||||
|
completion: c.clone(),
|
||||||
|
len: len as usize,
|
||||||
|
};
|
||||||
|
self.insert_op(op);
|
||||||
|
Ok(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
270
simulator/runner/memory/io.rs
Normal file
270
simulator/runner/memory/io.rs
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use rand::{RngCore, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
use turso_core::{Clock, Completion, IO, Instant, OpenFlags, Result};
|
||||||
|
|
||||||
|
use crate::runner::SimIO;
|
||||||
|
use crate::runner::clock::SimulatorClock;
|
||||||
|
use crate::runner::memory::file::MemorySimFile;
|
||||||
|
|
||||||
|
/// File descriptor
|
||||||
|
pub type Fd = String;
|
||||||
|
|
||||||
|
pub enum OperationType {
|
||||||
|
Read {
|
||||||
|
completion: Completion,
|
||||||
|
offset: usize,
|
||||||
|
},
|
||||||
|
Write {
|
||||||
|
buffer: Arc<turso_core::Buffer>,
|
||||||
|
completion: Completion,
|
||||||
|
offset: usize,
|
||||||
|
},
|
||||||
|
WriteV {
|
||||||
|
buffers: Vec<Arc<turso_core::Buffer>>,
|
||||||
|
completion: Completion,
|
||||||
|
offset: usize,
|
||||||
|
},
|
||||||
|
Sync {
|
||||||
|
completion: Completion,
|
||||||
|
},
|
||||||
|
Truncate {
|
||||||
|
completion: Completion,
|
||||||
|
len: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OperationType {
|
||||||
|
fn get_completion(&self) -> &Completion {
|
||||||
|
match self {
|
||||||
|
OperationType::Read { completion, .. }
|
||||||
|
| OperationType::Write { completion, .. }
|
||||||
|
| OperationType::WriteV { completion, .. }
|
||||||
|
| OperationType::Sync { completion, .. }
|
||||||
|
| OperationType::Truncate { completion, .. } => completion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Operation {
|
||||||
|
pub time: Option<turso_core::Instant>,
|
||||||
|
pub op: OperationType,
|
||||||
|
pub fault: bool,
|
||||||
|
pub fd: Arc<Fd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operation {
|
||||||
|
fn do_operation(self, files: &IndexMap<Fd, Arc<MemorySimFile>>) {
|
||||||
|
let fd = self.fd;
|
||||||
|
match self.op {
|
||||||
|
OperationType::Read { completion, offset } => {
|
||||||
|
let file = files.get(fd.as_str()).unwrap();
|
||||||
|
let file_buf = file.buffer.borrow_mut();
|
||||||
|
let buffer = completion.as_read().buf.clone();
|
||||||
|
let buf_size = {
|
||||||
|
let buf = buffer.as_mut_slice();
|
||||||
|
// TODO: check for sector faults here
|
||||||
|
|
||||||
|
buf.copy_from_slice(&file_buf[offset..][0..buf.len()]);
|
||||||
|
buf.len() as i32
|
||||||
|
};
|
||||||
|
completion.complete(buf_size);
|
||||||
|
}
|
||||||
|
OperationType::Write {
|
||||||
|
buffer,
|
||||||
|
completion,
|
||||||
|
offset,
|
||||||
|
} => {
|
||||||
|
let file = files.get(fd.as_str()).unwrap();
|
||||||
|
let buf_size = file.write_buf(buffer.as_slice(), offset);
|
||||||
|
completion.complete(buf_size as i32);
|
||||||
|
}
|
||||||
|
OperationType::WriteV {
|
||||||
|
buffers,
|
||||||
|
completion,
|
||||||
|
offset,
|
||||||
|
} => {
|
||||||
|
if buffers.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let file = files.get(fd.as_str()).unwrap();
|
||||||
|
let mut pos = offset;
|
||||||
|
let written = buffers.into_iter().fold(0, |written, buffer| {
|
||||||
|
let buf_size = file.write_buf(buffer.as_slice(), pos);
|
||||||
|
pos += buf_size;
|
||||||
|
written + buf_size
|
||||||
|
});
|
||||||
|
completion.complete(written as i32);
|
||||||
|
}
|
||||||
|
OperationType::Sync { completion, .. } => {
|
||||||
|
// There is no Sync for in memory
|
||||||
|
completion.complete(0);
|
||||||
|
}
|
||||||
|
OperationType::Truncate { completion, len } => {
|
||||||
|
let file = files.get(fd.as_str()).unwrap();
|
||||||
|
let mut file_buf = file.buffer.borrow_mut();
|
||||||
|
file_buf.truncate(len);
|
||||||
|
completion.complete(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type CallbackQueue = Arc<Mutex<Vec<Operation>>>;
|
||||||
|
|
||||||
|
pub struct MemorySimIO {
|
||||||
|
callbacks: CallbackQueue,
|
||||||
|
timeouts: CallbackQueue,
|
||||||
|
pub files: RefCell<IndexMap<Fd, Arc<MemorySimFile>>>,
|
||||||
|
pub rng: RefCell<ChaCha8Rng>,
|
||||||
|
pub nr_run_once_faults: Cell<usize>,
|
||||||
|
pub page_size: usize,
|
||||||
|
seed: u64,
|
||||||
|
latency_probability: usize,
|
||||||
|
clock: Arc<SimulatorClock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for MemorySimIO {}
|
||||||
|
unsafe impl Sync for MemorySimIO {}
|
||||||
|
|
||||||
|
impl MemorySimIO {
|
||||||
|
pub fn new(
|
||||||
|
seed: u64,
|
||||||
|
page_size: usize,
|
||||||
|
latency_probability: usize,
|
||||||
|
min_tick: u64,
|
||||||
|
max_tick: u64,
|
||||||
|
) -> Self {
|
||||||
|
let files = RefCell::new(IndexMap::new());
|
||||||
|
let rng = RefCell::new(ChaCha8Rng::seed_from_u64(seed));
|
||||||
|
let nr_run_once_faults = Cell::new(0);
|
||||||
|
Self {
|
||||||
|
callbacks: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
timeouts: Arc::new(Mutex::new(Vec::new())),
|
||||||
|
files,
|
||||||
|
rng,
|
||||||
|
nr_run_once_faults,
|
||||||
|
page_size,
|
||||||
|
seed,
|
||||||
|
latency_probability,
|
||||||
|
clock: Arc::new(SimulatorClock::new(
|
||||||
|
ChaCha8Rng::seed_from_u64(seed),
|
||||||
|
min_tick,
|
||||||
|
max_tick,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimIO for MemorySimIO {
|
||||||
|
fn inject_fault(&self, fault: bool) {
|
||||||
|
for file in self.files.borrow().values() {
|
||||||
|
file.inject_fault(fault);
|
||||||
|
}
|
||||||
|
if fault {
|
||||||
|
tracing::debug!("fault injected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_stats(&self) {
|
||||||
|
for (path, file) in self.files.borrow().iter() {
|
||||||
|
tracing::info!(
|
||||||
|
"\n===========================\n\nPath: {}\n{}",
|
||||||
|
path,
|
||||||
|
file.stats_table()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syncing(&self) -> bool {
|
||||||
|
let callbacks = self.callbacks.try_lock().unwrap();
|
||||||
|
callbacks
|
||||||
|
.iter()
|
||||||
|
.any(|operation| matches!(operation.op, OperationType::Sync { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_files(&self) {
|
||||||
|
for file in self.files.borrow().values() {
|
||||||
|
file.closed.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clock for MemorySimIO {
|
||||||
|
fn now(&self) -> Instant {
|
||||||
|
self.clock.now().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IO for MemorySimIO {
|
||||||
|
fn open_file(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
_flags: OpenFlags, // TODO: ignoring open flags for now as we don't test read only mode in the simulator yet
|
||||||
|
_direct: bool,
|
||||||
|
) -> Result<Arc<dyn turso_core::File>> {
|
||||||
|
let mut files = self.files.borrow_mut();
|
||||||
|
let fd = path.to_string();
|
||||||
|
let file = if let Some(file) = files.get(path) {
|
||||||
|
file.closed.set(false);
|
||||||
|
file.clone()
|
||||||
|
} else {
|
||||||
|
let file = Arc::new(MemorySimFile::new(
|
||||||
|
self.callbacks.clone(),
|
||||||
|
fd.clone(),
|
||||||
|
self.seed,
|
||||||
|
self.latency_probability,
|
||||||
|
self.clock.clone(),
|
||||||
|
));
|
||||||
|
files.insert(fd, file.clone());
|
||||||
|
file
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_once(&self) -> Result<()> {
|
||||||
|
let mut callbacks = self.callbacks.lock();
|
||||||
|
let mut timeouts = self.timeouts.lock();
|
||||||
|
tracing::trace!(
|
||||||
|
callbacks.len = callbacks.len(),
|
||||||
|
timeouts.len = timeouts.len()
|
||||||
|
);
|
||||||
|
let files = self.files.borrow_mut();
|
||||||
|
let now = self.now();
|
||||||
|
|
||||||
|
callbacks.append(&mut timeouts);
|
||||||
|
|
||||||
|
while let Some(callback) = callbacks.pop() {
|
||||||
|
let completion = callback.op.get_completion();
|
||||||
|
if completion.finished() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if callback.time.is_none() || callback.time.is_some_and(|time| time < now) {
|
||||||
|
if callback.fault {
|
||||||
|
// Inject the fault by aborting the completion
|
||||||
|
completion.abort();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback.do_operation(&files);
|
||||||
|
} else {
|
||||||
|
timeouts.push(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_random_number(&self) -> i64 {
|
||||||
|
self.rng.borrow_mut().next_u64() as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&self, path: &str) -> Result<()> {
|
||||||
|
self.files.borrow_mut().shift_remove(path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
2
simulator/runner/memory/mod.rs
Normal file
2
simulator/runner/memory/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod file;
|
||||||
|
pub mod io;
|
||||||
@@ -8,6 +8,17 @@ pub mod execution;
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub mod file;
|
pub mod file;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
pub mod memory;
|
||||||
pub mod watch;
|
pub mod watch;
|
||||||
|
|
||||||
pub const FAULT_ERROR_MSG: &str = "Injected Fault";
|
pub const FAULT_ERROR_MSG: &str = "Injected Fault";
|
||||||
|
|
||||||
|
pub trait SimIO: turso_core::IO {
|
||||||
|
fn inject_fault(&self, fault: bool);
|
||||||
|
|
||||||
|
fn print_stats(&self);
|
||||||
|
|
||||||
|
fn syncing(&self) -> bool;
|
||||||
|
|
||||||
|
fn close_files(&self);
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ impl InteractionPlan {
|
|||||||
// Remove all properties that do not use the failing tables
|
// Remove all properties that do not use the failing tables
|
||||||
plan.plan.retain_mut(|interactions| {
|
plan.plan.retain_mut(|interactions| {
|
||||||
let retain = if idx == failing_execution.interaction_index {
|
let retain = if idx == failing_execution.interaction_index {
|
||||||
|
if let Interactions::Property(
|
||||||
|
Property::FsyncNoWait { tables, .. } | Property::FaultyQuery { tables, .. },
|
||||||
|
) = interactions
|
||||||
|
{
|
||||||
|
tables.retain(|table| depending_tables.contains(table));
|
||||||
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let mut has_table = interactions
|
let mut has_table = interactions
|
||||||
@@ -73,9 +79,13 @@ impl InteractionPlan {
|
|||||||
| Property::DropSelect { queries, .. } => {
|
| Property::DropSelect { queries, .. } => {
|
||||||
queries.clear();
|
queries.clear();
|
||||||
}
|
}
|
||||||
Property::FsyncNoWait { tables, .. }
|
Property::FsyncNoWait { tables, query }
|
||||||
| Property::FaultyQuery { tables, .. } => {
|
| Property::FaultyQuery { tables, query } => {
|
||||||
tables.retain(|table| depending_tables.contains(table));
|
if !query.uses().iter().any(|t| depending_tables.contains(t)) {
|
||||||
|
tables.clear();
|
||||||
|
} else {
|
||||||
|
tables.retain(|table| depending_tables.contains(table));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Property::SelectLimit { .. }
|
Property::SelectLimit { .. }
|
||||||
| Property::SelectSelectOptimizer { .. }
|
| Property::SelectSelectOptimizer { .. }
|
||||||
|
|||||||
Reference in New Issue
Block a user