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}