diff --git a/foundationdb-bench/src/bin/fdb-bench.rs b/foundationdb-bench/src/bin/fdb-bench.rs index c059afee..a52ae157 100644 --- a/foundationdb-bench/src/bin/fdb-bench.rs +++ b/foundationdb-bench/src/bin/fdb-bench.rs @@ -146,13 +146,12 @@ fn main() { let opt = Opt::from_args(); info!("opt: {:?}", opt); - fdb::boot(|| { - let db = Arc::new( - futures::executor::block_on(fdb::Database::new_compat(None)) - .expect("failed to get database"), - ); - - let bench = Bench { db, opt }; - bench.run(); - }); + let _guard = unsafe { foundationdb::boot() }; + let db = Arc::new( + futures::executor::block_on(fdb::Database::new_compat(None)) + .expect("failed to get database"), + ); + + let bench = Bench { db, opt }; + bench.run(); } diff --git a/foundationdb-bindingtester/src/main.rs b/foundationdb-bindingtester/src/main.rs index 92662bcc..69f38e03 100644 --- a/foundationdb-bindingtester/src/main.rs +++ b/foundationdb-bindingtester/src/main.rs @@ -1582,23 +1582,22 @@ fn main() { "Starting rust bindingtester with api_version {}", api_version ); - api::FdbApiBuilder::default() + let builder = api::FdbApiBuilder::default() .set_runtime_version(api_version) .build() - .expect("failed to initialize FoundationDB API") - .boot(|| { - let db = Arc::new( - futures::executor::block_on(fdb::Database::new_compat(cluster_path)) - .expect("failed to get database"), - ); - - let mut sm = StackMachine::new(&db, Bytes::from(prefix.to_owned().into_bytes())); - futures::executor::block_on(sm.run(db)).unwrap(); - sm.join(); - - info!("Closing..."); - }) - .expect("failed to initialize FoundationDB network thread"); + .expect("failed to initialize FoundationDB API"); + let _network = unsafe { builder.boot() }; + + let db = Arc::new( + futures::executor::block_on(fdb::Database::new_compat(cluster_path)) + .expect("failed to get database"), + ); + + let mut sm = StackMachine::new(&db, Bytes::from(prefix.to_owned().into_bytes())); + futures::executor::block_on(sm.run(db)).unwrap(); + sm.join(); + + info!("Closing..."); info!("Done."); } diff --git a/foundationdb/README.md b/foundationdb/README.md index bc5e6cb5..cc410f07 100644 --- a/foundationdb/README.md +++ b/foundationdb/README.md @@ -65,11 +65,48 @@ async fn async_main() -> foundationdb::FdbResult<()> { Ok(()) } -foundationdb::boot(|| { +foundationdb::run(|| { futures::executor::block_on(async_main()).expect("failed to run"); }); ``` +## Migration from 0.4 to 0.5 + +The initialization of foundationdb API has changed due to undefined behavior being possible with only safe code (issues #170, #181, pulls #179, #182). + +Previously you had to wrote: + +```rust +let network = foundationdb::boot().expect("failed to initialize Fdb"); + +futures::executor::block_on(async_main()).expect("failed to run"); +// cleanly shutdown the client +drop(network); +``` + +This can be converted to: + +```rust +foundationdb::boot(|| { + futures::executor::block_on(async_main()).expect("failed to run"); +}).expect("failed to boot fdb"); +``` + +or + +```rust +#[tokio::main] +async fn main() { + // Safe because drop is called before the program exits + let network = unsafe { foundationdb::boot() }.expect("failed to initialize Fdb"); + + // Have fun with the FDB API + + // shutdown the client + drop(network); +} +``` + ## API stability _WARNING_ Until the 1.0 release of this library, the API may be in constant flux. diff --git a/foundationdb/examples/class-scheduling.rs b/foundationdb/examples/class-scheduling.rs index 92f0b5b7..c0a80cfa 100644 --- a/foundationdb/examples/class-scheduling.rs +++ b/foundationdb/examples/class-scheduling.rs @@ -364,11 +364,10 @@ async fn run_sim(db: &Database, students: usize, ops_per_student: usize) { } fn main() { - fdb::boot(|| { - let db = futures::executor::block_on(fdb::Database::new_compat(None)) - .expect("failed to get database"); - futures::executor::block_on(init(&db, &*ALL_CLASSES)); - println!("Initialized"); - futures::executor::block_on(run_sim(&db, 10, 10)); - }); + let _guard = unsafe { fdb::boot() }; + let db = futures::executor::block_on(fdb::Database::new_compat(None)) + .expect("failed to get database"); + futures::executor::block_on(init(&db, &*ALL_CLASSES)); + println!("Initialized"); + futures::executor::block_on(run_sim(&db, 10, 10)); } diff --git a/foundationdb/src/api.rs b/foundationdb/src/api.rs index c6f5ecd6..afe48229 100644 --- a/foundationdb/src/api.rs +++ b/foundationdb/src/api.rs @@ -18,8 +18,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::thread; -use futures::prelude::*; - use crate::options::NetworkOption; use crate::{error, FdbResult}; use foundationdb_sys as fdb_sys; @@ -92,9 +90,9 @@ impl Default for FdbApiBuilder { /// use foundationdb::api::FdbApiBuilder; /// /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized"); -/// network_builder.boot(|| { -/// // do some work with foundationDB -/// }).expect("fdb network to run"); +/// let guard = unsafe { network_builder.boot() }; +/// // do some work with foundationDB +/// drop(guard); /// ``` pub struct NetworkBuilder { _private: (), @@ -110,7 +108,7 @@ impl NetworkBuilder { /// Finalizes the initialization of the Network /// /// It's not recommended to use this method unless you really know what you are doing. - /// Otherwise, the `boot` method is the **safe** and easiest way to do it. + /// Otherwise, the `run` method is the **safe** and easiest way to do it. /// /// In order to start the network you have to call the unsafe `NetworkRunner::run()` method. /// This method starts the foundationdb network runloop, once started, the `NetworkStop::stop()` @@ -144,7 +142,19 @@ impl NetworkBuilder { Ok((NetworkRunner { cond: cond.clone() }, NetworkWait { cond })) } - /// Execute `f` with the FoundationDB Client API ready, this can only be called once per process. + /// Initialize the FoundationDB Client API, this can only be called once per process. + /// + /// # Returns + /// + /// A `NetworkAutoStop` handle which must be dropped before the program exits. + /// + /// # Safety + /// + /// This method used to be safe in version `0.4`. But because `drop` on the returned object + /// might not be called before the program exits, it was found unsafe. + /// You should prefer the safe `run` variant. + /// If you still want to use this, you *MUST* ensure drop is called on the returned object + /// before the program exits. This is not required if the program is aborted. /// /// # Examples /// @@ -152,82 +162,39 @@ impl NetworkBuilder { /// use foundationdb::api::FdbApiBuilder; /// /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized"); - /// network_builder.boot(|| { - /// // do some interesting things with the API... - /// }); + /// let network = unsafe { network_builder.boot() }; + /// // do some interesting things with the API... + /// drop(network); /// ``` - pub fn boot(self, f: impl (FnOnce() -> T) + panic::UnwindSafe) -> FdbResult { - let (runner, cond) = self.build()?; - - let net_thread = thread::spawn(move || { - unsafe { runner.run() }.expect("failed to run"); - }); - - let network = cond.wait(); - - let res = panic::catch_unwind(f); - - if let Err(err) = network.stop() { - eprintln!("failed to stop network: {}", err); - // Not aborting can probably cause undefined behavior - std::process::abort(); - } - net_thread.join().expect("failed to join fdb thread"); - - match res { - Err(payload) => panic::resume_unwind(payload), - Ok(v) => Ok(v), - } - } - - /// Async execute `f` with the FoundationDB Client API ready, this can only be called once per process. - /// - /// # Examples /// /// ```rust /// use foundationdb::api::FdbApiBuilder; /// - /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized"); - /// network_builder.boot_async(|| async { + /// #[tokio::main] + /// async fn main() { + /// let network_builder = FdbApiBuilder::default().build().expect("fdb api initialized"); + /// let network = unsafe { network_builder.boot() }; /// // do some interesting things with the API... - /// }); + /// drop(network); + /// } /// ``` - pub async fn boot_async(self, f: F) -> FdbResult - where - F: (FnOnce() -> Fut) + panic::UnwindSafe, - Fut: Future + panic::UnwindSafe, - { + pub unsafe fn boot(self) -> FdbResult { let (runner, cond) = self.build()?; - let net_thread = thread::spawn(move || { - unsafe { runner.run() }.expect("failed to run"); - }); + let net_thread = runner.spawn(); let network = cond.wait(); - let res = panic::catch_unwind(f); - let res = match res { - Ok(fut) => fut.catch_unwind().await, - Err(err) => Err(err), - }; - - if let Err(err) = network.stop() { - eprintln!("failed to stop network: {}", err); - // Not aborting can probably cause undefined behavior - std::process::abort(); - } - net_thread.join().expect("failed to join fdb thread"); - - match res { - Err(payload) => panic::resume_unwind(payload), - Ok(v) => Ok(v), - } + Ok(NetworkAutoStop { + handle: Some(net_thread), + network: Some(network), + }) } } /// A foundationDB network event loop runner /// -/// Most of the time you should never need to use this directly and use `NetworkBuilder::boot()`. +/// Most of the time you should never need to use this directly and use `NetworkBuilder::run()`. pub struct NetworkRunner { cond: Arc<(Mutex, Condvar)>, } @@ -257,11 +224,17 @@ impl NetworkRunner { error::eval(unsafe { fdb_sys::fdb_run_network() }) } + + unsafe fn spawn(self) -> thread::JoinHandle<()> { + thread::spawn(move || { + self.run().expect("failed to run network thread"); + }) + } } /// A condition object that can wait for the associated `NetworkRunner` to actually run. /// -/// Most of the time you should never need to use this directly and use `NetworkBuilder::boot()`. +/// Most of the time you should never need to use this directly and use `NetworkBuilder::run()`. pub struct NetworkWait { cond: Arc<(Mutex, Condvar)>, } @@ -284,7 +257,7 @@ impl NetworkWait { /// Allow to stop the associated and running `NetworkRunner`. /// -/// Most of the time you should never need to use this directly and use `NetworkBuilder::boot()`. +/// Most of the time you should never need to use this directly and use `NetworkBuilder::run()`. pub struct NetworkStop { _private: (), } @@ -296,6 +269,26 @@ impl NetworkStop { } } +/// Stop the associated `NetworkRunner` and thread if dropped +pub struct NetworkAutoStop { + network: Option, + handle: Option>, +} +impl Drop for NetworkAutoStop { + fn drop(&mut self) { + if let Err(err) = self.network.take().unwrap().stop() { + eprintln!("failed to stop network: {}", err); + // Not aborting can probably cause undefined behavior + std::process::abort(); + } + self.handle + .take() + .unwrap() + .join() + .expect("failed to join fdb thread"); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/foundationdb/src/lib.rs b/foundationdb/src/lib.rs index bbdfc9b2..e70ec5f1 100644 --- a/foundationdb/src/lib.rs +++ b/foundationdb/src/lib.rs @@ -18,28 +18,28 @@ //! - Ubuntu Linux (this may work on the Linux subsystem for Windows as well) //! //! ```console -//! $> curl -O https://www.foundationdb.org/downloads/6.1.12/ubuntu/installers/foundationdb-clients_6.1.12-1_amd64.deb -//! $> curl -O https://www.foundationdb.org/downloads/6.1.12/ubuntu/installers/foundationdb-server_6.1.12-1_amd64.deb -//! $> sudo dpkg -i foundationdb-clients_6.1.12-1_amd64.deb -//! $> sudo dpkg -i foundationdb-server_6.1.12-1_amd64.deb +//! $> curl -O https://www.foundationdb.org/downloads/6.2.25/ubuntu/installers/foundationdb-clients_6.2.25-1_amd64.deb +//! $> curl -O https://www.foundationdb.org/downloads/6.2.25/ubuntu/installers/foundationdb-server_6.2.25-1_amd64.deb +//! $> sudo dpkg -i foundationdb-clients_6.2.25-1_amd64.deb +//! $> sudo dpkg -i foundationdb-server_6.2.25-1_amd64.deb //! ``` //! //! - macOS //! //! ```console -//! $> curl -O https://www.foundationdb.org/downloads/6.1.12/macOS/installers/FoundationDB-6.1.12.pkg -//! $> sudo installer -pkg FoundationDB-6.1.12.pkg -target / +//! $> curl -O https://www.foundationdb.org/downloads/6.2.25/macOS/installers/FoundationDB-6.2.25.pkg +//! $> sudo installer -pkg FoundationDB-6.2.25.pkg -target / //! ``` //! //! - Windows //! -//! Install [foundationdb-6.1.12-x64.msi](https://www.foundationdb.org/downloads/6.1.12/windows/installers/foundationdb-6.1.12-x64.msi) +//! Install [foundationdb-6.2.25-x64.msi](https://www.foundationdb.org/downloads/6.2.25/windows/installers/foundationdb-6.2.25-x64.msi) //! //! ## Add dependencies on foundationdb-rs //! //! ```toml //! [dependencies] -//! foundationdb = "0.4" +//! foundationdb = "0.5" //! futures = "0.3" //! ``` //! @@ -70,9 +70,23 @@ //! Ok(()) //! } //! -//! foundationdb::boot(|| { -//! futures::executor::block_on(async_main()).expect("failed to run"); -//! }); +//! // Safe because drop is called before the program exits +//! let network = unsafe { foundationdb::boot() }; +//! futures::executor::block_on(async_main()).expect("failed to run"); +//! drop(network); +//! ``` +//! +//! ```rust +//! #[tokio::main] +//! async fn main() { +//! // Safe because drop is called before the program exits +//! let network = unsafe { foundationdb::boot() }; +//! +//! // Have fun with the FDB API +//! +//! // shutdown the client +//! drop(network); +//! } //! ``` //! //! ## API stability @@ -104,43 +118,41 @@ pub use crate::error::FdbResult; pub use crate::keyselector::*; pub use crate::transaction::*; -/// Execute `f` with the FoundationDB Client API ready, this can only be called once per process. +/// Initialize the FoundationDB Client API, this can only be called once per process. +/// +/// # Returns +/// +/// A `NetworkAutoStop` handle which must be dropped before the program exits. +/// +/// # Safety +/// +/// This method used to be safe in version `0.4`. But because `drop` on the returned object +/// might not be called before the program exits, it was found unsafe. +/// You should prefer the safe `run` variant. +/// If you still want to use this, you *MUST* ensure drop is called on the returned object +/// before the program exits. This is not required if the program is aborted. /// /// # Examples /// /// ```rust -/// foundationdb::boot(|| { -/// // do some interesting things with the API... -/// }); +/// let network = unsafe { foundationdb::boot() }; +/// // do some interesting things with the API... +/// drop(network); /// ``` -pub fn boot(f: impl (FnOnce() -> T) + std::panic::UnwindSafe) -> T { - api::FdbApiBuilder::default() - .build() - .expect("foundationdb API to be initialized") - .boot(f) - .expect("foundationdb network to be setup") -} - -/// Async execute `f` with the FoundationDB Client API ready, this can only be called once per process. -/// -/// # Examples /// /// ```rust -/// foundationdb::boot_async(|| async { +/// #[tokio::main] +/// async fn main() { +/// let network = unsafe { foundationdb::boot() }; /// // do some interesting things with the API... -/// }); +/// drop(network); +/// } /// ``` -pub async fn boot_async(f: F) -> T -where - F: (FnOnce() -> Fut) + std::panic::UnwindSafe, - Fut: std::future::Future + std::panic::UnwindSafe, -{ - api::FdbApiBuilder::default() +pub unsafe fn boot() -> api::NetworkAutoStop { + let network_builder = api::FdbApiBuilder::default() .build() - .expect("foundationdb API to be initialized") - .boot_async(f) - .await - .expect("foundationdb network to be setup") + .expect("foundationdb API to be initialized"); + network_builder.boot().expect("fdb network running") } /// Returns the default Fdb cluster configuration file path diff --git a/foundationdb/tests/api_ub.rs b/foundationdb/tests/api_ub.rs new file mode 100644 index 00000000..27de181e --- /dev/null +++ b/foundationdb/tests/api_ub.rs @@ -0,0 +1,20 @@ +use std::panic; + +#[test] +#[ignore] +fn test_run() { + let old = panic::take_hook(); + panic::set_hook(Box::new(|_| {})); + let mut db = None; + let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { + // Run the foundationdb client API + let _drop_me = unsafe { foundationdb::boot() }; + db = Some(futures::executor::block_on(foundationdb::Database::new_compat(None)).unwrap()); + // Try to escape via unwind + panic!("UNWIND!") + })); + assert!(result.is_err()); + let trx = db.unwrap().create_trx().unwrap(); + let _err = futures::executor::block_on(trx.get_read_version()).unwrap_err(); + panic::set_hook(old); +} diff --git a/foundationdb/tests/atomic.rs b/foundationdb/tests/atomic.rs index 0f13b332..bdede1da 100644 --- a/foundationdb/tests/atomic.rs +++ b/foundationdb/tests/atomic.rs @@ -12,7 +12,8 @@ mod common; #[test] fn test_atomic() { - boot(|| futures::executor::block_on(test_atomic_async()).expect("failed to run")); + let _guard = unsafe { foundationdb::boot() }; + futures::executor::block_on(test_atomic_async()).expect("failed to run"); } async fn atomic_add(db: &Database, key: &[u8], value: i64) -> FdbResult<()> { diff --git a/foundationdb/tests/future.rs b/foundationdb/tests/future.rs index 755335e8..ae57ea3b 100644 --- a/foundationdb/tests/future.rs +++ b/foundationdb/tests/future.rs @@ -32,7 +32,8 @@ where #[test] fn test_future_discard() { - boot(|| futures::executor::block_on(test_future_discard_async()).expect("failed to run")); + let _guard = unsafe { foundationdb::boot() }; + futures::executor::block_on(test_future_discard_async()).expect("failed to run"); } async fn test_future_discard_async() -> FdbResult<()> { diff --git a/foundationdb/tests/get.rs b/foundationdb/tests/get.rs index be44a2d1..2b4cab14 100644 --- a/foundationdb/tests/get.rs +++ b/foundationdb/tests/get.rs @@ -13,19 +13,18 @@ mod common; #[test] fn test_get() { - boot(|| { - futures::executor::block_on(test_set_get_async()).expect("failed to run"); - futures::executor::block_on(test_get_multi_async()).expect("failed to run"); - futures::executor::block_on(test_set_conflict_async()).expect("failed to run"); - futures::executor::block_on(test_set_conflict_snapshot_async()).expect("failed to run"); - futures::executor::block_on(test_transact_async()).expect("failed to run"); - futures::executor::block_on(test_transact_limit()).expect("failed to run"); - futures::executor::block_on(test_transact_timeout()).expect("failed to run"); - futures::executor::block_on(test_versionstamp_async()).expect("failed to run"); - futures::executor::block_on(test_read_version_async()).expect("failed to run"); - futures::executor::block_on(test_set_read_version_async()).expect("failed to run"); - futures::executor::block_on(test_get_addresses_for_key_async()).expect("failed to run"); - }); + let _guard = unsafe { foundationdb::boot() }; + futures::executor::block_on(test_set_get_async()).expect("failed to run"); + futures::executor::block_on(test_get_multi_async()).expect("failed to run"); + futures::executor::block_on(test_set_conflict_async()).expect("failed to run"); + futures::executor::block_on(test_set_conflict_snapshot_async()).expect("failed to run"); + futures::executor::block_on(test_transact_async()).expect("failed to run"); + futures::executor::block_on(test_transact_limit()).expect("failed to run"); + futures::executor::block_on(test_transact_timeout()).expect("failed to run"); + futures::executor::block_on(test_versionstamp_async()).expect("failed to run"); + futures::executor::block_on(test_read_version_async()).expect("failed to run"); + futures::executor::block_on(test_set_read_version_async()).expect("failed to run"); + futures::executor::block_on(test_get_addresses_for_key_async()).expect("failed to run"); } async fn test_set_get_async() -> FdbResult<()> { diff --git a/foundationdb/tests/hca.rs b/foundationdb/tests/hca.rs index 43458232..d1be639f 100644 --- a/foundationdb/tests/hca.rs +++ b/foundationdb/tests/hca.rs @@ -16,12 +16,10 @@ mod common; #[test] fn test_hca_many_sequential_allocations() { - foundationdb::boot(|| { - futures::executor::block_on(test_hca_many_sequential_allocations_async()) - .expect("failed to run"); - futures::executor::block_on(test_hca_concurrent_allocations_async()) - .expect("failed to run"); - }); + let _guard = unsafe { foundationdb::boot() }; + futures::executor::block_on(test_hca_many_sequential_allocations_async()) + .expect("failed to run"); + futures::executor::block_on(test_hca_concurrent_allocations_async()).expect("failed to run"); } async fn test_hca_many_sequential_allocations_async() -> FdbResult<()> { diff --git a/foundationdb/tests/range.rs b/foundationdb/tests/range.rs index 9cf636ac..884ddafe 100644 --- a/foundationdb/tests/range.rs +++ b/foundationdb/tests/range.rs @@ -14,11 +14,10 @@ mod common; #[test] fn test_range() { - foundationdb::boot(|| { - futures::executor::block_on(test_get_range_async()).expect("failed to run"); - futures::executor::block_on(test_range_option_async()).expect("failed to run"); - futures::executor::block_on(test_get_ranges_async()).expect("failed to run"); - }); + let _guard = unsafe { foundationdb::boot() }; + futures::executor::block_on(test_get_range_async()).expect("failed to run"); + futures::executor::block_on(test_range_option_async()).expect("failed to run"); + futures::executor::block_on(test_get_ranges_async()).expect("failed to run"); } async fn test_get_range_async() -> FdbResult<()> { diff --git a/foundationdb/tests/tokio.rs b/foundationdb/tests/tokio.rs index a91dcbdd..0f547c37 100644 --- a/foundationdb/tests/tokio.rs +++ b/foundationdb/tests/tokio.rs @@ -7,12 +7,11 @@ mod common; #[test] fn test_tokio_send() { - boot(|| { - let mut rt = Runtime::new().unwrap(); - rt.block_on(async { - do_transact().await; - do_trx().await; - }); + let _guard = unsafe { foundationdb::boot() }; + let mut rt = Runtime::new().unwrap(); + rt.block_on(async { + do_transact().await; + do_trx().await; }); } diff --git a/foundationdb/tests/tokio_async.rs b/foundationdb/tests/tokio_async.rs deleted file mode 100644 index b016b11a..00000000 --- a/foundationdb/tests/tokio_async.rs +++ /dev/null @@ -1,54 +0,0 @@ -use foundationdb::*; -use futures::prelude::*; -use std::sync::Arc; - -mod common; - -#[tokio::test] -async fn test_tokio_send() { - boot_async(|| async { - do_transact().await; - do_trx().await; - }) - .await -} - -async fn do_transact() { - let db = Arc::new( - foundationdb::Database::new_compat(None) - .await - .expect("failed to open fdb"), - ); - - let adb = db.clone(); - tokio::spawn(async move { - async fn txnfn(_txn: &Transaction) -> FdbResult<()> { - Ok(()) - } - - adb.transact_boxed( - (), - |txn: &Transaction, ()| txnfn(txn).boxed(), - TransactOption::default(), - ) - .await - .expect("failed to transact") - }); -} - -async fn do_trx() { - let db = Arc::new( - foundationdb::Database::new_compat(None) - .await - .expect("failed to open fdb"), - ); - - let adb = db.clone(); - tokio::spawn(async move { - adb.create_trx() - .expect("failed to create trx") - .commit() - .await - .expect("failed to commit"); - }); -} diff --git a/foundationdb/tests/watch.rs b/foundationdb/tests/watch.rs index 2bf3ee96..d44a94dd 100644 --- a/foundationdb/tests/watch.rs +++ b/foundationdb/tests/watch.rs @@ -11,10 +11,9 @@ mod common; #[test] fn test_watch() { - boot(|| { - futures::executor::block_on(test_watch_async()).expect("failed to run"); - futures::executor::block_on(test_watch_without_commit_async()).expect("failed to run"); - }); + let _guard = unsafe { foundationdb::boot() }; + futures::executor::block_on(test_watch_async()).expect("failed to run"); + futures::executor::block_on(test_watch_without_commit_async()).expect("failed to run"); } async fn test_watch_async() -> FdbResult<()> {