read: use ThreadLocal for both backends

This commit is contained in:
hinto.janai 2024-04-28 20:45:05 -04:00
parent bd246eb9a3
commit 8b8bb6342f
No known key found for this signature in database
GPG key ID: D47CE05FA175A499

View file

@ -227,11 +227,13 @@ fn map_request(
} }
//---------------------------------------------------------------------------------------------------- Thread Local //---------------------------------------------------------------------------------------------------- Thread Local
/// `heed`'s transactions and tables are not `Sync`, so we cannot use /// Q: Why does this exist?
///
/// A1: `heed`'s transactions and tables are not `Sync`, so we cannot use
/// them with rayon, however, we set a feature such that they are `Send`. /// them with rayon, however, we set a feature such that they are `Send`.
/// ///
/// Thus, before using rayon, we put the tx/table inside a /// A2: When sending to rayon, we want to ensure each read transaction
/// `ThreadLocal` which gives access to those threads. /// is only being used by 1 thread only to scale reads
/// ///
/// <https://github.com/Cuprate/cuprate/pull/113#discussion_r1576762346> /// <https://github.com/Cuprate/cuprate/pull/113#discussion_r1576762346>
#[inline] #[inline]
@ -239,92 +241,6 @@ fn thread_local<T: Send>(env: &impl Env) -> ThreadLocal<T> {
ThreadLocal::with_capacity(env.config().reader_threads.as_threads().get()) ThreadLocal::with_capacity(env.config().reader_threads.as_threads().get())
} }
/// Only `heed` requires the above [`thread_local()`] function,
/// as `redb`'s transactions and tables are `Send + Sync`.
///
/// Thus, wrapping them in `ThreadLocal` is wasteful.
///
/// This macro branches depending on what backend we're using
/// and either returns `ThreadLocal<T>` or the T directly.
///
/// An imaginary signature would look something like:
/// ```ignore
/// fn set_tx_ro() -> if heed {
/// ThreadLocal<TxRo>
/// } else {
/// TxRo
/// };
/// ```
///
/// - See [`set_tables`] for the same thing but for `ThreadLocal<impl Tables>`
/// - See [`get_tx_ro_and_tables`] for retrieving the output
///
/// # Note
/// Note that this is _only_ needed when `Send`ing another thread,
/// i.e. when using `rayon`. If the function handling the `Request`
/// is single-threaded, normal `tx_ro()` and `open_tables()` can be used.
///
/// # Early return
/// Note that this early returns with `?` from whatever
/// scope it was called from if `tx_ro()` errors.
///
/// # Example
/// ```ignore
/// // Outside scope, still single threaded.
/// // Set the transaction and tables.
/// let tx_ro = set_tx_ro!(env, env_inner);
/// let tables = set_tables!(env, env_inner, tx_ro);
///
/// iter
/// .into_par_iter() // <- we've entered `rayon` scope
/// .map(|_| {
/// // Access the outside scope's `tx_ro` and `tables`.
/// // If needed, this will initialize some `ThreadLocal`'s.
/// let (tx_ro, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables);
///
/// /* do rayon stuff */
/// });
/// ```
macro_rules! set_tx_ro {
($env:ident, $env_inner:ident) => {{
cfg_if::cfg_if! {
if #[cfg(all(feature = "redb", not(feature = "heed")))] {
$env_inner.tx_ro()?
} else {
thread_local($env)
}
}
}};
}
/// Same as [`set_tx_ro`] but for the variable holding `impl Tables`.
macro_rules! set_tables {
($env:ident, $env_inner:ident, $tx_ro:ident) => {{
cfg_if::cfg_if! {
if #[cfg(all(feature = "redb", not(feature = "heed")))] {
$env_inner.open_tables(&$tx_ro)?
} else {
thread_local($env)
}
}
}};
}
/// Access the values set with [`set_tx_ro`] and [`set_tables`].
macro_rules! get_tx_ro_and_tables {
($env_inner:ident, $tx_ro:ident, $tables:ident) => {{
cfg_if::cfg_if! {
if #[cfg(all(feature = "redb", not(feature = "heed")))] {
(&$tx_ro, &$tables)
} else {
let tx_ro = $tx_ro.get_or_try(|| $env_inner.tx_ro())?;
let tables = $tables.get_or_try(|| $env_inner.open_tables(tx_ro))?;
(tx_ro, tables)
}
}
}};
}
//---------------------------------------------------------------------------------------------------- Handler functions //---------------------------------------------------------------------------------------------------- Handler functions
// These are the actual functions that do stuff according to the incoming [`Request`]. // These are the actual functions that do stuff according to the incoming [`Request`].
// //
@ -376,13 +292,14 @@ fn block_extended_header_in_range(
range: std::ops::Range<BlockHeight>, range: std::ops::Range<BlockHeight>,
) -> ResponseResult { ) -> ResponseResult {
let env_inner = env.env_inner(); let env_inner = env.env_inner();
let tx_ro = set_tx_ro!(env, env_inner); let tx_ro = thread_local(env);
let tables = set_tables!(env, env_inner, tx_ro); let tables = thread_local(env);
let vec = range let vec = range
.into_par_iter() .into_par_iter()
.map(|block_height| { .map(|block_height| {
let (tx_ro, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables); let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?;
let tables = tables.get_or_try(|| env_inner.open_tables(tx_ro))?;
get_block_extended_header_from_height(&block_height, tables) get_block_extended_header_from_height(&block_height, tables)
}) })
.collect::<Result<Vec<ExtendedBlockHeader>, RuntimeError>>()?; .collect::<Result<Vec<ExtendedBlockHeader>, RuntimeError>>()?;
@ -424,12 +341,13 @@ fn generated_coins(env: &ConcreteEnv) -> ResponseResult {
#[inline] #[inline]
fn outputs(env: &ConcreteEnv, map: HashMap<Amount, HashSet<AmountIndex>>) -> ResponseResult { fn outputs(env: &ConcreteEnv, map: HashMap<Amount, HashSet<AmountIndex>>) -> ResponseResult {
let env_inner = env.env_inner(); let env_inner = env.env_inner();
let tx_ro = set_tx_ro!(env, env_inner); let tx_ro = thread_local(env);
let tables = set_tables!(env, env_inner, tx_ro); let tables = thread_local(env);
// -> Result<(AmountIndex, OutputOnChain), RuntimeError> // -> Result<(AmountIndex, OutputOnChain), RuntimeError>
let inner_map = |amount, amount_index| { let inner_map = |amount, amount_index| {
let (tx_ro, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables); let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?;
let tables = tables.get_or_try(|| env_inner.open_tables(tx_ro))?;
if amount == 0 { if amount == 0 {
// v2 transactions. // v2 transactions.
@ -473,21 +391,23 @@ fn outputs(env: &ConcreteEnv, map: HashMap<Amount, HashSet<AmountIndex>>) -> Res
#[inline] #[inline]
fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec<Amount>) -> ResponseResult { fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec<Amount>) -> ResponseResult {
let env_inner = env.env_inner(); let env_inner = env.env_inner();
let tx_ro = set_tx_ro!(env, env_inner); let tx_ro = thread_local(env);
let tables = set_tables!(env, env_inner, tx_ro); let tables = thread_local(env);
// Cache the amount of RCT outputs once. // Cache the amount of RCT outputs once.
// INVARIANT: #[cfg] @ lib.rs asserts `usize == u64` // INVARIANT: #[cfg] @ lib.rs asserts `usize == u64`
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
let num_rct_outputs = get_tx_ro_and_tables!(env_inner, tx_ro, tables) let num_rct_outputs = {
.1 let tx_ro = env_inner.tx_ro()?;
.rct_outputs() let tables = env_inner.open_tables(&tx_ro)?;
.len()? as usize; tables.rct_outputs().len()? as usize
};
let map = amounts let map = amounts
.into_par_iter() .into_par_iter()
.map(|amount| { .map(|amount| {
let (tx_ro, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables); let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?;
let tables = tables.get_or_try(|| env_inner.open_tables(tx_ro))?;
if amount == 0 { if amount == 0 {
// v2 transactions. // v2 transactions.
@ -514,11 +434,12 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec<Amount>) -> Respon
#[inline] #[inline]
fn check_k_is_not_spent(env: &ConcreteEnv, key_images: HashSet<KeyImage>) -> ResponseResult { fn check_k_is_not_spent(env: &ConcreteEnv, key_images: HashSet<KeyImage>) -> ResponseResult {
let env_inner = env.env_inner(); let env_inner = env.env_inner();
let tx_ro = set_tx_ro!(env, env_inner); let tx_ro = thread_local(env);
let tables = set_tables!(env, env_inner, tx_ro); let tables = thread_local(env);
let key_image_exists = |key_image| { let key_image_exists = |key_image| {
let (tx_ro, tables) = get_tx_ro_and_tables!(env_inner, tx_ro, tables); let tx_ro = tx_ro.get_or_try(|| env_inner.tx_ro())?;
let tables = tables.get_or_try(|| env_inner.open_tables(tx_ro))?;
key_image_exists(&key_image, tables.key_images()) key_image_exists(&key_image, tables.key_images())
}; };