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}