From c98d91333c34b014c822e22bad20430a43d6593a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20M=C3=BCller?= Date: Sat, 27 Jul 2024 05:08:52 +0200 Subject: [PATCH 1/7] Restrict Env type to prepare using it in place of ConcreteEnv --- storage/database/src/env.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/database/src/env.rs b/storage/database/src/env.rs index cae497330..e93e1be21 100644 --- a/storage/database/src/env.rs +++ b/storage/database/src/env.rs @@ -61,7 +61,7 @@ pub trait Env: Sized { // For `heed`, this is just `heed::Env`, for `redb` this is // `(redb::Database, redb::Durability)` as each transaction // needs the sync mode set during creation. - type EnvInner<'env>: EnvInner<'env> + type EnvInner<'env>: EnvInner<'env> + Sync where Self: 'env; @@ -209,7 +209,7 @@ pub trait EnvInner<'env> { /// The read-only transaction type of the backend. /// /// `'tx` is the lifetime of the transaction itself. - type Ro<'tx>: TxRo<'tx>; + type Ro<'tx>: TxRo<'tx> + Send; /// The read-write transaction type of the backend. /// /// `'tx` is the lifetime of the transaction itself. From 990e9f2353856777efe5caaa1051cc9433d7c4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20M=C3=BCller?= Date: Sat, 27 Jul 2024 05:09:44 +0200 Subject: [PATCH 2/7] Make functions in service/read.rs generic --- storage/blockchain/src/service/read.rs | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 3f0b26338..f73b64fba 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -177,7 +177,7 @@ impl tower::Service for DatabaseReadHandle { let env = Arc::clone(&self.env); self.pool.spawn(move || { let _permit: OwnedSemaphorePermit = permit; - map_request(&env, request, response_sender); + map_request(&*env, request, response_sender); }); // drop(permit/env); InfallibleOneshotReceiver::from(receiver) @@ -195,8 +195,8 @@ impl tower::Service for DatabaseReadHandle { /// 1. `Request` is mapped to a handler function /// 2. Handler function is called /// 3. [`BCResponse`] is sent -fn map_request( - env: &ConcreteEnv, // Access to the database +fn map_request( + env: &E, // Access to the database request: BCReadRequest, // The request we must fulfill response_sender: ResponseSender, // The channel we must send the response back to ) { @@ -299,7 +299,7 @@ macro_rules! get_tables { /// [`BCReadRequest::BlockExtendedHeader`]. #[inline] -fn block_extended_header(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult { +fn block_extended_header(env: &E, block_height: BlockHeight) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; @@ -312,7 +312,7 @@ fn block_extended_header(env: &ConcreteEnv, block_height: BlockHeight) -> Respon /// [`BCReadRequest::BlockHash`]. #[inline] -fn block_hash(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult { +fn block_hash(env: &E, block_height: BlockHeight) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; @@ -325,7 +325,7 @@ fn block_hash(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult { /// [`BCReadRequest::FilterUnknownHashes`]. #[inline] -fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet) -> ResponseResult { +fn filter_unknown_hashes(env: &E, mut hashes: HashSet) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; @@ -353,8 +353,8 @@ fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet) -> R /// [`BCReadRequest::BlockExtendedHeaderInRange`]. #[inline] -fn block_extended_header_in_range( - env: &ConcreteEnv, +fn block_extended_header_in_range( + env: &E, range: std::ops::Range, ) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. @@ -377,7 +377,7 @@ fn block_extended_header_in_range( /// [`BCReadRequest::ChainHeight`]. #[inline] -fn chain_height(env: &ConcreteEnv) -> ResponseResult { +fn chain_height(env: &E) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; @@ -393,7 +393,7 @@ fn chain_height(env: &ConcreteEnv) -> ResponseResult { /// [`BCReadRequest::GeneratedCoins`]. #[inline] -fn generated_coins(env: &ConcreteEnv) -> ResponseResult { +fn generated_coins(env: &E) -> ResponseResult { // Single-threaded, no `ThreadLocal` required. let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; @@ -410,7 +410,7 @@ fn generated_coins(env: &ConcreteEnv) -> ResponseResult { /// [`BCReadRequest::Outputs`]. #[inline] -fn outputs(env: &ConcreteEnv, outputs: HashMap>) -> ResponseResult { +fn outputs(env: &E, outputs: HashMap>) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. let env_inner = env.env_inner(); let tx_ro = thread_local(env); @@ -451,7 +451,7 @@ fn outputs(env: &ConcreteEnv, outputs: HashMap>) -> /// [`BCReadRequest::NumberOutputsWithAmount`]. #[inline] -fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> ResponseResult { +fn number_outputs_with_amount(env: &E, amounts: Vec) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. let env_inner = env.env_inner(); let tx_ro = thread_local(env); @@ -496,7 +496,7 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec) -> Respon /// [`BCReadRequest::KeyImagesSpent`]. #[inline] -fn key_images_spent(env: &ConcreteEnv, key_images: HashSet) -> ResponseResult { +fn key_images_spent(env: &E, key_images: HashSet) -> ResponseResult { // Prepare tx/tables in `ThreadLocal`. let env_inner = env.env_inner(); let tx_ro = thread_local(env); @@ -532,7 +532,7 @@ fn key_images_spent(env: &ConcreteEnv, key_images: HashSet) -> Respons } /// [`BCReadRequest::CompactChainHistory`] -fn compact_chain_history(env: &ConcreteEnv) -> ResponseResult { +fn compact_chain_history(env: &E) -> ResponseResult { let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; @@ -573,7 +573,7 @@ fn compact_chain_history(env: &ConcreteEnv) -> ResponseResult { /// `block_ids` must be sorted in chronological block order, or else /// the returned result is unspecified and meaningless, as this function /// performs a binary search. -fn find_first_unknown(env: &ConcreteEnv, block_ids: &[BlockHash]) -> ResponseResult { +fn find_first_unknown(env: &E, block_ids: &[BlockHash]) -> ResponseResult { let env_inner = env.env_inner(); let tx_ro = env_inner.tx_ro()?; From 732e8e1d1d1de32c518b66a382e14c99b38423cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20M=C3=BCller?= Date: Tue, 30 Jul 2024 02:10:34 +0200 Subject: [PATCH 3/7] Implement hot-swapping --- storage/blockchain/src/config/backend.rs | 22 +++--- storage/blockchain/src/config/config.rs | 6 +- storage/blockchain/src/config/mod.rs | 3 + storage/blockchain/src/free.rs | 6 +- storage/blockchain/src/service/free.rs | 20 ++++- storage/blockchain/src/service/read.rs | 97 ++++++++++++++---------- storage/blockchain/src/service/write.rs | 24 +++--- storage/database/src/backend/mod.rs | 17 ++++- storage/database/src/lib.rs | 7 ++ 9 files changed, 131 insertions(+), 71 deletions(-) diff --git a/storage/blockchain/src/config/backend.rs b/storage/blockchain/src/config/backend.rs index ee72b3dfa..566a50f60 100644 --- a/storage/blockchain/src/config/backend.rs +++ b/storage/blockchain/src/config/backend.rs @@ -1,22 +1,22 @@ //! SOMEDAY //---------------------------------------------------------------------------------------------------- Import -use std::{ - borrow::Cow, - num::NonZeroUsize, - path::{Path, PathBuf}, -}; +//use std::{ +// borrow::Cow, +// num::NonZeroUsize, +// path::{Path, PathBuf}, +//}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use cuprate_helper::fs::cuprate_blockchain_dir; +//use cuprate_helper::fs::cuprate_blockchain_dir; -use crate::{ - config::{ReaderThreads, SyncMode}, - constants::DATABASE_DATA_FILENAME, - resize::ResizeAlgorithm, -}; +//use crate::{ +// config::{ReaderThreads, SyncMode}, +// constants::DATABASE_DATA_FILENAME, +// resize::ResizeAlgorithm, +//}; //---------------------------------------------------------------------------------------------------- Backend /// SOMEDAY: allow runtime hot-swappable backends. diff --git a/storage/blockchain/src/config/config.rs b/storage/blockchain/src/config/config.rs index c58e292a0..fbf5cb11a 100644 --- a/storage/blockchain/src/config/config.rs +++ b/storage/blockchain/src/config/config.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use cuprate_database::{config::SyncMode, resize::ResizeAlgorithm}; use cuprate_helper::fs::cuprate_blockchain_dir; -use crate::config::ReaderThreads; +use crate::config::{Backend, ReaderThreads}; //---------------------------------------------------------------------------------------------------- ConfigBuilder /// Builder for [`Config`]. @@ -65,6 +65,7 @@ impl ConfigBuilder { .build(); Config { + backend: Backend::default(), db_config, reader_threads, } @@ -149,6 +150,9 @@ impl Default for ConfigBuilder { #[derive(Debug, Clone, PartialEq, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Config { + /// The database backend. + pub backend: Backend, + /// The database configuration. pub db_config: cuprate_database::config::Config, diff --git a/storage/blockchain/src/config/mod.rs b/storage/blockchain/src/config/mod.rs index 7ecc14c4c..2a81552de 100644 --- a/storage/blockchain/src/config/mod.rs +++ b/storage/blockchain/src/config/mod.rs @@ -45,3 +45,6 @@ pub use config::{Config, ConfigBuilder}; mod reader_threads; pub use reader_threads::ReaderThreads; + +mod backend; +pub use backend::Backend; diff --git a/storage/blockchain/src/free.rs b/storage/blockchain/src/free.rs index 8288e65f7..edd81ef56 100644 --- a/storage/blockchain/src/free.rs +++ b/storage/blockchain/src/free.rs @@ -1,7 +1,7 @@ //! General free functions (related to the database). //---------------------------------------------------------------------------------------------------- Import -use cuprate_database::{ConcreteEnv, Env, EnvInner, InitError, RuntimeError, TxRw}; +use cuprate_database::{Env, EnvInner, InitError, RuntimeError, TxRw}; use crate::{config::Config, tables::OpenTables}; @@ -22,9 +22,9 @@ use crate::{config::Config, tables::OpenTables}; /// - A table could not be created/opened #[cold] #[inline(never)] // only called once -pub fn open(config: Config) -> Result { +pub fn open(config: Config) -> Result { // Attempt to open the database environment. - let env = ::open(config.db_config)?; + let env = E::open(config.db_config)?; /// Convert runtime errors to init errors. /// diff --git a/storage/blockchain/src/service/free.rs b/storage/blockchain/src/service/free.rs index 3701f66f0..bbd498208 100644 --- a/storage/blockchain/src/service/free.rs +++ b/storage/blockchain/src/service/free.rs @@ -3,14 +3,15 @@ //---------------------------------------------------------------------------------------------------- Import use std::sync::Arc; -use cuprate_database::InitError; +use cuprate_database::{Env, InitError}; use crate::{ - config::Config, + config::{Backend, Config}, service::{DatabaseReadHandle, DatabaseWriteHandle}, }; //---------------------------------------------------------------------------------------------------- Init +#[allow(unreachable_patterns)] #[cold] #[inline(never)] // Only called once (?) /// Initialize a database & thread-pool, and return a read/write handle to it. @@ -21,10 +22,22 @@ use crate::{ /// # Errors /// This will forward the error if [`crate::open`] failed. pub fn init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle), InitError> { + match config.backend { + #[cfg(feature = "heed")] + Backend::Heed => do_init::(config), + #[cfg(feature = "redb")] + Backend::Redb => do_init::(config), + _ => panic!("Selected database backend not available in this build."), + } +} + +#[cold] +#[inline(never)] +fn do_init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle), InitError> { let reader_threads = config.reader_threads; // Initialize the database itself. - let db = Arc::new(crate::open(config)?); + let db = Arc::new(crate::open::(config)?); // Spawn the Reader thread pool and Writer. let readers = DatabaseReadHandle::init(&db, reader_threads); @@ -33,6 +46,7 @@ pub fn init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle), Ok((readers, writer)) } + //---------------------------------------------------------------------------------------------------- Compact history /// Given a position in the compact history, returns the height offset that should be in that position. /// diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index f73b64fba..4c81ead5a 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -39,6 +39,54 @@ use crate::{ types::{Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId}, }; +trait SpawnReadTask { + fn spawn(&self, request: BCReadRequest, permit: OwnedSemaphorePermit) -> ResponseReceiver; +} + +#[derive(Clone)] +struct GenericEnvSpawnReadTask { + /// Access to the database. + env: Arc, + + /// Handle to the custom `rayon` DB reader thread-pool. + /// + /// Requests are [`rayon::ThreadPool::spawn`]ed in this thread-pool, + /// and responses are returned via a channel we (the caller) provide. + pool: Arc, +} + +impl GenericEnvSpawnReadTask { + fn new(env: &Arc, pool: Arc) -> Self { + Self { + env: Arc::clone(&env), + pool + } + } +} + +impl SpawnReadTask for GenericEnvSpawnReadTask { + fn spawn(&self, request: BCReadRequest, permit: OwnedSemaphorePermit) -> ResponseReceiver { + // Response channel we `.await` on. + let (response_sender, receiver) = oneshot::channel(); + + // Spawn the request in the rayon DB thread-pool. + // + // Note that this uses `self.pool` instead of `rayon::spawn` + // such that any `rayon` parallel code that runs within + // the passed closure uses the same `rayon` threadpool. + // + // INVARIANT: + // The below `DatabaseReader` function impl block relies on this behavior. + let env = Arc::clone(&self.env); + self.pool.spawn(move || { + let _permit: OwnedSemaphorePermit = permit; + map_request(&*env, request, response_sender); + }); // drop(permit/env); + + InfallibleOneshotReceiver::from(receiver) + } +} + //---------------------------------------------------------------------------------------------------- DatabaseReadHandle /// Read handle to the database. /// @@ -49,12 +97,6 @@ use crate::{ /// will return an `async`hronous channel that can be `.await`ed upon /// to receive the corresponding [`BCResponse`]. pub struct DatabaseReadHandle { - /// Handle to the custom `rayon` DB reader thread-pool. - /// - /// Requests are [`rayon::ThreadPool::spawn`]ed in this thread-pool, - /// and responses are returned via a channel we (the caller) provide. - pool: Arc, - /// Counting semaphore asynchronous permit for database access. /// Each [`tower::Service::poll_ready`] will acquire a permit /// before actually sending a request to the `rayon` DB threadpool. @@ -68,8 +110,7 @@ pub struct DatabaseReadHandle { /// the request, i.e., after [`map_request()`] finishes. permit: Option, - /// Access to the database. - env: Arc, + spawn: Arc, } // `OwnedSemaphorePermit` does not implement `Clone`, @@ -78,10 +119,9 @@ pub struct DatabaseReadHandle { impl Clone for DatabaseReadHandle { fn clone(&self) -> Self { Self { - pool: Arc::clone(&self.pool), semaphore: self.semaphore.clone(), permit: None, - env: Arc::clone(&self.env), + spawn: Arc::clone(&self.spawn), } } } @@ -95,28 +135,26 @@ impl DatabaseReadHandle { /// Should be called _once_ per actual database. #[cold] #[inline(never)] // Only called once. - pub(super) fn init(env: &Arc, reader_threads: ReaderThreads) -> Self { + pub(super) fn init(env: &Arc, reader_threads: ReaderThreads) -> Self { // How many reader threads to spawn? let reader_count = reader_threads.as_threads().get(); // Spawn `rayon` reader threadpool. - let pool = rayon::ThreadPoolBuilder::new() + let pool = Arc::new(rayon::ThreadPoolBuilder::new() .num_threads(reader_count) .thread_name(|i| format!("cuprate_helper::service::read::DatabaseReader{i}")) .build() - .unwrap(); + .unwrap() + ); // Create a semaphore with the same amount of // permits as the amount of reader threads. let semaphore = PollSemaphore::new(Arc::new(Semaphore::new(reader_count))); + let spawn = Arc::new(GenericEnvSpawnReadTask::new(env, pool)); + // Return a handle to the pool. - Self { - pool: Arc::new(pool), - semaphore, - permit: None, - env: Arc::clone(env), - } + Self { semaphore, permit: None, spawn } } /// Access to the actual database environment. @@ -132,7 +170,7 @@ impl DatabaseReadHandle { /// in this manner has not been tested. #[inline] pub const fn env(&self) -> &Arc { - &self.env + unimplemented!(); } } @@ -163,24 +201,7 @@ impl tower::Service for DatabaseReadHandle { .take() .expect("poll_ready() should have acquire a permit before calling call()"); - // Response channel we `.await` on. - let (response_sender, receiver) = oneshot::channel(); - - // Spawn the request in the rayon DB thread-pool. - // - // Note that this uses `self.pool` instead of `rayon::spawn` - // such that any `rayon` parallel code that runs within - // the passed closure uses the same `rayon` threadpool. - // - // INVARIANT: - // The below `DatabaseReader` function impl block relies on this behavior. - let env = Arc::clone(&self.env); - self.pool.spawn(move || { - let _permit: OwnedSemaphorePermit = permit; - map_request(&*env, request, response_sender); - }); // drop(permit/env); - - InfallibleOneshotReceiver::from(receiver) + self.spawn.spawn(request, permit) } } diff --git a/storage/blockchain/src/service/write.rs b/storage/blockchain/src/service/write.rs index 041ae7b66..958ecfef1 100644 --- a/storage/blockchain/src/service/write.rs +++ b/storage/blockchain/src/service/write.rs @@ -8,7 +8,7 @@ use std::{ use futures::channel::oneshot; -use cuprate_database::{ConcreteEnv, Env, EnvInner, RuntimeError, TxRw}; +use cuprate_database::{Env, EnvInner, RuntimeError, TxRw}; use cuprate_helper::asynch::InfallibleOneshotReceiver; use cuprate_types::{ blockchain::{BCResponse, BCWriteRequest}, @@ -46,7 +46,7 @@ impl DatabaseWriteHandle { /// Initialize the single `DatabaseWriter` thread. #[cold] #[inline(never)] // Only called once. - pub(super) fn init(env: Arc) -> Self { + pub(super) fn init(env: Arc) -> Self { // Initialize `Request/Response` channels. let (sender, receiver) = crossbeam::channel::unbounded(); @@ -87,7 +87,7 @@ impl tower::Service for DatabaseWriteHandle { //---------------------------------------------------------------------------------------------------- DatabaseWriter /// The single database writer thread. -pub(super) struct DatabaseWriter { +pub(super) struct DatabaseWriter { /// Receiver side of the database request channel. /// /// Any caller can send some requests to this channel. @@ -96,16 +96,16 @@ pub(super) struct DatabaseWriter { receiver: crossbeam::channel::Receiver<(BCWriteRequest, ResponseSender)>, /// Access to the database. - env: Arc, + env: Arc, } -impl Drop for DatabaseWriter { +impl Drop for DatabaseWriter { fn drop(&mut self) { // TODO: log the writer thread has exited? } } -impl DatabaseWriter { +impl DatabaseWriter { /// The `DatabaseWriter`'s main function. /// /// The writer just loops in this function, handling requests forever @@ -130,10 +130,12 @@ impl DatabaseWriter { return; }; + // FIXME + #[allow(unused_doc_comments, non_snake_case)] /// How many times should we retry handling the request on resize errors? /// /// This is 1 on automatically resizing databases, meaning there is only 1 iteration. - const REQUEST_RETRY_LIMIT: usize = if ConcreteEnv::MANUAL_RESIZE { 3 } else { 1 }; + let REQUEST_RETRY_LIMIT: usize = if E::MANUAL_RESIZE { 3 } else { 1 }; // Map [`Request`]'s to specific database functions. // @@ -151,11 +153,11 @@ impl DatabaseWriter { // FIXME: will there be more than 1 write request? // this won't have to be an enum. let response = match &request { - BCWriteRequest::WriteBlock(block) => write_block(&self.env, block), + BCWriteRequest::WriteBlock(block) => write_block(&*self.env, block), }; // If the database needs to resize, do so. - if ConcreteEnv::MANUAL_RESIZE && matches!(response, Err(RuntimeError::ResizeNeeded)) + if E::MANUAL_RESIZE && matches!(response, Err(RuntimeError::ResizeNeeded)) { // If this is the last iteration of the outer `for` loop and we // encounter a resize error _again_, it means something is wrong. @@ -183,7 +185,7 @@ impl DatabaseWriter { // Automatically resizing databases should not be returning a resize error. #[cfg(debug_assertions)] - if !ConcreteEnv::MANUAL_RESIZE { + if !E::MANUAL_RESIZE { assert!( !matches!(response, Err(RuntimeError::ResizeNeeded)), "auto-resizing database returned a ResizeNeeded error" @@ -218,7 +220,7 @@ impl DatabaseWriter { /// [`BCWriteRequest::WriteBlock`]. #[inline] -fn write_block(env: &ConcreteEnv, block: &VerifiedBlockInformation) -> ResponseResult { +fn write_block(env: &E, block: &VerifiedBlockInformation) -> ResponseResult { let env_inner = env.env_inner(); let tx_rw = env_inner.tx_rw()?; diff --git a/storage/database/src/backend/mod.rs b/storage/database/src/backend/mod.rs index 11ae40b8b..e27501ec1 100644 --- a/storage/database/src/backend/mod.rs +++ b/storage/database/src/backend/mod.rs @@ -1,13 +1,22 @@ //! Database backends. +#[cfg(feature = "heed")] +mod heed; + +#[cfg(feature = "heed")] +pub use heed::ConcreteEnv as HeedEnv; + +#[cfg(feature = "redb")] +mod redb; + +#[cfg(feature = "redb")] +pub use redb::ConcreteEnv as RedbEnv; + cfg_if::cfg_if! { - // If both backends are enabled, fallback to `heed`. - // This is useful when using `--all-features`. + // TODO remove this block when all ConcreteEnv references are gone. if #[cfg(all(feature = "redb", not(feature = "heed")))] { - mod redb; pub use redb::ConcreteEnv; } else { - mod heed; pub use heed::ConcreteEnv; } } diff --git a/storage/database/src/lib.rs b/storage/database/src/lib.rs index da36b0d5b..e467234d0 100644 --- a/storage/database/src/lib.rs +++ b/storage/database/src/lib.rs @@ -121,6 +121,13 @@ pub mod config; pub mod resize; pub use backend::ConcreteEnv; + +#[cfg(feature="heed")] +pub use backend::HeedEnv; + +#[cfg(feature="redb")] +pub use backend::RedbEnv; + pub use constants::{ DATABASE_BACKEND, DATABASE_CORRUPT_MSG, DATABASE_DATA_FILENAME, DATABASE_LOCK_FILENAME, }; From 70511a7f196ae59ce686af8c4d33376d7fad08b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20M=C3=BCller?= Date: Wed, 31 Jul 2024 11:10:39 +0200 Subject: [PATCH 4/7] Fix tests --- storage/blockchain/README.md | 8 ++++++-- storage/blockchain/src/config/mod.rs | 9 +++++++-- storage/blockchain/src/ops/mod.rs | 8 ++++++-- storage/blockchain/src/service/free.rs | 22 ++++++++++++++-------- storage/blockchain/src/service/mod.rs | 1 + storage/blockchain/src/service/tests.rs | 14 ++++++++++---- 6 files changed, 44 insertions(+), 18 deletions(-) diff --git a/storage/blockchain/README.md b/storage/blockchain/README.md index 480054696..594e78b76 100644 --- a/storage/blockchain/README.md +++ b/storage/blockchain/README.md @@ -66,7 +66,6 @@ For examples of the higher-level APIs, see: ```rust use cuprate_blockchain::{ cuprate_database::{ - ConcreteEnv, Env, EnvInner, DatabaseRo, DatabaseRw, TxRo, TxRw, }, @@ -74,6 +73,11 @@ use cuprate_blockchain::{ tables::{Tables, TablesMut, OpenTables}, }; +#[cfg(feature = "heed")] +use cuprate_blockchain::cuprate_database::HeedEnv as ConcreteEnv; +#[cfg(all(feature = "redb", not(feature = "heed")))] +use cuprate_blockchain::cuprate_database::RedbEnv as ConcreteEnv; + # fn main() -> Result<(), Box> { // Create a configuration for the database environment. let tmp_dir = tempfile::tempdir()?; @@ -83,7 +87,7 @@ let config = ConfigBuilder::new() .build(); // Initialize the database environment. -let env = cuprate_blockchain::open(config)?; +let env = cuprate_blockchain::open::(config)?; // Open up a transaction + tables for writing. let env_inner = env.env_inner(); diff --git a/storage/blockchain/src/config/mod.rs b/storage/blockchain/src/config/mod.rs index 2a81552de..fc9dbfc58 100644 --- a/storage/blockchain/src/config/mod.rs +++ b/storage/blockchain/src/config/mod.rs @@ -19,6 +19,11 @@ //! config::{ConfigBuilder, ReaderThreads}, //! }; //! +//!#[cfg(feature = "heed")] +//!use cuprate_blockchain::cuprate_database::HeedEnv as ConcreteEnv; +//!#[cfg(all(feature = "redb", not(feature = "heed")))] +//!use cuprate_blockchain::cuprate_database::RedbEnv as ConcreteEnv; +//! //! # fn main() -> Result<(), Box> { //! let tmp_dir = tempfile::tempdir()?; //! let db_dir = tmp_dir.path().to_owned(); @@ -34,9 +39,9 @@ //! .build(); //! //! // Start a database `service` using this configuration. -//! let (reader_handle, _) = cuprate_blockchain::service::init(config.clone())?; +//! let (_, _, env) = cuprate_blockchain::service::do_init::(config.clone())?; //! // It's using the config we provided. -//! assert_eq!(reader_handle.env().config(), &config.db_config); +//! assert_eq!(env.config(), &config.db_config); //! # Ok(()) } //! ``` diff --git a/storage/blockchain/src/ops/mod.rs b/storage/blockchain/src/ops/mod.rs index 2699fc82e..dcba72d42 100644 --- a/storage/blockchain/src/ops/mod.rs +++ b/storage/blockchain/src/ops/mod.rs @@ -57,7 +57,6 @@ //! use cuprate_test_utils::data::block_v16_tx0; //! use cuprate_blockchain::{ //! cuprate_database::{ -//! ConcreteEnv, //! Env, EnvInner, //! DatabaseRo, DatabaseRw, TxRo, TxRw, //! }, @@ -66,6 +65,11 @@ //! ops::block::{add_block, pop_block}, //! }; //! +//! #[cfg(feature = "heed")] +//! use cuprate_blockchain::cuprate_database::HeedEnv as ConcreteEnv; +//! #[cfg(all(feature = "redb",not(feature = "heed")))] +//! use cuprate_blockchain::cuprate_database::RedbEnv as ConcreteEnv; +//! //! # fn main() -> Result<(), Box> { //! // Create a configuration for the database environment. //! let tmp_dir = tempfile::tempdir()?; @@ -75,7 +79,7 @@ //! .build(); //! //! // Initialize the database environment. -//! let env = cuprate_blockchain::open(config)?; +//! let env = cuprate_blockchain::open::(config)?; //! //! // Open up a transaction + tables for writing. //! let env_inner = env.env_inner(); diff --git a/storage/blockchain/src/service/free.rs b/storage/blockchain/src/service/free.rs index bbd498208..107d22a5f 100644 --- a/storage/blockchain/src/service/free.rs +++ b/storage/blockchain/src/service/free.rs @@ -13,7 +13,7 @@ use crate::{ //---------------------------------------------------------------------------------------------------- Init #[allow(unreachable_patterns)] #[cold] -#[inline(never)] // Only called once (?) +#[inline(never)] // Only called once /// Initialize a database & thread-pool, and return a read/write handle to it. /// /// Once the returned handles are [`Drop::drop`]ed, the reader @@ -22,18 +22,24 @@ use crate::{ /// # Errors /// This will forward the error if [`crate::open`] failed. pub fn init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle), InitError> { + let (reader, writer); + match config.backend { #[cfg(feature = "heed")] - Backend::Heed => do_init::(config), + Backend::Heed => (reader, writer, _) = do_init::(config)?, #[cfg(feature = "redb")] - Backend::Redb => do_init::(config), + Backend::Redb => (reader, writer, _) = do_init::(config)?, _ => panic!("Selected database backend not available in this build."), - } + }; + + Ok((reader, writer)) } #[cold] -#[inline(never)] -fn do_init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle), InitError> { +#[inline(never)] +/// Like [`init()`], but generic over database backend and returning the database environment too. +/// Only exported for use in tests. +pub fn do_init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle, Arc), InitError> { let reader_threads = config.reader_threads; // Initialize the database itself. @@ -41,9 +47,9 @@ fn do_init(config: Config) -> Result<(DatabaseRe // Spawn the Reader thread pool and Writer. let readers = DatabaseReadHandle::init(&db, reader_threads); - let writer = DatabaseWriteHandle::init(db); + let writer = DatabaseWriteHandle::init(Arc::clone(&db)); - Ok((readers, writer)) + Ok((readers, writer, db)) } diff --git a/storage/blockchain/src/service/mod.rs b/storage/blockchain/src/service/mod.rs index 1d9d10b4d..52209522e 100644 --- a/storage/blockchain/src/service/mod.rs +++ b/storage/blockchain/src/service/mod.rs @@ -126,6 +126,7 @@ pub use write::DatabaseWriteHandle; mod free; pub use free::init; +pub use free::do_init; // Internal type aliases for `service`. mod types; diff --git a/storage/blockchain/src/service/tests.rs b/storage/blockchain/src/service/tests.rs index 4f3fbe49a..b8a1e7829 100644 --- a/storage/blockchain/src/service/tests.rs +++ b/storage/blockchain/src/service/tests.rs @@ -15,7 +15,14 @@ use std::{ use pretty_assertions::assert_eq; use tower::{Service, ServiceExt}; -use cuprate_database::{ConcreteEnv, DatabaseIter, DatabaseRo, Env, EnvInner, RuntimeError}; +use cuprate_database::{DatabaseIter, DatabaseRo, Env, EnvInner, RuntimeError}; + +#[cfg(all(feature = "redb", not(feature = "heed")))] +use cuprate_database::RedbEnv as ConcreteEnv; + +#[cfg(feature = "heed")] +use cuprate_database::HeedEnv as ConcreteEnv; + use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3}; use cuprate_types::{ blockchain::{BCReadRequest, BCResponse, BCWriteRequest}, @@ -29,7 +36,7 @@ use crate::{ blockchain::chain_height, output::id_to_output_on_chain, }, - service::{init, DatabaseReadHandle, DatabaseWriteHandle}, + service::{do_init, DatabaseReadHandle, DatabaseWriteHandle}, tables::{OpenTables, Tables, TablesIter}, tests::AssertTableLen, types::{Amount, AmountIndex, PreRctOutputId}, @@ -48,8 +55,7 @@ fn init_service() -> ( .db_directory(Cow::Owned(tempdir.path().into())) .low_power() .build(); - let (reader, writer) = init(config).unwrap(); - let env = reader.env().clone(); + let (reader, writer, env) = do_init::(config).unwrap(); (reader, writer, env, tempdir) } From c59d342dc2179499262edd711bcb455edbe0b09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20M=C3=BCller?= Date: Thu, 1 Aug 2024 09:37:07 +0200 Subject: [PATCH 5/7] cleanup1 --- storage/blockchain/src/service/read.rs | 17 +---------------- storage/blockchain/src/tests.rs | 7 ++++++- storage/database/src/backend/mod.rs | 9 --------- storage/database/src/lib.rs | 2 -- 4 files changed, 7 insertions(+), 28 deletions(-) diff --git a/storage/blockchain/src/service/read.rs b/storage/blockchain/src/service/read.rs index 4c81ead5a..4bb5b70b5 100644 --- a/storage/blockchain/src/service/read.rs +++ b/storage/blockchain/src/service/read.rs @@ -13,7 +13,7 @@ use thread_local::ThreadLocal; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use tokio_util::sync::PollSemaphore; -use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError}; +use cuprate_database::{DatabaseRo, Env, EnvInner, RuntimeError}; use cuprate_helper::{asynch::InfallibleOneshotReceiver, map::combine_low_high_bits_to_u128}; use cuprate_types::{ blockchain::{BCReadRequest, BCResponse}, @@ -157,21 +157,6 @@ impl DatabaseReadHandle { Self { semaphore, permit: None, spawn } } - /// Access to the actual database environment. - /// - /// # ⚠️ Warning - /// This function gives you access to the actual - /// underlying database connected to by `self`. - /// - /// I.e. it allows you to read/write data _directly_ - /// instead of going through a request. - /// - /// Be warned that using the database directly - /// in this manner has not been tested. - #[inline] - pub const fn env(&self) -> &Arc { - unimplemented!(); - } } impl tower::Service for DatabaseReadHandle { diff --git a/storage/blockchain/src/tests.rs b/storage/blockchain/src/tests.rs index 65527e102..7c847f860 100644 --- a/storage/blockchain/src/tests.rs +++ b/storage/blockchain/src/tests.rs @@ -9,7 +9,12 @@ use std::{borrow::Cow, fmt::Debug}; use pretty_assertions::assert_eq; -use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner}; +use cuprate_database::{DatabaseRo, Env, EnvInner}; + +#[cfg(feature = "heed")] +use cuprate_database::HeedEnv as ConcreteEnv; +#[cfg(all(feature = "redb", not(feature = "heed")))] +use cuprate_database::RedbEnv as ConcreteEnv; use crate::{ config::ConfigBuilder, diff --git a/storage/database/src/backend/mod.rs b/storage/database/src/backend/mod.rs index e27501ec1..2b0499cc0 100644 --- a/storage/database/src/backend/mod.rs +++ b/storage/database/src/backend/mod.rs @@ -12,14 +12,5 @@ mod redb; #[cfg(feature = "redb")] pub use redb::ConcreteEnv as RedbEnv; -cfg_if::cfg_if! { - // TODO remove this block when all ConcreteEnv references are gone. - if #[cfg(all(feature = "redb", not(feature = "heed")))] { - pub use redb::ConcreteEnv; - } else { - pub use heed::ConcreteEnv; - } -} - #[cfg(test)] mod tests; diff --git a/storage/database/src/lib.rs b/storage/database/src/lib.rs index e467234d0..2d992d3ac 100644 --- a/storage/database/src/lib.rs +++ b/storage/database/src/lib.rs @@ -120,8 +120,6 @@ mod transaction; pub mod config; pub mod resize; -pub use backend::ConcreteEnv; - #[cfg(feature="heed")] pub use backend::HeedEnv; From 9ea39ad4056aaca03659063a194c2a48518892ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20M=C3=BCller?= Date: Thu, 1 Aug 2024 18:25:23 +0200 Subject: [PATCH 6/7] Cleanup --- storage/database/src/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/storage/database/src/lib.rs b/storage/database/src/lib.rs index 2d992d3ac..b7ac9f139 100644 --- a/storage/database/src/lib.rs +++ b/storage/database/src/lib.rs @@ -144,13 +144,3 @@ pub(crate) mod tests; // Used inside public facing macros. #[doc(hidden)] pub use paste; - -//---------------------------------------------------------------------------------------------------- -// HACK: needed to satisfy the `unused_crate_dependencies` lint. -cfg_if::cfg_if! { - if #[cfg(feature = "redb")] { - use redb as _; - } else { - use heed as _; - } -} From 007e87606d0bfec6ada26fef9da87bf35a161990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20M=C3=BCller?= Date: Thu, 1 Aug 2024 18:25:34 +0200 Subject: [PATCH 7/7] Restore doc tests --- storage/database/README.md | 8 ++++++-- storage/database/src/backend/tests.rs | 6 +++++- storage/database/src/config/mod.rs | 7 ++++++- storage/database/src/env.rs | 7 ++++++- storage/database/src/tables.rs | 7 ++++++- storage/database/src/tests.rs | 7 ++++++- 6 files changed, 35 insertions(+), 7 deletions(-) diff --git a/storage/database/README.md b/storage/database/README.md index aed738eb9..256a05a4e 100644 --- a/storage/database/README.md +++ b/storage/database/README.md @@ -108,12 +108,16 @@ The below is an example of using `cuprate-database`. ```rust use cuprate_database::{ - ConcreteEnv, config::ConfigBuilder, Env, EnvInner, DatabaseRo, DatabaseRw, TxRo, TxRw, }; +#[cfg(feature = "heed")] +use cuprate_database::HeedEnv as ConcreteEnv; +#[cfg(all(feature = "redb", not(feature = "heed")))] +use cuprate_database::RedbEnv as ConcreteEnv; + # fn main() -> Result<(), Box> { // Create a configuration for the database environment. let tmp_dir = tempfile::tempdir()?; @@ -153,4 +157,4 @@ let tx_ro = env_inner.tx_ro()?; let table = env_inner.open_db_ro::(&tx_ro)?; assert_eq!(table.first()?, (0, 1)); # Ok(()) } -``` \ No newline at end of file +``` diff --git a/storage/database/src/backend/tests.rs b/storage/database/src/backend/tests.rs index ac6b5927c..5350c02fa 100644 --- a/storage/database/src/backend/tests.rs +++ b/storage/database/src/backend/tests.rs @@ -21,9 +21,13 @@ use crate::{ resize::ResizeAlgorithm, tests::{tmp_concrete_env, TestTable}, transaction::{TxRo, TxRw}, - ConcreteEnv, }; +#[cfg(feature = "heed")] +use crate::HeedEnv as ConcreteEnv; +#[cfg(all(feature = "redb", not(feature = "heed")))] +use crate::RedbEnv as ConcreteEnv; + //---------------------------------------------------------------------------------------------------- Tests /// Simply call [`Env::open`]. If this fails, something is really wrong. #[test] diff --git a/storage/database/src/config/mod.rs b/storage/database/src/config/mod.rs index 19a324e1f..d62919032 100644 --- a/storage/database/src/config/mod.rs +++ b/storage/database/src/config/mod.rs @@ -13,10 +13,15 @@ //! # Example //! ```rust //! use cuprate_database::{ -//! ConcreteEnv, Env, +//! Env, //! config::{ConfigBuilder, SyncMode} //! }; //! +//! #[cfg(feature = "heed")] +//! use cuprate_database::HeedEnv as ConcreteEnv; +//! #[cfg(all(feature = "redb", not(feature = "heed")))] +//! use cuprate_database::RedbEnv as ConcreteEnv; +//! //! # fn main() -> Result<(), Box> { //! let db_dir = tempfile::tempdir()?; //! diff --git a/storage/database/src/env.rs b/storage/database/src/env.rs index e93e1be21..5fd9fe393 100644 --- a/storage/database/src/env.rs +++ b/storage/database/src/env.rs @@ -237,11 +237,16 @@ pub trait EnvInner<'env> { /// /// ```rust /// # use cuprate_database::{ - /// # ConcreteEnv, /// # config::ConfigBuilder, /// # Env, EnvInner, /// # DatabaseRo, DatabaseRw, TxRo, TxRw, /// # }; + /// # + /// # #[cfg(feature = "heed")] + /// # use cuprate_database::HeedEnv as ConcreteEnv; + /// # #[cfg(all(feature = "redb", not(feature = "heed")))] + /// # use cuprate_database::RedbEnv as ConcreteEnv; + /// /// # fn main() -> Result<(), Box> { /// # let tmp_dir = tempfile::tempdir()?; /// # let db_dir = tmp_dir.path().to_owned(); diff --git a/storage/database/src/tables.rs b/storage/database/src/tables.rs index 83a00e16e..24bbcbeea 100644 --- a/storage/database/src/tables.rs +++ b/storage/database/src/tables.rs @@ -44,12 +44,17 @@ /// An example: /// ```rust /// use cuprate_database::{ -/// ConcreteEnv, Table, +/// Table, /// config::ConfigBuilder, /// Env, EnvInner, /// DatabaseRo, DatabaseRw, TxRo, TxRw, /// }; /// +/// #[cfg(feature = "heed")] +/// use cuprate_database::HeedEnv as ConcreteEnv; +/// #[cfg(all(feature = "redb", not(feature = "heed")))] +/// use cuprate_database::RedbEnv as ConcreteEnv; +/// /// // This generates `pub struct Table{1,2,3}` /// // where all those implement `Table` with /// // the defined name and key/value types. diff --git a/storage/database/src/tests.rs b/storage/database/src/tests.rs index 9c9317d2e..da7447272 100644 --- a/storage/database/src/tests.rs +++ b/storage/database/src/tests.rs @@ -7,7 +7,12 @@ //---------------------------------------------------------------------------------------------------- Import use std::borrow::Cow; -use crate::{config::ConfigBuilder, table::Table, ConcreteEnv, Env}; +use crate::{config::ConfigBuilder, table::Table, Env}; + +#[cfg(feature = "heed")] +use crate::HeedEnv as ConcreteEnv; +#[cfg(all(feature = "redb",not(feature = "heed")))] +use crate::RedbEnv as ConcreteEnv; //---------------------------------------------------------------------------------------------------- struct /// A test table.