mirror of
https://github.com/neondatabase/neon.git
synced 2026-01-08 05:52:55 +00:00
This PR contains the first version of a [FoundationDB-like](https://www.youtube.com/watch?v=4fFDFbi3toc) simulation testing for safekeeper and walproposer. ### desim This is a core "framework" for running determenistic simulation. It operates on threads, allowing to test syncronous code (like walproposer). `libs/desim/src/executor.rs` contains implementation of a determenistic thread execution. This is achieved by blocking all threads, and each time allowing only a single thread to make an execution step. All executor's threads are blocked using `yield_me(after_ms)` function. This function is called when a thread wants to sleep or wait for an external notification (like blocking on a channel until it has a ready message). `libs/desim/src/chan.rs` contains implementation of a channel (basic sync primitive). It has unlimited capacity and any thread can push or read messages to/from it. `libs/desim/src/network.rs` has a very naive implementation of a network (only reliable TCP-like connections are supported for now), that can have arbitrary delays for each package and failure injections for breaking connections with some probability. `libs/desim/src/world.rs` ties everything together, to have a concept of virtual nodes that can have network connections between them. ### walproposer_sim Has everything to run walproposer and safekeepers in a simulation. `safekeeper.rs` reimplements all necesary stuff from `receive_wal.rs`, `send_wal.rs` and `timelines_global_map.rs`. `walproposer_api.rs` implements all walproposer callback to use simulation library. `simulation.rs` defines a schedule – a set of events like `restart <sk>` or `write_wal` that should happen at time `<ts>`. It also has code to spawn walproposer/safekeeper threads and provide config to them. ### tests `simple_test.rs` has tests that just start walproposer and 3 safekeepers together in a simulation, and tests that they are not crashing right away. `misc_test.rs` has tests checking more advanced simulation cases, like crashing or restarting threads, testing memory deallocation, etc. `random_test.rs` is the main test, it checks thousands of random seeds (schedules) for correctness. It roughly corresponds to running a real python integration test in an environment with very unstable network and cpu, but in a determenistic way (each seed results in the same execution log) and much much faster. Closes #547 --------- Co-authored-by: Arseny Sher <sher-ars@yandex.ru>
121 lines
4.7 KiB
Rust
121 lines
4.7 KiB
Rust
//! Links with walproposer, pgcommon, pgport and runs bindgen on walproposer.h
|
|
//! to generate Rust bindings for it.
|
|
|
|
use std::{env, path::PathBuf, process::Command};
|
|
|
|
use anyhow::{anyhow, Context};
|
|
use bindgen::CargoCallbacks;
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
// Tell cargo to invalidate the built crate whenever the wrapper changes
|
|
println!("cargo:rerun-if-changed=bindgen_deps.h");
|
|
|
|
// Finding the location of built libraries and Postgres C headers:
|
|
// - if POSTGRES_INSTALL_DIR is set look into it, otherwise look into `<project_root>/pg_install`
|
|
// - if there's a `bin/pg_config` file use it for getting include server, otherwise use `<project_root>/pg_install/{PG_MAJORVERSION}/include/postgresql/server`
|
|
let pg_install_dir = if let Some(postgres_install_dir) = env::var_os("POSTGRES_INSTALL_DIR") {
|
|
postgres_install_dir.into()
|
|
} else {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../pg_install")
|
|
};
|
|
|
|
let pg_install_abs = std::fs::canonicalize(pg_install_dir)?;
|
|
let walproposer_lib_dir = pg_install_abs.join("build/walproposer-lib");
|
|
let walproposer_lib_search_str = walproposer_lib_dir
|
|
.to_str()
|
|
.ok_or(anyhow!("Bad non-UTF path"))?;
|
|
|
|
let pgxn_neon = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../pgxn/neon");
|
|
let pgxn_neon = std::fs::canonicalize(pgxn_neon)?;
|
|
let pgxn_neon = pgxn_neon.to_str().ok_or(anyhow!("Bad non-UTF path"))?;
|
|
|
|
println!("cargo:rustc-link-lib=static=pgport");
|
|
println!("cargo:rustc-link-lib=static=pgcommon");
|
|
println!("cargo:rustc-link-lib=static=walproposer");
|
|
println!("cargo:rustc-link-search={walproposer_lib_search_str}");
|
|
|
|
// Rebuild crate when libwalproposer.a changes
|
|
println!("cargo:rerun-if-changed={walproposer_lib_search_str}/libwalproposer.a");
|
|
|
|
let pg_config_bin = pg_install_abs.join("v16").join("bin").join("pg_config");
|
|
let inc_server_path: String = if pg_config_bin.exists() {
|
|
let output = Command::new(pg_config_bin)
|
|
.arg("--includedir-server")
|
|
.output()
|
|
.context("failed to execute `pg_config --includedir-server`")?;
|
|
|
|
if !output.status.success() {
|
|
panic!("`pg_config --includedir-server` failed")
|
|
}
|
|
|
|
String::from_utf8(output.stdout)
|
|
.context("pg_config output is not UTF-8")?
|
|
.trim_end()
|
|
.into()
|
|
} else {
|
|
let server_path = pg_install_abs
|
|
.join("v16")
|
|
.join("include")
|
|
.join("postgresql")
|
|
.join("server")
|
|
.into_os_string();
|
|
server_path
|
|
.into_string()
|
|
.map_err(|s| anyhow!("Bad postgres server path {s:?}"))?
|
|
};
|
|
|
|
// The bindgen::Builder is the main entry point
|
|
// to bindgen, and lets you build up options for
|
|
// the resulting bindings.
|
|
let bindings = bindgen::Builder::default()
|
|
// The input header we would like to generate
|
|
// bindings for.
|
|
.header("bindgen_deps.h")
|
|
// Tell cargo to invalidate the built crate whenever any of the
|
|
// included header files changed.
|
|
.parse_callbacks(Box::new(CargoCallbacks))
|
|
.allowlist_type("WalProposer")
|
|
.allowlist_type("WalProposerConfig")
|
|
.allowlist_type("walproposer_api")
|
|
.allowlist_function("WalProposerCreate")
|
|
.allowlist_function("WalProposerStart")
|
|
.allowlist_function("WalProposerBroadcast")
|
|
.allowlist_function("WalProposerPoll")
|
|
.allowlist_function("WalProposerFree")
|
|
.allowlist_function("SafekeeperStateDesiredEvents")
|
|
.allowlist_var("DEBUG5")
|
|
.allowlist_var("DEBUG4")
|
|
.allowlist_var("DEBUG3")
|
|
.allowlist_var("DEBUG2")
|
|
.allowlist_var("DEBUG1")
|
|
.allowlist_var("LOG")
|
|
.allowlist_var("INFO")
|
|
.allowlist_var("NOTICE")
|
|
.allowlist_var("WARNING")
|
|
.allowlist_var("ERROR")
|
|
.allowlist_var("FATAL")
|
|
.allowlist_var("PANIC")
|
|
.allowlist_var("WPEVENT")
|
|
.allowlist_var("WL_LATCH_SET")
|
|
.allowlist_var("WL_SOCKET_READABLE")
|
|
.allowlist_var("WL_SOCKET_WRITEABLE")
|
|
.allowlist_var("WL_TIMEOUT")
|
|
.allowlist_var("WL_SOCKET_CLOSED")
|
|
.allowlist_var("WL_SOCKET_MASK")
|
|
.clang_arg("-DWALPROPOSER_LIB")
|
|
.clang_arg(format!("-I{pgxn_neon}"))
|
|
.clang_arg(format!("-I{inc_server_path}"))
|
|
// Finish the builder and generate the bindings.
|
|
.generate()
|
|
// Unwrap the Result and panic on failure.
|
|
.expect("Unable to generate bindings");
|
|
|
|
// Write the bindings to the $OUT_DIR/bindings.rs file.
|
|
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
|
|
bindings
|
|
.write_to_file(out_path)
|
|
.expect("Couldn't write bindings!");
|
|
|
|
Ok(())
|
|
}
|