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        }).map_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                e
86            })?;
87
88        Ok(NetworkBuilder { _private: () })
89    }
90}
91
92impl Default for FdbApiBuilder {
93    fn default() -> Self {
94        FdbApiBuilder {
95            runtime_version: fdb_sys::FDB_API_VERSION as i32,
96        }
97    }
98}
99
100/// A Builder with which the foundationDB network event loop can be created
101///
102/// The foundationDB Network event loop can only be run once.
103///
104/// ```
105/// use foundationdb::api::FdbApiBuilder;
106///
107/// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
108/// let guard = unsafe { network_builder.boot() };
109/// // do some work with foundationDB
110/// drop(guard);
111/// ```
112pub struct NetworkBuilder {
113    _private: (),
114}
115
116impl NetworkBuilder {
117    /// Set network options.
118    pub fn set_option(self, option: NetworkOption) -> FdbResult<Self> {
119        unsafe { option.apply()? };
120        Ok(self)
121    }
122
123    /// Finalizes the initialization of the Network and returns a way to run/wait/stop the
124    /// FoundationDB run loop.
125    ///
126    /// It's not recommended to use this method directly, you probably want the `boot()` method.
127    ///
128    /// In order to start the network you have to:
129    ///  - call the unsafe `NetworkRunner::run()` method, most likely in a dedicated thread
130    ///  - wait for the thread to start `NetworkWait::wait`
131    ///
132    /// In order for the sequence to be safe, you **MUST** as stated in the `NetworkRunner::run()` method
133    /// ensure that `NetworkStop::stop()` is called before the process exit.
134    /// Aborting the process is still safe.
135    ///
136    /// # Example
137    ///
138    /// ```
139    /// use foundationdb::api::FdbApiBuilder;
140    ///
141    /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
142    /// let (runner, cond) = network_builder.build().expect("fdb network runners");
143    ///
144    /// let net_thread = std::thread::spawn(move || {
145    ///     unsafe { runner.run() }.expect("failed to run");
146    /// });
147    ///
148    /// // Wait for the foundationDB network thread to start
149    /// let fdb_network = cond.wait();
150    ///
151    /// // do some work with foundationDB, if a panic occur you still **MUST** catch it and call
152    /// // fdb_network.stop();
153    ///
154    /// // You **MUST** call fdb_network.stop() before the process exit
155    /// fdb_network.stop().expect("failed to stop network");
156    /// net_thread.join().expect("failed to join fdb thread");
157    /// ```
158    #[allow(clippy::mutex_atomic)]
159    pub fn build(self) -> FdbResult<(NetworkRunner, NetworkWait)> {
160        unsafe { error::eval(fdb_sys::fdb_setup_network())? }
161
162        let cond = Arc::new((Mutex::new(false), Condvar::new()));
163        Ok((NetworkRunner { cond: cond.clone() }, NetworkWait { cond }))
164    }
165
166    /// Starts the FoundationDB run loop in a dedicated thread.
167    /// This finish initializing the FoundationDB Client API and can only be called once per process.
168    ///
169    /// # Returns
170    ///
171    /// A `NetworkAutoStop` handle which must be dropped before the program exits.
172    ///
173    /// # Safety
174    ///
175    /// You *MUST* ensure `drop` is called on the returned object before the program exits.
176    /// This is not required if the program is aborted.
177    ///
178    /// This method used to be safe in version `0.4`. But because `drop` on the returned object
179    /// might not be called before the program exits, it was found unsafe.
180    ///
181    /// # Panics
182    ///
183    /// Panics if the dedicated thread cannot be spawned or the internal condition primitive is
184    /// poisonned.
185    ///
186    /// # Examples
187    ///
188    /// ```rust
189    /// use foundationdb::api::FdbApiBuilder;
190    ///
191    /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
192    /// let network = unsafe { network_builder.boot() };
193    /// // do some interesting things with the API...
194    /// drop(network);
195    /// ```
196    ///
197    /// ```rust
198    /// use foundationdb::api::FdbApiBuilder;
199    ///
200    /// #[tokio::main]
201    /// async fn main() {
202    ///     let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
203    ///     let network = unsafe { network_builder.boot() };
204    ///     // do some interesting things with the API...
205    ///     drop(network);
206    /// }
207    /// ```
208    pub unsafe fn boot(self) -> FdbResult<NetworkAutoStop> {
209        let (runner, cond) = self.build()?;
210
211        let net_thread = runner.spawn();
212
213        let network = cond.wait();
214
215        Ok(NetworkAutoStop {
216            handle: Some(net_thread),
217            network: Some(network),
218        })
219    }
220}
221
222/// A foundationDB network event loop runner
223///
224/// Most of the time you should never need to use this directly and use `boot()`.
225pub struct NetworkRunner {
226    cond: Arc<(Mutex<bool>, Condvar)>,
227}
228
229impl NetworkRunner {
230    /// Start the foundationDB network event loop in the current thread.
231    ///
232    /// # Safety
233    ///
234    /// This method is unsafe because you **MUST** call the `stop` method on the
235    /// associated `NetworkStop` before the program exit.
236    ///
237    /// This will only returns once the `stop` method on the associated `NetworkStop`
238    /// object is called or if the foundationDB event loop return an error.
239    pub unsafe fn run(self) -> FdbResult<()> {
240        self._run()
241    }
242
243    fn _run(self) -> FdbResult<()> {
244        {
245            let (lock, cvar) = &*self.cond;
246            let mut started = lock.lock().unwrap();
247            *started = true;
248            // We notify the condvar that the value has changed.
249            cvar.notify_one();
250        }
251
252        error::eval(unsafe { fdb_sys::fdb_run_network() })
253    }
254
255    unsafe fn spawn(self) -> thread::JoinHandle<()> {
256        thread::spawn(move || {
257            self.run().expect("failed to run network thread");
258        })
259    }
260}
261
262/// A condition object that can wait for the associated `NetworkRunner` to actually run.
263///
264/// Most of the time you should never need to use this directly and use `boot()`.
265pub struct NetworkWait {
266    cond: Arc<(Mutex<bool>, Condvar)>,
267}
268
269impl NetworkWait {
270    /// Wait for the associated `NetworkRunner` to actually run.
271    ///
272    /// # Panics
273    ///
274    /// Panics if the internal lock cannot is poisoned
275    pub fn wait(self) -> NetworkStop {
276        // Wait for the thread to start up.
277        {
278            let (lock, cvar) = &*self.cond;
279            let mut started = lock.lock().unwrap();
280            while !*started {
281                started = cvar.wait(started).unwrap();
282            }
283        }
284
285        NetworkStop { _private: () }
286    }
287}
288
289/// Allow to stop the associated and running `NetworkRunner`.
290///
291/// Most of the time you should never need to use this directly and use `boot()`.
292pub struct NetworkStop {
293    _private: (),
294}
295
296impl NetworkStop {
297    /// Signals the event loop invoked by `Network::run` to terminate.
298    pub fn stop(self) -> FdbResult<()> {
299        error::eval(unsafe { fdb_sys::fdb_stop_network() })
300    }
301}
302
303/// Stop the associated `NetworkRunner` and thread if dropped
304///
305/// If trying to stop the FoundationDB run loop results in an error.
306/// The error is printed in `stderr` and the process aborts.
307///
308/// # Panics
309///
310/// Panics if the network thread cannot be joined.
311pub struct NetworkAutoStop {
312    network: Option<NetworkStop>,
313    handle: Option<std::thread::JoinHandle<()>>,
314}
315impl Drop for NetworkAutoStop {
316    fn drop(&mut self) {
317        if let Err(err) = self.network.take().unwrap().stop() {
318            eprintln!("failed to stop network: {}", err);
319            // Not aborting can probably cause undefined behavior
320            std::process::abort();
321        }
322        self.handle
323            .take()
324            .unwrap()
325            .join()
326            .expect("failed to join fdb thread");
327    }
328}
329
330#[cfg(test)]
331mod tests {
332    use super::*;
333
334    #[test]
335    fn test_max_api() {
336        assert!(get_max_api_version() > 0);
337    }
338}