foundationdb_simulation/
lib.rs1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use std::{ptr::NonNull, sync::Arc};
5
6use foundationdb::Database;
7use foundationdb_sys::FDBDatabase as FDBDatabaseAlias;
8
9mod bindings;
10mod fdb_rt;
11
12use bindings::{FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, OpaqueWorkload, Promise};
13pub use bindings::{Metric, Metrics, Severity, WorkloadContext};
14use fdb_rt::fdb_spawn;
15
16pub type SimDatabase = Arc<Database>;
21pub type WrappedWorkload = FDBWorkload;
23
24#[allow(async_fn_in_trait)]
26pub trait RustWorkload {
27 async fn setup(&mut self, db: SimDatabase);
34
35 async fn start(&mut self, db: SimDatabase);
41
42 async fn check(&mut self, db: SimDatabase);
49
50 fn get_metrics(&self, out: Metrics);
57
58 fn get_check_timeout(&self) -> f64;
60}
61
62pub trait RustWorkloadFactory {
64 const FDB_API_VERSION: u32 = foundationdb_sys::FDB_API_VERSION;
66 fn create(name: String, context: WorkloadContext) -> WrappedWorkload;
70}
71
72pub trait SingleRustWorkload: RustWorkload {
74 const FDB_API_VERSION: u32 = foundationdb_sys::FDB_API_VERSION;
76 fn new(name: String, context: WorkloadContext) -> Self;
78}
79
80fn check_database_ref(database: SimDatabase) {
84 if Arc::strong_count(&database) != 1 || Arc::weak_count(&database) != 0 {
85 eprintln!("Reference to Database kept between phases (setup/start/check). All references should be dropped.");
86 std::process::exit(1);
87 }
88 std::mem::forget(database);
89}
90
91unsafe fn database_new(raw_database: *mut FDBDatabase) -> SimDatabase {
92 Arc::new(Database::new_from_pointer(NonNull::new_unchecked(
93 raw_database as *mut FDBDatabaseAlias,
94 )))
95}
96unsafe extern "C" fn workload_setup<W: RustWorkload + 'static>(
97 raw_workload: *mut OpaqueWorkload,
98 raw_database: *mut FDBDatabase,
99 raw_promise: FDBPromise,
100) {
101 let workload = &mut *(raw_workload as *mut W);
102 let database = database_new(raw_database);
103 let done = Promise::new(raw_promise);
104 fdb_spawn(async move {
105 workload.setup(database.clone()).await;
106 check_database_ref(database);
107 done.send(true);
108 });
109}
110unsafe extern "C" fn workload_start<W: RustWorkload + 'static>(
111 raw_workload: *mut OpaqueWorkload,
112 raw_database: *mut FDBDatabase,
113 raw_promise: FDBPromise,
114) {
115 let workload = &mut *(raw_workload as *mut W);
116 let database = database_new(raw_database);
117 let done = Promise::new(raw_promise);
118 fdb_spawn(async move {
119 workload.start(database.clone()).await;
120 check_database_ref(database);
121 done.send(true);
122 });
123}
124unsafe extern "C" fn workload_check<W: RustWorkload + 'static>(
125 raw_workload: *mut OpaqueWorkload,
126 raw_database: *mut FDBDatabase,
127 raw_promise: FDBPromise,
128) {
129 let workload = &mut *(raw_workload as *mut W);
130 let database = database_new(raw_database);
131 let done = Promise::new(raw_promise);
132 fdb_spawn(async move {
133 workload.check(database.clone()).await;
134 check_database_ref(database);
135 done.send(true);
136 });
137}
138unsafe extern "C" fn workload_get_metrics<W: RustWorkload>(
139 raw_workload: *mut OpaqueWorkload,
140 raw_metrics: FDBMetrics,
141) {
142 let workload = &*(raw_workload as *mut W);
143 let out = Metrics::new(raw_metrics);
144 workload.get_metrics(out)
145}
146unsafe extern "C" fn workload_get_check_timeout<W: RustWorkload>(
147 raw_workload: *mut OpaqueWorkload,
148) -> f64 {
149 let workload = &*(raw_workload as *mut W);
150 workload.get_check_timeout()
151}
152unsafe extern "C" fn workload_drop<W: RustWorkload>(raw_workload: *mut OpaqueWorkload) {
153 unsafe { drop(Box::from_raw(raw_workload as *mut W)) };
154}
155
156impl WrappedWorkload {
157 pub fn new<W: RustWorkload + 'static>(workload: W) -> Self {
158 let workload = Box::into_raw(Box::new(workload));
159 WrappedWorkload {
160 inner: workload as *mut _,
161 setup: Some(workload_setup::<W>),
162 start: Some(workload_start::<W>),
163 check: Some(workload_check::<W>),
164 getMetrics: Some(workload_get_metrics::<W>),
165 getCheckTimeout: Some(workload_get_check_timeout::<W>),
166 free: Some(workload_drop::<W>),
167 }
168 }
169}
170#[doc(hidden)]
174pub mod internals {
176 pub use crate::bindings::{str_from_c, FDBWorkloadContext};
177
178 #[cfg(feature = "cpp-abi")]
179 extern "C" {
180 pub fn workloadCppFactory(logger: *const u8) -> *const u8;
181 }
182
183 #[allow(non_snake_case)]
184 #[cfg(not(feature = "cpp-abi"))]
185 pub unsafe extern "C" fn workloadCppFactory(_logger: *const u8) -> *const u8 {
186 eprintln!(
187 "This Rust workload was compiled without the C++ shim adapter. To fix this, either:
188
189- Re-run the simulation with `useCAPI = true` (FoundationDB 7.4 or newer), or
190- Recompile the workload with FoundationDB versions prior to 7.4 or the `cpp-abi` feature"
191 );
192 std::process::exit(1);
193 }
194}
195
196#[macro_export]
199macro_rules! register_factory {
200 ($name:ident) => {
201 #[no_mangle]
202 extern "C" fn workloadCFactory(
203 raw_name: *const i8,
204 raw_context: $crate::internals::FDBWorkloadContext,
205 ) -> $crate::WrappedWorkload {
206 use std::sync::atomic::{AtomicBool, Ordering};
207 static DONE: AtomicBool = AtomicBool::new(false);
208 if DONE
209 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
210 .is_ok()
211 {
212 let version = <$name as $crate::RustWorkloadFactory>::FDB_API_VERSION;
213 let _ = foundationdb::api::FdbApiBuilder::default()
214 .set_runtime_version(version as i32)
215 .build();
216 println!("FDB API version selected: {version}");
217 }
218 let name = $crate::internals::str_from_c(raw_name);
219 let context = $crate::WorkloadContext::new(raw_context);
220 <$name as $crate::RustWorkloadFactory>::create(name, context)
221 }
222 #[no_mangle]
223 extern "C" fn workloadFactory(logger: *const u8) -> *const u8 {
224 unsafe { $crate::internals::workloadCppFactory(logger) }
225 }
226 };
227}
228
229#[macro_export]
232macro_rules! register_workload {
233 ($name:ident) => {
234 #[no_mangle]
235 extern "C" fn workloadCFactory(
236 raw_name: *const i8,
237 raw_context: $crate::internals::FDBWorkloadContext,
238 ) -> $crate::WrappedWorkload {
239 use std::sync::atomic::{AtomicBool, Ordering};
240 static DONE: AtomicBool = AtomicBool::new(false);
241 if DONE
242 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
243 .is_ok()
244 {
245 let version = <$name as $crate::SingleRustWorkload>::FDB_API_VERSION;
246 let _ = foundationdb::api::FdbApiBuilder::default()
247 .set_runtime_version(version as i32)
248 .build();
249 println!("FDB API version selected: {version}");
250 }
251 let name = $crate::internals::str_from_c(raw_name);
252 let context = $crate::WorkloadContext::new(raw_context);
253 $crate::WrappedWorkload::new(<$name as $crate::SingleRustWorkload>::new(name, context))
254 }
255 #[no_mangle]
256 extern "C" fn workloadFactory(logger: *const u8) -> *const u8 {
257 unsafe { $crate::internals::workloadCppFactory(logger) }
258 }
259 };
260}