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::{
13 FDB_WORKLOAD_API_VERSION, FDBDatabase, FDBMetrics, FDBPromise, FDBWorkload, FDBWorkload_VT,
14 OpaqueWorkload, Promise,
15};
16pub use bindings::{Metric, Metrics, Severity, WorkloadContext};
17use fdb_rt::fdb_spawn;
18
19pub type SimDatabase = Arc<Database>;
24pub type WrappedWorkload = FDBWorkload;
26
27#[allow(async_fn_in_trait)]
29pub trait RustWorkload: Sized + 'static {
30 async fn setup(&mut self, db: SimDatabase);
37
38 async fn start(&mut self, db: SimDatabase);
44
45 async fn check(&mut self, db: SimDatabase);
52
53 fn get_metrics(&self, out: Metrics);
60
61 fn get_check_timeout(&self) -> f64;
63
64 const VT: FDBWorkload_VT = FDBWorkload_VT {
66 setup: Some(workload_setup::<Self>),
67 start: Some(workload_start::<Self>),
68 check: Some(workload_check::<Self>),
69 getMetrics: Some(workload_get_metrics::<Self>),
70 getCheckTimeout: Some(workload_get_check_timeout::<Self>),
71 free: Some(workload_drop::<Self>),
72 };
73
74 fn wrap(self) -> WrappedWorkload {
76 let inner = Box::into_raw(Box::new(self));
77 WrappedWorkload {
78 api_version: FDB_WORKLOAD_API_VERSION,
79 inner: inner as *mut _,
80 vt: &Self::VT as *const _ as *mut _,
81 }
82 }
83}
84
85pub trait RustWorkloadFactory {
87 const FDB_API_VERSION: u32 = foundationdb_sys::FDB_API_VERSION;
89 fn create(name: String, context: WorkloadContext) -> WrappedWorkload;
93}
94
95pub trait SingleRustWorkload: RustWorkload {
97 const FDB_API_VERSION: u32 = foundationdb_sys::FDB_API_VERSION;
99 fn new(name: String, context: WorkloadContext) -> Self;
101}
102
103fn check_database_ref(database: SimDatabase) {
107 if Arc::strong_count(&database) != 1 || Arc::weak_count(&database) != 0 {
108 eprintln!(
109 "Reference to Database kept between phases (setup/start/check). All references should be dropped."
110 );
111 std::process::exit(1);
112 }
113 std::mem::forget(database);
114}
115
116unsafe fn database_new(raw_database: *mut FDBDatabase) -> SimDatabase {
117 unsafe {
118 Arc::new(Database::new_from_pointer(NonNull::new_unchecked(
119 raw_database as *mut FDBDatabaseAlias,
120 )))
121 }
122}
123unsafe extern "C" fn workload_setup<W: RustWorkload + 'static>(
124 raw_workload: *mut OpaqueWorkload,
125 raw_database: *mut FDBDatabase,
126 raw_promise: FDBPromise,
127) {
128 unsafe {
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.setup(database.clone()).await;
134 check_database_ref(database);
135 done.send(true);
136 });
137 }
138}
139unsafe extern "C" fn workload_start<W: RustWorkload + 'static>(
140 raw_workload: *mut OpaqueWorkload,
141 raw_database: *mut FDBDatabase,
142 raw_promise: FDBPromise,
143) {
144 unsafe {
145 let workload = &mut *(raw_workload as *mut W);
146 let database = database_new(raw_database);
147 let done = Promise::new(raw_promise);
148 fdb_spawn(async move {
149 workload.start(database.clone()).await;
150 check_database_ref(database);
151 done.send(true);
152 });
153 }
154}
155unsafe extern "C" fn workload_check<W: RustWorkload + 'static>(
156 raw_workload: *mut OpaqueWorkload,
157 raw_database: *mut FDBDatabase,
158 raw_promise: FDBPromise,
159) {
160 unsafe {
161 let workload = &mut *(raw_workload as *mut W);
162 let database = database_new(raw_database);
163 let done = Promise::new(raw_promise);
164 fdb_spawn(async move {
165 workload.check(database.clone()).await;
166 check_database_ref(database);
167 done.send(true);
168 });
169 }
170}
171unsafe extern "C" fn workload_get_metrics<W: RustWorkload>(
172 raw_workload: *mut OpaqueWorkload,
173 raw_metrics: FDBMetrics,
174) {
175 unsafe {
176 let workload = &*(raw_workload as *mut W);
177 let out = Metrics::new(raw_metrics);
178 workload.get_metrics(out)
179 }
180}
181unsafe extern "C" fn workload_get_check_timeout<W: RustWorkload>(
182 raw_workload: *mut OpaqueWorkload,
183) -> f64 {
184 unsafe {
185 let workload = &*(raw_workload as *mut W);
186 workload.get_check_timeout()
187 }
188}
189unsafe extern "C" fn workload_drop<W: RustWorkload>(raw_workload: *mut OpaqueWorkload) {
190 unsafe { drop(Box::from_raw(raw_workload as *mut W)) };
191}
192
193#[doc(hidden)]
197pub mod internals {
199 pub use crate::bindings::{FDBWorkloadContext, str_from_c};
200 pub use crate::fdb_rt::poll_pending_tasks;
201
202 #[cfg(feature = "cpp-abi")]
203 unsafe extern "C" {
204 pub fn workloadCppFactory(logger: *const u8) -> *const u8;
205 }
206
207 #[allow(non_snake_case)]
208 #[cfg(not(feature = "cpp-abi"))]
209 pub unsafe extern "C" fn workloadCppFactory(_logger: *const u8) -> *const u8 {
210 eprintln!(
211 "This Rust workload was compiled without the C++ shim adapter. To fix this, either:
212
213- Re-run the simulation with `useCAPI = true` (FoundationDB 7.4 or newer), or
214- Recompile the workload with FoundationDB versions prior to 7.4 or the `cpp-abi` feature"
215 );
216 std::process::exit(1);
217 }
218}
219
220#[macro_export]
223macro_rules! register_factory {
224 ($name:ident) => {
225 #[unsafe(no_mangle)]
226 extern "C" fn workloadCFactory(
227 raw_name: *const i8,
228 raw_context: $crate::internals::FDBWorkloadContext,
229 ) -> $crate::WrappedWorkload {
230 use std::sync::atomic::{AtomicBool, Ordering};
231 static DONE: AtomicBool = AtomicBool::new(false);
232 if DONE
233 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
234 .is_ok()
235 {
236 let version = <$name as $crate::RustWorkloadFactory>::FDB_API_VERSION;
237 let _ = foundationdb::api::FdbApiBuilder::default()
238 .set_runtime_version(version as i32)
239 .build();
240 println!("FDB API version selected: {version}");
241 foundationdb::future::CUSTOM_EXECUTOR_HOOK
242 .set($crate::internals::poll_pending_tasks)
243 .unwrap();
244 }
245 let name = $crate::internals::str_from_c(raw_name);
246 let context = $crate::WorkloadContext::new(raw_context);
247 <$name as $crate::RustWorkloadFactory>::create(name, context)
248 }
249 #[unsafe(no_mangle)]
250 extern "C" fn workloadFactory(logger: *const u8) -> *const u8 {
251 unsafe { $crate::internals::workloadCppFactory(logger) }
252 }
253 };
254}
255
256#[macro_export]
259macro_rules! register_workload {
260 ($name:ident) => {
261 #[unsafe(no_mangle)]
262 extern "C" fn workloadCFactory(
263 raw_name: *const i8,
264 raw_context: $crate::internals::FDBWorkloadContext,
265 ) -> $crate::WrappedWorkload {
266 use std::sync::atomic::{AtomicBool, Ordering};
267 static DONE: AtomicBool = AtomicBool::new(false);
268 if DONE
269 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
270 .is_ok()
271 {
272 let version = <$name as $crate::SingleRustWorkload>::FDB_API_VERSION;
273 let _ = foundationdb::api::FdbApiBuilder::default()
274 .set_runtime_version(version as i32)
275 .build();
276 println!("FDB API version selected: {version}");
277 foundationdb::future::CUSTOM_EXECUTOR_HOOK
278 .set($crate::internals::poll_pending_tasks)
279 .unwrap();
280 }
281 let name = $crate::internals::str_from_c(raw_name);
282 let context = $crate::WorkloadContext::new(raw_context);
283 $crate::RustWorkload::wrap(<$name as $crate::SingleRustWorkload>::new(name, context))
284 }
285 #[unsafe(no_mangle)]
286 extern "C" fn workloadFactory(logger: *const u8) -> *const u8 {
287 unsafe { $crate::internals::workloadCppFactory(logger) }
288 }
289 };
290}