foundationdb/api.rs
1// Copyright 2018 foundationdb-rs developers, https://github.com/Clikengo/foundationdb-rs/graphs/contributors
2// Copyright 2013-2018 Apple, Inc and the FoundationDB project authors.
3//
4// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
5// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
6// http://opensource.org/licenses/MIT>, at your option. This file may not be
7// copied, modified, or distributed except according to those terms.
8
9//! Configuration of foundationDB API and Network
10//!
11//! Provides a safe wrapper around the following C API calls:
12//!
13//! - [API versioning](https://apple.github.io/foundationdb/api-c.html#api-versioning)
14//! - [Network](https://apple.github.io/foundationdb/api-c.html#network)
15
16use std::panic;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::{Arc, Condvar, Mutex};
19use std::thread;
20
21use crate::options::NetworkOption;
22use crate::{error, FdbResult};
23use foundationdb_sys as fdb_sys;
24
25/// Returns the max api version of the underlying Fdb C API Client
26pub fn get_max_api_version() -> i32 {
27 unsafe { fdb_sys::fdb_get_max_api_version() }
28}
29
30static VERSION_SELECTED: AtomicBool = AtomicBool::new(false);
31
32/// A Builder with which different versions of the Fdb C API can be initialized
33///
34/// The foundationDB C API can only be initialized once.
35///
36/// ```
37/// foundationdb::api::FdbApiBuilder::default().build().expect("fdb api initialized");
38/// ```
39pub struct FdbApiBuilder {
40 runtime_version: i32,
41}
42
43impl FdbApiBuilder {
44 /// The version of run-time behavior the API is requested to provide.
45 pub fn runtime_version(&self) -> i32 {
46 self.runtime_version
47 }
48
49 /// Set the version of run-time behavior the API is requested to provide.
50 ///
51 /// Must be less than or equal to header_version, `foundationdb_sys::FDB_API_VERSION`, and should almost always be equal.
52 /// Language bindings which themselves expose API versioning will usually pass the version requested by the application.
53 pub fn set_runtime_version(mut self, version: i32) -> Self {
54 self.runtime_version = version;
55 self
56 }
57
58 /// Initialize the foundationDB API and returns a `NetworkBuilder`
59 ///
60 /// # Panics
61 ///
62 /// This function will panic if called more than once
63 pub fn build(self) -> FdbResult<NetworkBuilder> {
64 if VERSION_SELECTED
65 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
66 .is_err()
67 {
68 panic!("the fdb select api version can only be run once per process");
69 }
70 error::eval(unsafe {
71 fdb_sys::fdb_select_api_version_impl(
72 self.runtime_version,
73 fdb_sys::FDB_API_VERSION as i32,
74 )
75 }).inspect_err(|e| {
76 // 2203: api_version_not_supported
77 // generally mean the local libfdb doesn't support requested target version
78 if e.code() == 2203 {
79 let max_api_version = unsafe { fdb_sys::fdb_get_max_api_version() };
80 if max_api_version < fdb_sys::FDB_API_VERSION as i32 {
81 println!("The version of FoundationDB binding requested '{}' is not supported", fdb_sys::FDB_API_VERSION);
82 println!("by the installed FoundationDB C library. Maximum supported version by the local library is {max_api_version}");
83 }
84 }
85 })?;
86
87 Ok(NetworkBuilder { _private: () })
88 }
89}
90
91impl Default for FdbApiBuilder {
92 fn default() -> Self {
93 FdbApiBuilder {
94 runtime_version: fdb_sys::FDB_API_VERSION as i32,
95 }
96 }
97}
98
99/// A Builder with which the foundationDB network event loop can be created
100///
101/// The foundationDB Network event loop can only be run once.
102///
103/// ```
104/// use foundationdb::api::FdbApiBuilder;
105///
106/// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
107/// let guard = unsafe { network_builder.boot() };
108/// // do some work with foundationDB
109/// drop(guard);
110/// ```
111pub struct NetworkBuilder {
112 _private: (),
113}
114
115impl NetworkBuilder {
116 /// Set network options.
117 pub fn set_option(self, option: NetworkOption) -> FdbResult<Self> {
118 unsafe { option.apply()? };
119 Ok(self)
120 }
121
122 /// Finalizes the initialization of the Network and returns a way to run/wait/stop the
123 /// FoundationDB run loop.
124 ///
125 /// It's not recommended to use this method directly, you probably want the `boot()` method.
126 ///
127 /// In order to start the network you have to:
128 /// - call the unsafe `NetworkRunner::run()` method, most likely in a dedicated thread
129 /// - wait for the thread to start `NetworkWait::wait`
130 ///
131 /// In order for the sequence to be safe, you **MUST** as stated in the `NetworkRunner::run()` method
132 /// ensure that `NetworkStop::stop()` is called before the process exit.
133 /// Aborting the process is still safe.
134 ///
135 /// # Example
136 ///
137 /// ```
138 /// use foundationdb::api::FdbApiBuilder;
139 ///
140 /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
141 /// let (runner, cond) = network_builder.build().expect("fdb network runners");
142 ///
143 /// let net_thread = std::thread::spawn(move || {
144 /// unsafe { runner.run() }.expect("failed to run");
145 /// });
146 ///
147 /// // Wait for the foundationDB network thread to start
148 /// let fdb_network = cond.wait();
149 ///
150 /// // do some work with foundationDB, if a panic occur you still **MUST** catch it and call
151 /// // fdb_network.stop();
152 ///
153 /// // You **MUST** call fdb_network.stop() before the process exit
154 /// fdb_network.stop().expect("failed to stop network");
155 /// net_thread.join().expect("failed to join fdb thread");
156 /// ```
157 #[allow(clippy::mutex_atomic)]
158 pub fn build(self) -> FdbResult<(NetworkRunner, NetworkWait)> {
159 unsafe { error::eval(fdb_sys::fdb_setup_network())? }
160
161 let cond = Arc::new((Mutex::new(false), Condvar::new()));
162 Ok((NetworkRunner { cond: cond.clone() }, NetworkWait { cond }))
163 }
164
165 /// Starts the FoundationDB run loop in a dedicated thread.
166 /// This finish initializing the FoundationDB Client API and can only be called once per process.
167 ///
168 /// # Returns
169 ///
170 /// A `NetworkAutoStop` handle which must be dropped before the program exits.
171 ///
172 /// # Safety
173 ///
174 /// You *MUST* ensure `drop` is called on the returned object before the program exits.
175 /// This is not required if the program is aborted.
176 ///
177 /// This method used to be safe in version `0.4`. But because `drop` on the returned object
178 /// might not be called before the program exits, it was found unsafe.
179 ///
180 /// # Panics
181 ///
182 /// Panics if the dedicated thread cannot be spawned or the internal condition primitive is
183 /// poisonned.
184 ///
185 /// # Examples
186 ///
187 /// ```rust
188 /// use foundationdb::api::FdbApiBuilder;
189 ///
190 /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
191 /// let network = unsafe { network_builder.boot() };
192 /// // do some interesting things with the API...
193 /// drop(network);
194 /// ```
195 ///
196 /// ```rust
197 /// use foundationdb::api::FdbApiBuilder;
198 ///
199 /// #[tokio::main]
200 /// async fn main() {
201 /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
202 /// let network = unsafe { network_builder.boot() };
203 /// // do some interesting things with the API...
204 /// drop(network);
205 /// }
206 /// ```
207 pub unsafe fn boot(self) -> FdbResult<NetworkAutoStop> {
208 let (runner, cond) = self.build()?;
209
210 let net_thread = runner.spawn();
211
212 let network = cond.wait();
213
214 Ok(NetworkAutoStop {
215 handle: Some(net_thread),
216 network: Some(network),
217 })
218 }
219}
220
221/// A foundationDB network event loop runner
222///
223/// Most of the time you should never need to use this directly and use `boot()`.
224pub struct NetworkRunner {
225 cond: Arc<(Mutex<bool>, Condvar)>,
226}
227
228impl NetworkRunner {
229 /// Start the foundationDB network event loop in the current thread.
230 ///
231 /// # Safety
232 ///
233 /// This method is unsafe because you **MUST** call the `stop` method on the
234 /// associated `NetworkStop` before the program exit.
235 ///
236 /// This will only returns once the `stop` method on the associated `NetworkStop`
237 /// object is called or if the foundationDB event loop return an error.
238 pub unsafe fn run(self) -> FdbResult<()> {
239 self._run()
240 }
241
242 fn _run(self) -> FdbResult<()> {
243 {
244 let (lock, cvar) = &*self.cond;
245 let mut started = lock.lock().unwrap();
246 *started = true;
247 // We notify the condvar that the value has changed.
248 cvar.notify_one();
249 }
250
251 error::eval(unsafe { fdb_sys::fdb_run_network() })
252 }
253
254 unsafe fn spawn(self) -> thread::JoinHandle<()> {
255 thread::spawn(move || {
256 self.run().expect("failed to run network thread");
257 })
258 }
259}
260
261/// A condition object that can wait for the associated `NetworkRunner` to actually run.
262///
263/// Most of the time you should never need to use this directly and use `boot()`.
264pub struct NetworkWait {
265 cond: Arc<(Mutex<bool>, Condvar)>,
266}
267
268impl NetworkWait {
269 /// Wait for the associated `NetworkRunner` to actually run.
270 ///
271 /// # Panics
272 ///
273 /// Panics if the internal lock cannot is poisoned
274 pub fn wait(self) -> NetworkStop {
275 // Wait for the thread to start up.
276 {
277 let (lock, cvar) = &*self.cond;
278 let mut started = lock.lock().unwrap();
279 while !*started {
280 started = cvar.wait(started).unwrap();
281 }
282 }
283
284 NetworkStop { _private: () }
285 }
286}
287
288/// Allow to stop the associated and running `NetworkRunner`.
289///
290/// Most of the time you should never need to use this directly and use `boot()`.
291pub struct NetworkStop {
292 _private: (),
293}
294
295impl NetworkStop {
296 /// Signals the event loop invoked by `Network::run` to terminate.
297 pub fn stop(self) -> FdbResult<()> {
298 error::eval(unsafe { fdb_sys::fdb_stop_network() })
299 }
300}
301
302/// Stop the associated `NetworkRunner` and thread if dropped
303///
304/// If trying to stop the FoundationDB run loop results in an error.
305/// The error is printed in `stderr` and the process aborts.
306///
307/// # Panics
308///
309/// Panics if the network thread cannot be joined.
310pub struct NetworkAutoStop {
311 network: Option<NetworkStop>,
312 handle: Option<std::thread::JoinHandle<()>>,
313}
314impl Drop for NetworkAutoStop {
315 fn drop(&mut self) {
316 if let Err(err) = self.network.take().unwrap().stop() {
317 eprintln!("failed to stop network: {err}");
318 // Not aborting can probably cause undefined behavior
319 std::process::abort();
320 }
321 self.handle
322 .take()
323 .unwrap()
324 .join()
325 .expect("failed to join fdb thread");
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_max_api() {
335 assert!(get_max_api_version() > 0);
336 }
337}