Skip to content

Commit 7530fe5

Browse files
authored
Merge pull request #204 from Clikengo/fix_boot_api
Simplify and fix UB in boot API
2 parents cf5ceb6 + 4cee9d1 commit 7530fe5

File tree

15 files changed

+229
-228
lines changed

15 files changed

+229
-228
lines changed

foundationdb-bench/src/bin/fdb-bench.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,12 @@ fn main() {
146146
let opt = Opt::from_args();
147147
info!("opt: {:?}", opt);
148148

149-
fdb::boot(|| {
150-
let db = Arc::new(
151-
futures::executor::block_on(fdb::Database::new_compat(None))
152-
.expect("failed to get database"),
153-
);
154-
155-
let bench = Bench { db, opt };
156-
bench.run();
157-
});
149+
let _guard = unsafe { foundationdb::boot() };
150+
let db = Arc::new(
151+
futures::executor::block_on(fdb::Database::new_compat(None))
152+
.expect("failed to get database"),
153+
);
154+
155+
let bench = Bench { db, opt };
156+
bench.run();
158157
}

foundationdb-bindingtester/src/main.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,23 +1582,22 @@ fn main() {
15821582
"Starting rust bindingtester with api_version {}",
15831583
api_version
15841584
);
1585-
api::FdbApiBuilder::default()
1585+
let builder = api::FdbApiBuilder::default()
15861586
.set_runtime_version(api_version)
15871587
.build()
1588-
.expect("failed to initialize FoundationDB API")
1589-
.boot(|| {
1590-
let db = Arc::new(
1591-
futures::executor::block_on(fdb::Database::new_compat(cluster_path))
1592-
.expect("failed to get database"),
1593-
);
1594-
1595-
let mut sm = StackMachine::new(&db, Bytes::from(prefix.to_owned().into_bytes()));
1596-
futures::executor::block_on(sm.run(db)).unwrap();
1597-
sm.join();
1598-
1599-
info!("Closing...");
1600-
})
1601-
.expect("failed to initialize FoundationDB network thread");
1588+
.expect("failed to initialize FoundationDB API");
1589+
let _network = unsafe { builder.boot() };
1590+
1591+
let db = Arc::new(
1592+
futures::executor::block_on(fdb::Database::new_compat(cluster_path))
1593+
.expect("failed to get database"),
1594+
);
1595+
1596+
let mut sm = StackMachine::new(&db, Bytes::from(prefix.to_owned().into_bytes()));
1597+
futures::executor::block_on(sm.run(db)).unwrap();
1598+
sm.join();
1599+
1600+
info!("Closing...");
16021601

16031602
info!("Done.");
16041603
}

foundationdb/README.md

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,48 @@ async fn async_main() -> foundationdb::FdbResult<()> {
6565
Ok(())
6666
}
6767

68-
foundationdb::boot(|| {
68+
foundationdb::run(|| {
6969
futures::executor::block_on(async_main()).expect("failed to run");
7070
});
7171
```
7272

73+
## Migration from 0.4 to 0.5
74+
75+
The initialization of foundationdb API has changed due to undefined behavior being possible with only safe code (issues #170, #181, pulls #179, #182).
76+
77+
Previously you had to wrote:
78+
79+
```rust
80+
let network = foundationdb::boot().expect("failed to initialize Fdb");
81+
82+
futures::executor::block_on(async_main()).expect("failed to run");
83+
// cleanly shutdown the client
84+
drop(network);
85+
```
86+
87+
This can be converted to:
88+
89+
```rust
90+
foundationdb::boot(|| {
91+
futures::executor::block_on(async_main()).expect("failed to run");
92+
}).expect("failed to boot fdb");
93+
```
94+
95+
or
96+
97+
```rust
98+
#[tokio::main]
99+
async fn main() {
100+
// Safe because drop is called before the program exits
101+
let network = unsafe { foundationdb::boot() }.expect("failed to initialize Fdb");
102+
103+
// Have fun with the FDB API
104+
105+
// shutdown the client
106+
drop(network);
107+
}
108+
```
109+
73110
## API stability
74111

75112
_WARNING_ Until the 1.0 release of this library, the API may be in constant flux.

foundationdb/examples/class-scheduling.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,10 @@ async fn run_sim(db: &Database, students: usize, ops_per_student: usize) {
364364
}
365365

366366
fn main() {
367-
fdb::boot(|| {
368-
let db = futures::executor::block_on(fdb::Database::new_compat(None))
369-
.expect("failed to get database");
370-
futures::executor::block_on(init(&db, &*ALL_CLASSES));
371-
println!("Initialized");
372-
futures::executor::block_on(run_sim(&db, 10, 10));
373-
});
367+
let _guard = unsafe { fdb::boot() };
368+
let db = futures::executor::block_on(fdb::Database::new_compat(None))
369+
.expect("failed to get database");
370+
futures::executor::block_on(init(&db, &*ALL_CLASSES));
371+
println!("Initialized");
372+
futures::executor::block_on(run_sim(&db, 10, 10));
374373
}

foundationdb/src/api.rs

Lines changed: 61 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
1818
use std::sync::{Arc, Condvar, Mutex};
1919
use std::thread;
2020

21-
use futures::prelude::*;
22-
2321
use crate::options::NetworkOption;
2422
use crate::{error, FdbResult};
2523
use foundationdb_sys as fdb_sys;
@@ -92,9 +90,9 @@ impl Default for FdbApiBuilder {
9290
/// use foundationdb::api::FdbApiBuilder;
9391
///
9492
/// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
95-
/// network_builder.boot(|| {
96-
/// // do some work with foundationDB
97-
/// }).expect("fdb network to run");
93+
/// let guard = unsafe { network_builder.boot() };
94+
/// // do some work with foundationDB
95+
/// drop(guard);
9896
/// ```
9997
pub struct NetworkBuilder {
10098
_private: (),
@@ -110,7 +108,7 @@ impl NetworkBuilder {
110108
/// Finalizes the initialization of the Network
111109
///
112110
/// It's not recommended to use this method unless you really know what you are doing.
113-
/// Otherwise, the `boot` method is the **safe** and easiest way to do it.
111+
/// Otherwise, the `run` method is the **safe** and easiest way to do it.
114112
///
115113
/// In order to start the network you have to call the unsafe `NetworkRunner::run()` method.
116114
/// This method starts the foundationdb network runloop, once started, the `NetworkStop::stop()`
@@ -144,90 +142,59 @@ impl NetworkBuilder {
144142
Ok((NetworkRunner { cond: cond.clone() }, NetworkWait { cond }))
145143
}
146144

147-
/// Execute `f` with the FoundationDB Client API ready, this can only be called once per process.
145+
/// Initialize the FoundationDB Client API, this can only be called once per process.
146+
///
147+
/// # Returns
148+
///
149+
/// A `NetworkAutoStop` handle which must be dropped before the program exits.
150+
///
151+
/// # Safety
152+
///
153+
/// This method used to be safe in version `0.4`. But because `drop` on the returned object
154+
/// might not be called before the program exits, it was found unsafe.
155+
/// You should prefer the safe `run` variant.
156+
/// If you still want to use this, you *MUST* ensure drop is called on the returned object
157+
/// before the program exits. This is not required if the program is aborted.
148158
///
149159
/// # Examples
150160
///
151161
/// ```rust
152162
/// use foundationdb::api::FdbApiBuilder;
153163
///
154164
/// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
155-
/// network_builder.boot(|| {
156-
/// // do some interesting things with the API...
157-
/// });
165+
/// let network = unsafe { network_builder.boot() };
166+
/// // do some interesting things with the API...
167+
/// drop(network);
158168
/// ```
159-
pub fn boot<T>(self, f: impl (FnOnce() -> T) + panic::UnwindSafe) -> FdbResult<T> {
160-
let (runner, cond) = self.build()?;
161-
162-
let net_thread = thread::spawn(move || {
163-
unsafe { runner.run() }.expect("failed to run");
164-
});
165-
166-
let network = cond.wait();
167-
168-
let res = panic::catch_unwind(f);
169-
170-
if let Err(err) = network.stop() {
171-
eprintln!("failed to stop network: {}", err);
172-
// Not aborting can probably cause undefined behavior
173-
std::process::abort();
174-
}
175-
net_thread.join().expect("failed to join fdb thread");
176-
177-
match res {
178-
Err(payload) => panic::resume_unwind(payload),
179-
Ok(v) => Ok(v),
180-
}
181-
}
182-
183-
/// Async execute `f` with the FoundationDB Client API ready, this can only be called once per process.
184-
///
185-
/// # Examples
186169
///
187170
/// ```rust
188171
/// use foundationdb::api::FdbApiBuilder;
189172
///
190-
/// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
191-
/// network_builder.boot_async(|| async {
173+
/// #[tokio::main]
174+
/// async fn main() {
175+
/// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized");
176+
/// let network = unsafe { network_builder.boot() };
192177
/// // do some interesting things with the API...
193-
/// });
178+
/// drop(network);
179+
/// }
194180
/// ```
195-
pub async fn boot_async<F, Fut, T>(self, f: F) -> FdbResult<T>
196-
where
197-
F: (FnOnce() -> Fut) + panic::UnwindSafe,
198-
Fut: Future<Output = T> + panic::UnwindSafe,
199-
{
181+
pub unsafe fn boot(self) -> FdbResult<NetworkAutoStop> {
200182
let (runner, cond) = self.build()?;
201183

202-
let net_thread = thread::spawn(move || {
203-
unsafe { runner.run() }.expect("failed to run");
204-
});
184+
let net_thread = runner.spawn();
205185

206186
let network = cond.wait();
207187

208-
let res = panic::catch_unwind(f);
209-
let res = match res {
210-
Ok(fut) => fut.catch_unwind().await,
211-
Err(err) => Err(err),
212-
};
213-
214-
if let Err(err) = network.stop() {
215-
eprintln!("failed to stop network: {}", err);
216-
// Not aborting can probably cause undefined behavior
217-
std::process::abort();
218-
}
219-
net_thread.join().expect("failed to join fdb thread");
220-
221-
match res {
222-
Err(payload) => panic::resume_unwind(payload),
223-
Ok(v) => Ok(v),
224-
}
188+
Ok(NetworkAutoStop {
189+
handle: Some(net_thread),
190+
network: Some(network),
191+
})
225192
}
226193
}
227194

228195
/// A foundationDB network event loop runner
229196
///
230-
/// Most of the time you should never need to use this directly and use `NetworkBuilder::boot()`.
197+
/// Most of the time you should never need to use this directly and use `NetworkBuilder::run()`.
231198
pub struct NetworkRunner {
232199
cond: Arc<(Mutex<bool>, Condvar)>,
233200
}
@@ -257,11 +224,17 @@ impl NetworkRunner {
257224

258225
error::eval(unsafe { fdb_sys::fdb_run_network() })
259226
}
227+
228+
unsafe fn spawn(self) -> thread::JoinHandle<()> {
229+
thread::spawn(move || {
230+
self.run().expect("failed to run network thread");
231+
})
232+
}
260233
}
261234

262235
/// A condition object that can wait for the associated `NetworkRunner` to actually run.
263236
///
264-
/// Most of the time you should never need to use this directly and use `NetworkBuilder::boot()`.
237+
/// Most of the time you should never need to use this directly and use `NetworkBuilder::run()`.
265238
pub struct NetworkWait {
266239
cond: Arc<(Mutex<bool>, Condvar)>,
267240
}
@@ -284,7 +257,7 @@ impl NetworkWait {
284257

285258
/// Allow to stop the associated and running `NetworkRunner`.
286259
///
287-
/// Most of the time you should never need to use this directly and use `NetworkBuilder::boot()`.
260+
/// Most of the time you should never need to use this directly and use `NetworkBuilder::run()`.
288261
pub struct NetworkStop {
289262
_private: (),
290263
}
@@ -296,6 +269,26 @@ impl NetworkStop {
296269
}
297270
}
298271

272+
/// Stop the associated `NetworkRunner` and thread if dropped
273+
pub struct NetworkAutoStop {
274+
network: Option<NetworkStop>,
275+
handle: Option<std::thread::JoinHandle<()>>,
276+
}
277+
impl Drop for NetworkAutoStop {
278+
fn drop(&mut self) {
279+
if let Err(err) = self.network.take().unwrap().stop() {
280+
eprintln!("failed to stop network: {}", err);
281+
// Not aborting can probably cause undefined behavior
282+
std::process::abort();
283+
}
284+
self.handle
285+
.take()
286+
.unwrap()
287+
.join()
288+
.expect("failed to join fdb thread");
289+
}
290+
}
291+
299292
#[cfg(test)]
300293
mod tests {
301294
use super::*;

0 commit comments

Comments
 (0)