From 8c16f81b6491cfa3322e31cde07f7b7033a43c04 Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Sat, 7 Dec 2024 19:21:29 +0100 Subject: [PATCH 01/12] First working parametric Tls for Txns --- heed/Cargo.toml | 2 +- heed/src/cursor.rs | 16 +++--- heed/src/databases/database.rs | 87 ++++++++++++++++--------------- heed/src/envs/env.rs | 40 ++++++++------ heed/src/envs/env_open_options.rs | 37 +++++++++---- heed/src/iterator/iter.rs | 55 +++++++++---------- heed/src/iterator/prefix.rs | 72 +++++++++++++------------ heed/src/iterator/range.rs | 62 +++++++++++----------- heed/src/lib.rs | 2 +- heed/src/mdb/lmdb_flags.rs | 1 + heed/src/txn.rs | 66 +++++++++++++++++------ 11 files changed, 254 insertions(+), 186 deletions(-) diff --git a/heed/Cargo.toml b/heed/Cargo.toml index bd1ab748..c7e5ef9f 100644 --- a/heed/Cargo.toml +++ b/heed/Cargo.toml @@ -57,7 +57,7 @@ serde = ["bitflags/serde", "dep:serde"] # # And a #MDB_BAD_RSLOT error will be thrown when multiple read # transactions exists on the same thread -read-txn-no-tls = [] +# read-txn-no-tls = [] # Enable the serde en/decoders for bincode, serde_json, or rmp_serde serde-bincode = ["heed-types/serde-bincode"] diff --git a/heed/src/cursor.rs b/heed/src/cursor.rs index d77508e5..2661ef4c 100644 --- a/heed/src/cursor.rs +++ b/heed/src/cursor.rs @@ -5,14 +5,14 @@ use crate::mdb::error::mdb_result; use crate::mdb::ffi; use crate::*; -pub struct RoCursor<'txn> { +pub struct RoCursor<'txn, T> { cursor: *mut ffi::MDB_cursor, - _marker: marker::PhantomData<&'txn ()>, + _marker: marker::PhantomData<&'txn T>, } -impl<'txn> RoCursor<'txn> { - // TODO should I ask for a &mut RoTxn, here? - pub(crate) fn new(txn: &'txn RoTxn, dbi: ffi::MDB_dbi) -> Result> { +impl<'txn, T> RoCursor<'txn, T> { + // TODO should I ask for a &mut RoTxn<'_, T>, here? + pub(crate) fn new(txn: &'txn RoTxn<'_, T>, dbi: ffi::MDB_dbi) -> Result> { let mut cursor: *mut ffi::MDB_cursor = ptr::null_mut(); let mut txn = txn.txn.unwrap(); unsafe { mdb_result(ffi::mdb_cursor_open(txn.as_mut(), dbi, &mut cursor))? } @@ -237,14 +237,14 @@ impl<'txn> RoCursor<'txn> { } } -impl Drop for RoCursor<'_> { +impl Drop for RoCursor<'_, T> { fn drop(&mut self) { unsafe { ffi::mdb_cursor_close(self.cursor) } } } pub struct RwCursor<'txn> { - cursor: RoCursor<'txn>, + cursor: RoCursor<'txn, WithoutTls>, } impl<'txn> RwCursor<'txn> { @@ -404,7 +404,7 @@ impl<'txn> RwCursor<'txn> { } impl<'txn> Deref for RwCursor<'txn> { - type Target = RoCursor<'txn>; + type Target = RoCursor<'txn, WithoutTls>; fn deref(&self) -> &Self::Target { &self.cursor diff --git a/heed/src/databases/database.rs b/heed/src/databases/database.rs index df8295ae..c5226634 100644 --- a/heed/src/databases/database.rs +++ b/heed/src/databases/database.rs @@ -55,16 +55,16 @@ use crate::*; /// # Ok(()) } /// ``` #[derive(Debug)] -pub struct DatabaseOpenOptions<'e, 'n, KC, DC, C = DefaultComparator> { - env: &'e Env, +pub struct DatabaseOpenOptions<'e, 'n, T, KC, DC, C = DefaultComparator> { + env: &'e Env, types: marker::PhantomData<(KC, DC, C)>, name: Option<&'n str>, flags: AllDatabaseFlags, } -impl<'e> DatabaseOpenOptions<'e, 'static, Unspecified, Unspecified> { +impl<'e, T> DatabaseOpenOptions<'e, 'static, T, Unspecified, Unspecified> { /// Create an options struct to open/create a database with specific flags. - pub fn new(env: &'e Env) -> Self { + pub fn new(env: &'e Env) -> Self { DatabaseOpenOptions { env, types: Default::default(), @@ -74,12 +74,12 @@ impl<'e> DatabaseOpenOptions<'e, 'static, Unspecified, Unspecified> { } } -impl<'e, 'n, KC, DC, C> DatabaseOpenOptions<'e, 'n, KC, DC, C> { +impl<'e, 'n, T, KC, DC, C> DatabaseOpenOptions<'e, 'n, T, KC, DC, C> { /// Change the type of the database. /// /// The default types are [`Unspecified`] and require a call to [`Database::remap_types`] /// to use the [`Database`]. - pub fn types(self) -> DatabaseOpenOptions<'e, 'n, NKC, NDC> { + pub fn types(self) -> DatabaseOpenOptions<'e, 'n, T, NKC, NDC> { DatabaseOpenOptions { env: self.env, types: Default::default(), @@ -87,10 +87,11 @@ impl<'e, 'n, KC, DC, C> DatabaseOpenOptions<'e, 'n, KC, DC, C> { flags: self.flags, } } + /// Change the customized key compare function of the database. /// /// By default no customized compare function will be set when opening a database. - pub fn key_comparator(self) -> DatabaseOpenOptions<'e, 'n, KC, DC, NC> { + pub fn key_comparator(self) -> DatabaseOpenOptions<'e, 'n, T, KC, DC, NC> { DatabaseOpenOptions { env: self.env, types: Default::default(), @@ -131,7 +132,7 @@ impl<'e, 'n, KC, DC, C> DatabaseOpenOptions<'e, 'n, KC, DC, C> { /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. - pub fn open(&self, rtxn: &RoTxn) -> Result>> + pub fn open(&self, rtxn: &RoTxn) -> Result>> where KC: 'static, DC: 'static, @@ -339,7 +340,11 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get<'a, 'txn>(&self, txn: &'txn RoTxn, key: &'a KC::EItem) -> Result> + pub fn get<'a, 'txn, T>( + &self, + txn: &'txn RoTxn, + key: &'a KC::EItem, + ) -> Result> where KC: BytesEncode<'a>, DC: BytesDecode<'txn>, @@ -423,11 +428,11 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_duplicates<'a, 'txn>( + pub fn get_duplicates<'a, 'txn, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, key: &'a KC::EItem, - ) -> Result>> + ) -> Result>> where KC: BytesEncode<'a>, { @@ -486,9 +491,9 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_lower_than<'a, 'txn>( + pub fn get_lower_than<'a, 'txn, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -555,9 +560,9 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_lower_than_or_equal_to<'a, 'txn>( + pub fn get_lower_than_or_equal_to<'a, 'txn, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -628,9 +633,9 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_greater_than<'a, 'txn>( + pub fn get_greater_than<'a, 'txn, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -700,9 +705,9 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_greater_than_or_equal_to<'a, 'txn>( + pub fn get_greater_than_or_equal_to<'a, 'txn, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -759,7 +764,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn first<'txn>(&self, txn: &'txn RoTxn) -> Result> + pub fn first<'txn, T>(&self, txn: &'txn RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -813,7 +818,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn last<'txn>(&self, txn: &'txn RoTxn) -> Result> + pub fn last<'txn, T>(&self, txn: &'txn RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -870,7 +875,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn len(&self, txn: &RoTxn) -> Result { + pub fn len(&self, txn: &RoTxn) -> Result { self.stat(txn).map(|stat| stat.entries as u64) } @@ -913,7 +918,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn is_empty(&self, txn: &RoTxn) -> Result { + pub fn is_empty(&self, txn: &RoTxn) -> Result { self.len(txn).map(|l| l == 0) } @@ -955,7 +960,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn stat(&self, txn: &RoTxn) -> Result { + pub fn stat(&self, txn: &RoTxn) -> Result { assert_eq_env_db_txn!(self, txn); let mut db_stat = mem::MaybeUninit::uninit(); @@ -1019,7 +1024,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn iter<'txn>(&self, txn: &'txn RoTxn) -> Result> { + pub fn iter<'txn, T>(&self, txn: &'txn RoTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RoCursor::new(txn, self.dbi).map(|cursor| RoIter::new(cursor)) } @@ -1074,7 +1079,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { + pub fn iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwIter::new(cursor)) @@ -1120,7 +1125,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_iter<'txn>(&self, txn: &'txn RoTxn) -> Result> { + pub fn rev_iter<'txn, T>(&self, txn: &'txn RoTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RoCursor::new(txn, self.dbi).map(|cursor| RoRevIter::new(cursor)) @@ -1177,7 +1182,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { + pub fn rev_iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwRevIter::new(cursor)) @@ -1226,11 +1231,11 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn range<'a, 'txn, R>( + pub fn range<'a, 'txn, R, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, range: &'a R, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, @@ -1398,11 +1403,11 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_range<'a, 'txn, R>( + pub fn rev_range<'a, 'txn, R, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, range: &'a R, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, @@ -1572,11 +1577,11 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn prefix_iter<'a, 'txn>( + pub fn prefix_iter<'a, 'txn, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn, prefix: &'a KC::EItem, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, @@ -1704,11 +1709,11 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_prefix_iter<'a, 'txn>( + pub fn rev_prefix_iter<'a, 'txn, T>( &self, - txn: &'txn RoTxn, + txn: &'txn RoTxn<'_, T>, prefix: &'a KC::EItem, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, diff --git a/heed/src/envs/env.rs b/heed/src/envs/env.rs index 32595edb..52b86200 100644 --- a/heed/src/envs/env.rs +++ b/heed/src/envs/env.rs @@ -1,6 +1,7 @@ use std::any::TypeId; use std::ffi::CString; use std::fs::File; +use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::ptr::{self, NonNull}; use std::sync::Arc; @@ -25,18 +26,17 @@ use crate::{ }; /// An environment handle constructed by using [`EnvOpenOptions::open`]. -#[derive(Clone)] -pub struct Env { - inner: Arc, +pub struct Env { + inner: Arc>, } -impl Env { +impl Env { pub(crate) fn new( env_ptr: NonNull, path: PathBuf, signal_event: Arc, - ) -> Env { - Env { inner: Arc::new(EnvInner { env_ptr, path, signal_event }) } + ) -> Self { + Env { inner: Arc::new(EnvInner { env_ptr, path, signal_event, _tls_marker: PhantomData }) } } pub(crate) fn env_mut_ptr(&self) -> NonNull { @@ -194,7 +194,7 @@ impl Env { } /// Options and flags which can be used to configure how a [`Database`] is opened. - pub fn database_options(&self) -> DatabaseOpenOptions { + pub fn database_options(&self) -> DatabaseOpenOptions { DatabaseOpenOptions::new(self) } @@ -218,7 +218,7 @@ impl Env { /// known as `EINVAL`. pub fn open_database( &self, - rtxn: &RoTxn, + rtxn: &RoTxn, name: Option<&str>, ) -> Result>> where @@ -353,7 +353,7 @@ impl Env { /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full - pub fn read_txn(&self) -> Result { + pub fn read_txn(&self) -> Result> { RoTxn::new(self) } @@ -382,7 +382,7 @@ impl Env { /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full - pub fn static_read_txn(self) -> Result> { + pub fn static_read_txn(self) -> Result> { RoTxn::static_read_txn(self) } @@ -481,26 +481,32 @@ impl Env { } } -unsafe impl Send for Env {} +impl Clone for Env { + fn clone(&self) -> Self { + Env { inner: self.inner.clone() } + } +} -unsafe impl Sync for Env {} +unsafe impl Send for Env {} +unsafe impl Sync for Env {} -impl fmt::Debug for Env { +impl fmt::Debug for Env { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Env").field("path", &self.inner.path.display()).finish_non_exhaustive() } } -pub(crate) struct EnvInner { +pub(crate) struct EnvInner { env_ptr: NonNull, signal_event: Arc, pub(crate) path: PathBuf, + _tls_marker: PhantomData, } -unsafe impl Send for EnvInner {} -unsafe impl Sync for EnvInner {} +unsafe impl Send for EnvInner {} +unsafe impl Sync for EnvInner {} -impl Drop for EnvInner { +impl Drop for EnvInner { fn drop(&mut self) { let mut lock = OPENED_ENV.write().unwrap(); let removed = lock.remove(&self.path); diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index 437ae085..8dbc9471 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -2,6 +2,7 @@ use std::ffi::CString; #[cfg(windows)] use std::ffi::OsStr; use std::io::ErrorKind::NotFound; +use std::marker::PhantomData; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use std::path::Path; @@ -21,37 +22,54 @@ use super::{canonicalize_path, OPENED_ENV}; use crate::envs::OsStrExtLmdb as _; use crate::mdb::error::mdb_result; use crate::mdb::ffi; -use crate::{EnvFlags, Error, Result}; +use crate::txn::{TlsUsage, WithoutTls}; +use crate::{EnvFlags, Error, Result, WithTls}; /// Options and flags which can be used to configure how an environment is opened. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EnvOpenOptions { +pub struct EnvOpenOptions { map_size: Option, max_readers: Option, max_dbs: Option, flags: EnvFlags, + _tls_marker: PhantomData, } -impl Default for EnvOpenOptions { +impl Default for EnvOpenOptions { fn default() -> Self { Self::new() } } -impl EnvOpenOptions { +impl EnvOpenOptions { /// Creates a blank new set of options ready for configuration. - pub fn new() -> EnvOpenOptions { + pub fn new() -> EnvOpenOptions { EnvOpenOptions { map_size: None, max_readers: None, max_dbs: None, flags: EnvFlags::empty(), + _tls_marker: PhantomData, } } } -impl EnvOpenOptions { +impl EnvOpenOptions { + /// Make the read transactions `!Send` by specifying they will use Thread Local Storage (TLS). + pub fn read_txn_with_tls(self) -> EnvOpenOptions { + let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; + EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } + } + + /// Make the read transactions `Send` by specifying they will not use Thread Local Storage (TLS). + pub fn read_txn_without_tls(self) -> EnvOpenOptions { + let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; + EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } + } +} + +impl EnvOpenOptions { /// Set the size of the memory map to use for this environment. pub fn map_size(&mut self, size: usize) -> &mut Self { self.map_size = Some(size); @@ -151,7 +169,7 @@ impl EnvOpenOptions { /// [^6]: /// [^7]: /// [^8]: - pub unsafe fn open>(&self, path: P) -> Result { + pub unsafe fn open>(&self, path: P) -> Result> { self.raw_open_with_encryption( path.as_ref(), #[cfg(master3)] @@ -320,7 +338,7 @@ impl EnvOpenOptions { &self, path: &Path, #[cfg(master3)] enc: Option<(ffi::MDB_enc_func, &[u8], u32)>, - ) -> Result { + ) -> Result> { let mut lock = OPENED_ENV.write().unwrap(); let path = match canonicalize_path(path) { @@ -379,7 +397,8 @@ impl EnvOpenOptions { // When the `read-txn-no-tls` feature is enabled, we must force LMDB // to avoid using the thread local storage, this way we allow users // to use references of RoTxn between threads safely. - let flags = if cfg!(feature = "read-txn-no-tls") { + #[allow(deprecated)] // NO_TLS is inside of the crate + let flags = if T::ENABLED { // TODO make this a ZST flag on the Env and on RoTxn (make them Send when we can) self.flags | EnvFlags::NO_TLS } else { diff --git a/heed/src/iterator/iter.rs b/heed/src/iterator/iter.rs index 87f67662..3b4b27cb 100644 --- a/heed/src/iterator/iter.rs +++ b/heed/src/iterator/iter.rs @@ -7,14 +7,14 @@ use crate::iteration_method::{IterationMethod, MoveBetweenKeys, MoveThroughDupli use crate::*; /// A read-only iterator structure. -pub struct RoIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { - cursor: RoCursor<'txn>, +pub struct RoIter<'txn, T, KC, DC, IM = MoveThroughDuplicateValues> { + cursor: RoCursor<'txn, T>, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } -impl<'txn, KC, DC, IM> RoIter<'txn, KC, DC, IM> { - pub(crate) fn new(cursor: RoCursor<'txn>) -> RoIter<'txn, KC, DC, IM> { +impl<'txn, T, KC, DC, IM> RoIter<'txn, T, KC, DC, IM> { + pub(crate) fn new(cursor: RoCursor<'txn, T>) -> RoIter<'txn, T, KC, DC, IM> { RoIter { cursor, move_on_first: true, _phantom: marker::PhantomData } } @@ -63,7 +63,7 @@ impl<'txn, KC, DC, IM> RoIter<'txn, KC, DC, IM> { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn move_between_keys(self) -> RoIter<'txn, KC, DC, MoveBetweenKeys> { + pub fn move_between_keys(self) -> RoIter<'txn, T, KC, DC, MoveBetweenKeys> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, @@ -119,7 +119,9 @@ impl<'txn, KC, DC, IM> RoIter<'txn, KC, DC, IM> { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn move_through_duplicate_values(self) -> RoIter<'txn, KC, DC, MoveThroughDuplicateValues> { + pub fn move_through_duplicate_values( + self, + ) -> RoIter<'txn, T, KC, DC, MoveThroughDuplicateValues> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, @@ -128,7 +130,7 @@ impl<'txn, KC, DC, IM> RoIter<'txn, KC, DC, IM> { } /// Change the codec types of this iterator, specifying the codecs. - pub fn remap_types(self) -> RoIter<'txn, KC2, DC2, IM> { + pub fn remap_types(self) -> RoIter<'txn, T, KC2, DC2, IM> { RoIter { cursor: self.cursor, move_on_first: self.move_on_first, @@ -137,22 +139,22 @@ impl<'txn, KC, DC, IM> RoIter<'txn, KC, DC, IM> { } /// Change the key codec type of this iterator, specifying the new codec. - pub fn remap_key_type(self) -> RoIter<'txn, KC2, DC, IM> { + pub fn remap_key_type(self) -> RoIter<'txn, T, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. - pub fn remap_data_type(self) -> RoIter<'txn, KC, DC2, IM> { + pub fn remap_data_type(self) -> RoIter<'txn, T, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. - pub fn lazily_decode_data(self) -> RoIter<'txn, KC, LazyDecode, IM> { + pub fn lazily_decode_data(self) -> RoIter<'txn, T, KC, LazyDecode, IM> { self.remap_types::>() } } -impl<'txn, KC, DC, IM> Iterator for RoIter<'txn, KC, DC, IM> +impl<'txn, T, KC, DC, IM> Iterator for RoIter<'txn, T, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -202,14 +204,14 @@ where } } -impl fmt::Debug for RoIter<'_, KC, DC, IM> { +impl fmt::Debug for RoIter<'_, T, KC, DC, IM> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RoIter").finish() } } -#[cfg(feature = "read-txn-no-tls")] -unsafe impl Send for RoIter<'_, KC, DC, IM> {} +/// A `RoIter` is `Send` only if the `RoTxn` is. +unsafe impl Send for RoIter<'_, WithoutTls, KC, DC, IM> {} /// A read-write iterator structure. pub struct RwIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { @@ -441,21 +443,21 @@ impl fmt::Debug for RwIter<'_, KC, DC, IM> { } /// A reverse read-only iterator structure. -pub struct RoRevIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { - cursor: RoCursor<'txn>, +pub struct RoRevIter<'txn, T, KC, DC, IM = MoveThroughDuplicateValues> { + cursor: RoCursor<'txn, T>, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, IM)>, } -impl<'txn, KC, DC, IM> RoRevIter<'txn, KC, DC, IM> { - pub(crate) fn new(cursor: RoCursor<'txn>) -> RoRevIter<'txn, KC, DC, IM> { +impl<'txn, T, KC, DC, IM> RoRevIter<'txn, T, KC, DC, IM> { + pub(crate) fn new(cursor: RoCursor<'txn, T>) -> RoRevIter<'txn, T, KC, DC, IM> { RoRevIter { cursor, move_on_last: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. - pub fn move_between_keys(self) -> RoRevIter<'txn, KC, DC, MoveBetweenKeys> { + pub fn move_between_keys(self) -> RoRevIter<'txn, T, KC, DC, MoveBetweenKeys> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, @@ -468,7 +470,7 @@ impl<'txn, KC, DC, IM> RoRevIter<'txn, KC, DC, IM> { /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, - ) -> RoRevIter<'txn, KC, DC, MoveThroughDuplicateValues> { + ) -> RoRevIter<'txn, T, KC, DC, MoveThroughDuplicateValues> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, @@ -477,7 +479,7 @@ impl<'txn, KC, DC, IM> RoRevIter<'txn, KC, DC, IM> { } /// Change the codec types of this iterator, specifying the codecs. - pub fn remap_types(self) -> RoRevIter<'txn, KC2, DC2, IM> { + pub fn remap_types(self) -> RoRevIter<'txn, T, KC2, DC2, IM> { RoRevIter { cursor: self.cursor, move_on_last: self.move_on_last, @@ -486,22 +488,22 @@ impl<'txn, KC, DC, IM> RoRevIter<'txn, KC, DC, IM> { } /// Change the key codec type of this iterator, specifying the new codec. - pub fn remap_key_type(self) -> RoRevIter<'txn, KC2, DC, IM> { + pub fn remap_key_type(self) -> RoRevIter<'txn, T, KC2, DC, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. - pub fn remap_data_type(self) -> RoRevIter<'txn, KC, DC2, IM> { + pub fn remap_data_type(self) -> RoRevIter<'txn, T, KC, DC2, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. - pub fn lazily_decode_data(self) -> RoRevIter<'txn, KC, LazyDecode, IM> { + pub fn lazily_decode_data(self) -> RoRevIter<'txn, T, KC, LazyDecode, IM> { self.remap_types::>() } } -impl<'txn, KC, DC, IM> Iterator for RoRevIter<'txn, KC, DC, IM> +impl<'txn, T, KC, DC, IM> Iterator for RoRevIter<'txn, T, KC, DC, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -557,8 +559,7 @@ impl fmt::Debug for RoRevIter<'_, KC, DC, IM> { } } -#[cfg(feature = "read-txn-no-tls")] -unsafe impl Send for RoRevIter<'_, KC, DC, IM> {} +unsafe impl Send for RoRevIter<'_, WithoutTls, KC, DC, IM> {} /// A reverse read-write iterator structure. pub struct RwRevIter<'txn, KC, DC, IM = MoveThroughDuplicateValues> { diff --git a/heed/src/iterator/prefix.rs b/heed/src/iterator/prefix.rs index 854177e3..030baed2 100644 --- a/heed/src/iterator/prefix.rs +++ b/heed/src/iterator/prefix.rs @@ -47,8 +47,8 @@ fn retreat_prefix(bytes: &mut [u8]) -> bool { true } -fn move_on_prefix_end<'txn, C: LexicographicComparator>( - cursor: &mut RoCursor<'txn>, +fn move_on_prefix_end<'txn, T, C: LexicographicComparator>( + cursor: &mut RoCursor<'txn, T>, prefix: &mut [u8], ) -> Result> { if advance_prefix::(prefix) { @@ -64,22 +64,25 @@ fn move_on_prefix_end<'txn, C: LexicographicComparator>( } /// A read-only prefix iterator structure. -pub struct RoPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { - cursor: RoCursor<'txn>, +pub struct RoPrefix<'txn, T, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { + cursor: RoCursor<'txn, T>, prefix: Vec, move_on_first: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } -impl<'txn, KC, DC, C, IM> RoPrefix<'txn, KC, DC, C, IM> { - pub(crate) fn new(cursor: RoCursor<'txn>, prefix: Vec) -> RoPrefix<'txn, KC, DC, C, IM> { +impl<'txn, T, KC, DC, C, IM> RoPrefix<'txn, T, KC, DC, C, IM> { + pub(crate) fn new( + cursor: RoCursor<'txn, T>, + prefix: Vec, + ) -> RoPrefix<'txn, T, KC, DC, C, IM> { RoPrefix { cursor, prefix, move_on_first: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. - pub fn move_between_keys(self) -> RoPrefix<'txn, KC, DC, C, MoveBetweenKeys> { + pub fn move_between_keys(self) -> RoPrefix<'txn, T, KC, DC, C, MoveBetweenKeys> { RoPrefix { cursor: self.cursor, prefix: self.prefix, @@ -93,7 +96,7 @@ impl<'txn, KC, DC, C, IM> RoPrefix<'txn, KC, DC, C, IM> { /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, - ) -> RoPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { + ) -> RoPrefix<'txn, T, KC, DC, C, MoveThroughDuplicateValues> { RoPrefix { cursor: self.cursor, prefix: self.prefix, @@ -103,7 +106,7 @@ impl<'txn, KC, DC, C, IM> RoPrefix<'txn, KC, DC, C, IM> { } /// Change the codec types of this iterator, specifying the codecs. - pub fn remap_types(self) -> RoPrefix<'txn, KC2, DC2, C, IM> { + pub fn remap_types(self) -> RoPrefix<'txn, T, KC2, DC2, C, IM> { RoPrefix { cursor: self.cursor, prefix: self.prefix, @@ -113,22 +116,22 @@ impl<'txn, KC, DC, C, IM> RoPrefix<'txn, KC, DC, C, IM> { } /// Change the key codec type of this iterator, specifying the new codec. - pub fn remap_key_type(self) -> RoPrefix<'txn, KC2, DC, C, IM> { + pub fn remap_key_type(self) -> RoPrefix<'txn, T, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. - pub fn remap_data_type(self) -> RoPrefix<'txn, KC, DC2, C, IM> { + pub fn remap_data_type(self) -> RoPrefix<'txn, T, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. - pub fn lazily_decode_data(self) -> RoPrefix<'txn, KC, LazyDecode, C, IM> { + pub fn lazily_decode_data(self) -> RoPrefix<'txn, T, KC, LazyDecode, C, IM> { self.remap_types::>() } } -impl<'txn, KC, DC, C, IM> Iterator for RoPrefix<'txn, KC, DC, C, IM> +impl<'txn, T, KC, DC, C, IM> Iterator for RoPrefix<'txn, T, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -163,11 +166,11 @@ where fn last(mut self) -> Option { let result = if self.move_on_first { - move_on_prefix_end::(&mut self.cursor, &mut self.prefix) + move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { match ( self.cursor.current(), - move_on_prefix_end::(&mut self.cursor, &mut self.prefix), + move_on_prefix_end::(&mut self.cursor, &mut self.prefix), ) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) @@ -200,8 +203,7 @@ impl fmt::Debug for RoPrefix<'_, KC, DC, C, IM> { } } -#[cfg(feature = "read-txn-no-tls")] -unsafe impl Send for RoPrefix<'_, KC, DC, IM> {} +unsafe impl Send for RoPrefix<'_, WithoutTls, KC, DC, IM> {} /// A read-write prefix iterator structure. pub struct RwPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { @@ -417,11 +419,11 @@ where fn last(mut self) -> Option { let result = if self.move_on_first { - move_on_prefix_end::(&mut self.cursor, &mut self.prefix) + move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { match ( self.cursor.current(), - move_on_prefix_end::(&mut self.cursor, &mut self.prefix), + move_on_prefix_end::(&mut self.cursor, &mut self.prefix), ) { (Ok(Some((ckey, _))), Ok(Some((key, data)))) if ckey != key => { Ok(Some((key, data))) @@ -455,22 +457,25 @@ impl fmt::Debug for RwPrefix<'_, KC, DC, C, IM> { } /// A reverse read-only prefix iterator structure. -pub struct RoRevPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { - cursor: RoCursor<'txn>, +pub struct RoRevPrefix<'txn, T, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { + cursor: RoCursor<'txn, T>, prefix: Vec, move_on_last: bool, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } -impl<'txn, KC, DC, C, IM> RoRevPrefix<'txn, KC, DC, C, IM> { - pub(crate) fn new(cursor: RoCursor<'txn>, prefix: Vec) -> RoRevPrefix<'txn, KC, DC, C, IM> { +impl<'txn, T, KC, DC, C, IM> RoRevPrefix<'txn, T, KC, DC, C, IM> { + pub(crate) fn new( + cursor: RoCursor<'txn, T>, + prefix: Vec, + ) -> RoRevPrefix<'txn, T, KC, DC, C, IM> { RoRevPrefix { cursor, prefix, move_on_last: true, _phantom: marker::PhantomData } } /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. - pub fn move_between_keys(self) -> RoRevPrefix<'txn, KC, DC, C, MoveBetweenKeys> { + pub fn move_between_keys(self) -> RoRevPrefix<'txn, T, KC, DC, C, MoveBetweenKeys> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, @@ -484,7 +489,7 @@ impl<'txn, KC, DC, C, IM> RoRevPrefix<'txn, KC, DC, C, IM> { /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, - ) -> RoRevPrefix<'txn, KC, DC, C, MoveThroughDuplicateValues> { + ) -> RoRevPrefix<'txn, T, KC, DC, C, MoveThroughDuplicateValues> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, @@ -494,7 +499,7 @@ impl<'txn, KC, DC, C, IM> RoRevPrefix<'txn, KC, DC, C, IM> { } /// Change the codec types of this iterator, specifying the codecs. - pub fn remap_types(self) -> RoRevPrefix<'txn, KC2, DC2, C, IM> { + pub fn remap_types(self) -> RoRevPrefix<'txn, T, KC2, DC2, C, IM> { RoRevPrefix { cursor: self.cursor, prefix: self.prefix, @@ -504,22 +509,22 @@ impl<'txn, KC, DC, C, IM> RoRevPrefix<'txn, KC, DC, C, IM> { } /// Change the key codec type of this iterator, specifying the new codec. - pub fn remap_key_type(self) -> RoRevPrefix<'txn, KC2, DC, C, IM> { + pub fn remap_key_type(self) -> RoRevPrefix<'txn, T, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. - pub fn remap_data_type(self) -> RoRevPrefix<'txn, KC, DC2, C, IM> { + pub fn remap_data_type(self) -> RoRevPrefix<'txn, T, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. - pub fn lazily_decode_data(self) -> RoRevPrefix<'txn, KC, LazyDecode, C, IM> { + pub fn lazily_decode_data(self) -> RoRevPrefix<'txn, T, KC, LazyDecode, C, IM> { self.remap_types::>() } } -impl<'txn, KC, DC, C, IM> Iterator for RoRevPrefix<'txn, KC, DC, C, IM> +impl<'txn, T, KC, DC, C, IM> Iterator for RoRevPrefix<'txn, T, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -531,7 +536,7 @@ where fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; - move_on_prefix_end::(&mut self.cursor, &mut self.prefix) + move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; @@ -590,8 +595,7 @@ impl fmt::Debug for RoRevPrefix<'_, KC, DC, C, IM> { } } -#[cfg(feature = "read-txn-no-tls")] -unsafe impl Send for RoRevPrefix<'_, KC, DC, IM> {} +unsafe impl Send for RoRevPrefix<'_, WithoutTls, KC, DC, IM> {} /// A reverse read-write prefix iterator structure. pub struct RwRevPrefix<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { @@ -784,7 +788,7 @@ where fn next(&mut self) -> Option { let result = if self.move_on_last { self.move_on_last = false; - move_on_prefix_end::(&mut self.cursor, &mut self.prefix) + move_on_prefix_end::(&mut self.cursor, &mut self.prefix) } else { self.cursor.move_on_prev(IM::MOVE_OPERATION) }; diff --git a/heed/src/iterator/range.rs b/heed/src/iterator/range.rs index b20da425..cefcdc58 100644 --- a/heed/src/iterator/range.rs +++ b/heed/src/iterator/range.rs @@ -8,8 +8,8 @@ use crate::cursor::MoveOperation; use crate::iteration_method::{IterationMethod, MoveBetweenKeys, MoveThroughDuplicateValues}; use crate::*; -fn move_on_range_end<'txn>( - cursor: &mut RoCursor<'txn>, +fn move_on_range_end<'txn, T>( + cursor: &mut RoCursor<'txn, T>, end_bound: &Bound>, ) -> Result> { match end_bound { @@ -25,8 +25,8 @@ fn move_on_range_end<'txn>( } } -fn move_on_range_start<'txn>( - cursor: &mut RoCursor<'txn>, +fn move_on_range_start<'txn, T>( + cursor: &mut RoCursor<'txn, T>, start_bound: &mut Bound>, ) -> Result> { match start_bound { @@ -40,20 +40,20 @@ fn move_on_range_start<'txn>( } /// A read-only range iterator structure. -pub struct RoRange<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { - cursor: RoCursor<'txn>, +pub struct RoRange<'txn, T, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { + cursor: RoCursor<'txn, T>, move_on_start: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } -impl<'txn, KC, DC, C, IM> RoRange<'txn, KC, DC, C, IM> { +impl<'txn, T, KC, DC, C, IM> RoRange<'txn, T, KC, DC, C, IM> { pub(crate) fn new( - cursor: RoCursor<'txn>, + cursor: RoCursor<'txn, T>, start_bound: Bound>, end_bound: Bound>, - ) -> RoRange<'txn, KC, DC, C, IM> { + ) -> RoRange<'txn, T, KC, DC, C, IM> { RoRange { cursor, move_on_start: true, @@ -66,7 +66,7 @@ impl<'txn, KC, DC, C, IM> RoRange<'txn, KC, DC, C, IM> { /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. - pub fn move_between_keys(self) -> RoRange<'txn, KC, DC, C, MoveBetweenKeys> { + pub fn move_between_keys(self) -> RoRange<'txn, T, KC, DC, C, MoveBetweenKeys> { RoRange { cursor: self.cursor, move_on_start: self.move_on_start, @@ -81,7 +81,7 @@ impl<'txn, KC, DC, C, IM> RoRange<'txn, KC, DC, C, IM> { /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, - ) -> RoRange<'txn, KC, DC, C, MoveThroughDuplicateValues> { + ) -> RoRange<'txn, T, KC, DC, C, MoveThroughDuplicateValues> { RoRange { cursor: self.cursor, move_on_start: self.move_on_start, @@ -92,7 +92,7 @@ impl<'txn, KC, DC, C, IM> RoRange<'txn, KC, DC, C, IM> { } /// Change the codec types of this iterator, specifying the codecs. - pub fn remap_types(self) -> RoRange<'txn, KC2, DC2, C, IM> { + pub fn remap_types(self) -> RoRange<'txn, T, KC2, DC2, C, IM> { RoRange { cursor: self.cursor, move_on_start: self.move_on_start, @@ -103,22 +103,22 @@ impl<'txn, KC, DC, C, IM> RoRange<'txn, KC, DC, C, IM> { } /// Change the key codec type of this iterator, specifying the new codec. - pub fn remap_key_type(self) -> RoRange<'txn, KC2, DC, C, IM> { + pub fn remap_key_type(self) -> RoRange<'txn, T, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. - pub fn remap_data_type(self) -> RoRange<'txn, KC, DC2, C, IM> { + pub fn remap_data_type(self) -> RoRange<'txn, T, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. - pub fn lazily_decode_data(self) -> RoRange<'txn, KC, LazyDecode, C, IM> { + pub fn lazily_decode_data(self) -> RoRange<'txn, T, KC, LazyDecode, C, IM> { self.remap_types::>() } } -impl<'txn, KC, DC, C, IM> Iterator for RoRange<'txn, KC, DC, C, IM> +impl<'txn, T, KC, DC, C, IM> Iterator for RoRange<'txn, T, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -199,8 +199,7 @@ impl fmt::Debug for RoRange<'_, KC, DC, C, IM> { } } -#[cfg(feature = "read-txn-no-tls")] -unsafe impl Send for RoRange<'_, KC, DC, C, IM> {} +unsafe impl Send for RoRange<'_, WithoutTls, KC, DC, C, IM> {} /// A read-write range iterator structure. pub struct RwRange<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { @@ -477,20 +476,20 @@ impl fmt::Debug for RwRange<'_, KC, DC, C, IM> { } /// A reverse read-only range iterator structure. -pub struct RoRevRange<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { - cursor: RoCursor<'txn>, +pub struct RoRevRange<'txn, T, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { + cursor: RoCursor<'txn, T>, move_on_end: bool, start_bound: Bound>, end_bound: Bound>, _phantom: marker::PhantomData<(KC, DC, C, IM)>, } -impl<'txn, KC, DC, C, IM> RoRevRange<'txn, KC, DC, C, IM> { +impl<'txn, T, KC, DC, C, IM> RoRevRange<'txn, T, KC, DC, C, IM> { pub(crate) fn new( - cursor: RoCursor<'txn>, + cursor: RoCursor<'txn, T>, start_bound: Bound>, end_bound: Bound>, - ) -> RoRevRange<'txn, KC, DC, C, IM> { + ) -> RoRevRange<'txn, T, KC, DC, C, IM> { RoRevRange { cursor, move_on_end: true, @@ -503,7 +502,7 @@ impl<'txn, KC, DC, C, IM> RoRevRange<'txn, KC, DC, C, IM> { /// Move on the first value of keys, ignoring duplicate values. /// /// For more info, see [`RoIter::move_between_keys`]. - pub fn move_between_keys(self) -> RoRevRange<'txn, KC, DC, C, MoveBetweenKeys> { + pub fn move_between_keys(self) -> RoRevRange<'txn, T, KC, DC, C, MoveBetweenKeys> { RoRevRange { cursor: self.cursor, move_on_end: self.move_on_end, @@ -518,7 +517,7 @@ impl<'txn, KC, DC, C, IM> RoRevRange<'txn, KC, DC, C, IM> { /// For more info, see [`RoIter::move_through_duplicate_values`]. pub fn move_through_duplicate_values( self, - ) -> RoRevRange<'txn, KC, DC, C, MoveThroughDuplicateValues> { + ) -> RoRevRange<'txn, T, KC, DC, C, MoveThroughDuplicateValues> { RoRevRange { cursor: self.cursor, move_on_end: self.move_on_end, @@ -529,7 +528,7 @@ impl<'txn, KC, DC, C, IM> RoRevRange<'txn, KC, DC, C, IM> { } /// Change the codec types of this iterator, specifying the codecs. - pub fn remap_types(self) -> RoRevRange<'txn, KC2, DC2, C, IM> { + pub fn remap_types(self) -> RoRevRange<'txn, T, KC2, DC2, C, IM> { RoRevRange { cursor: self.cursor, move_on_end: self.move_on_end, @@ -540,22 +539,22 @@ impl<'txn, KC, DC, C, IM> RoRevRange<'txn, KC, DC, C, IM> { } /// Change the key codec type of this iterator, specifying the new codec. - pub fn remap_key_type(self) -> RoRevRange<'txn, KC2, DC, C, IM> { + pub fn remap_key_type(self) -> RoRevRange<'txn, T, KC2, DC, C, IM> { self.remap_types::() } /// Change the data codec type of this iterator, specifying the new codec. - pub fn remap_data_type(self) -> RoRevRange<'txn, KC, DC2, C, IM> { + pub fn remap_data_type(self) -> RoRevRange<'txn, T, KC, DC2, C, IM> { self.remap_types::() } /// Wrap the data bytes into a lazy decoder. - pub fn lazily_decode_data(self) -> RoRevRange<'txn, KC, LazyDecode, C, IM> { + pub fn lazily_decode_data(self) -> RoRevRange<'txn, T, KC, LazyDecode, C, IM> { self.remap_types::>() } } -impl<'txn, KC, DC, C, IM> Iterator for RoRevRange<'txn, KC, DC, C, IM> +impl<'txn, T, KC, DC, C, IM> Iterator for RoRevRange<'txn, T, KC, DC, C, IM> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -638,8 +637,7 @@ impl fmt::Debug for RoRevRange<'_, KC, DC, C, IM> { } } -#[cfg(feature = "read-txn-no-tls")] -unsafe impl Send for RoRevRange<'_, KC, DC, C, IM> {} +unsafe impl Send for RoRevRange<'_, WithoutTls, KC, DC, C, IM> {} /// A reverse read-write range iterator structure. pub struct RwRevRange<'txn, KC, DC, C = DefaultComparator, IM = MoveThroughDuplicateValues> { diff --git a/heed/src/lib.rs b/heed/src/lib.rs index 83e0b62c..f3b0657f 100644 --- a/heed/src/lib.rs +++ b/heed/src/lib.rs @@ -104,7 +104,7 @@ use self::mdb::ffi::{from_val, into_val}; pub use self::mdb::flags::{DatabaseFlags, EnvFlags, PutFlags}; pub use self::reserved_space::ReservedSpace; pub use self::traits::{BoxedError, BytesDecode, BytesEncode, Comparator, LexicographicComparator}; -pub use self::txn::{RoTxn, RwTxn}; +pub use self::txn::{RoTxn, RwTxn, WithTls, WithoutTls}; /// The underlying LMDB library version information. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/heed/src/mdb/lmdb_flags.rs b/heed/src/mdb/lmdb_flags.rs index 1c623c26..4040d30f 100644 --- a/heed/src/mdb/lmdb_flags.rs +++ b/heed/src/mdb/lmdb_flags.rs @@ -28,6 +28,7 @@ bitflags! { /// Use asynchronous msync when MDB_WRITEMAP is used. const MAP_ASYNC = ffi::MDB_MAPASYNC; /// Tie reader locktable slots to MDB_txn objects instead of to threads. + #[deprecated(since="0.22.0", note="please use `Env::read_txn_with_tls` or `Env::read_txn_without_tls` instead")] const NO_TLS = ffi::MDB_NOTLS; /// Don't do any locking, caller must manage their own locks. const NO_LOCK = ffi::MDB_NOLOCK; diff --git a/heed/src/txn.rs b/heed/src/txn.rs index b73c4992..e7dfa010 100644 --- a/heed/src/txn.rs +++ b/heed/src/txn.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::marker::PhantomData; use std::ops::Deref; use std::ptr::{self, NonNull}; @@ -45,14 +46,15 @@ use crate::Result; /// } /// } /// ``` -pub struct RoTxn<'e> { +pub struct RoTxn<'e, T> { /// Makes the struct covariant and !Sync pub(crate) txn: Option>, - env: Cow<'e, Env>, + env: Cow<'e, Env>, + _tls_marker: PhantomData, } -impl<'e> RoTxn<'e> { - pub(crate) fn new(env: &'e Env) -> Result> { +impl<'e, T> RoTxn<'e, T> { + pub(crate) fn new(env: &'e Env) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); unsafe { @@ -64,10 +66,10 @@ impl<'e> RoTxn<'e> { ))? }; - Ok(RoTxn { txn: NonNull::new(txn), env: Cow::Borrowed(env) }) + Ok(RoTxn { txn: NonNull::new(txn), env: Cow::Borrowed(env), _tls_marker: PhantomData }) } - pub(crate) fn static_read_txn(env: Env) -> Result> { + pub(crate) fn static_read_txn(env: Env) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); unsafe { @@ -79,7 +81,7 @@ impl<'e> RoTxn<'e> { ))? }; - Ok(RoTxn { txn: NonNull::new(txn), env: Cow::Owned(env) }) + Ok(RoTxn { txn: NonNull::new(txn), env: Cow::Owned(env), _tls_marker: PhantomData }) } pub(crate) fn env_mut_ptr(&self) -> NonNull { @@ -104,7 +106,7 @@ impl<'e> RoTxn<'e> { } } -impl Drop for RoTxn<'_> { +impl Drop for RoTxn<'_, T> { fn drop(&mut self) { if let Some(mut txn) = self.txn.take() { // Asserts that the transaction hasn't been already @@ -114,8 +116,28 @@ impl Drop for RoTxn<'_> { } } -#[cfg(feature = "read-txn-no-tls")] -unsafe impl Send for RoTxn<'_> {} +/// Parameter defining that read transactions are opened with +/// Thread Local Storage (TLS) and cannot be sent between threads `!Send`. +pub enum WithTls {} + +/// Parameter defining that read transactions are opened without +/// Thread Local Storage (TLS) and are therefore `Send`. +pub enum WithoutTls {} + +pub trait TlsUsage { + const ENABLED: bool; +} + +impl TlsUsage for WithTls { + const ENABLED: bool = true; +} + +impl TlsUsage for WithoutTls { + const ENABLED: bool = false; +} + +/// Is sendable only if `MDB_NOTLS` has been used to open this transaction. +unsafe impl Send for RoTxn<'_, WithoutTls> {} /// A read-write transaction. /// @@ -155,11 +177,11 @@ unsafe impl Send for RoTxn<'_> {} /// } /// ``` pub struct RwTxn<'p> { - pub(crate) txn: RoTxn<'p>, + pub(crate) txn: RoTxn<'p, WithoutTls>, } impl<'p> RwTxn<'p> { - pub(crate) fn new(env: &'p Env) -> Result> { + pub(crate) fn new(env: &'p Env) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); unsafe { @@ -171,10 +193,16 @@ impl<'p> RwTxn<'p> { ))? }; - Ok(RwTxn { txn: RoTxn { txn: NonNull::new(txn), env: Cow::Borrowed(env) } }) + Ok(RwTxn { + txn: RoTxn { + txn: NonNull::new(txn), + env: Cow::Borrowed(env), + _tls_marker: PhantomData, + }, + }) } - pub(crate) fn nested(env: &'p Env, parent: &'p mut RwTxn) -> Result> { + pub(crate) fn nested(env: &'p Env, parent: &'p mut RwTxn) -> Result> { let mut txn: *mut ffi::MDB_txn = ptr::null_mut(); let parent_ptr: *mut ffi::MDB_txn = unsafe { parent.txn.txn.unwrap().as_mut() }; @@ -182,7 +210,13 @@ impl<'p> RwTxn<'p> { mdb_result(ffi::mdb_txn_begin(env.env_mut_ptr().as_mut(), parent_ptr, 0, &mut txn))? }; - Ok(RwTxn { txn: RoTxn { txn: NonNull::new(txn), env: Cow::Borrowed(env) } }) + Ok(RwTxn { + txn: RoTxn { + txn: NonNull::new(txn), + env: Cow::Borrowed(env), + _tls_marker: PhantomData, + }, + }) } pub(crate) fn env_mut_ptr(&self) -> NonNull { @@ -210,7 +244,7 @@ impl<'p> RwTxn<'p> { } impl<'p> Deref for RwTxn<'p> { - type Target = RoTxn<'p>; + type Target = RoTxn<'p, WithoutTls>; fn deref(&self) -> &Self::Target { &self.txn From 630c94b0cfe73928a7d1ce6db3a37e909ad899fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Sat, 14 Dec 2024 12:27:47 +0100 Subject: [PATCH 02/12] Fix most of the issues --- heed/src/cookbook.rs | 2 +- heed/src/databases/database.rs | 4 ++-- heed/src/envs/env.rs | 6 +++++- heed/src/txn.rs | 18 +++++++++++------- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/heed/src/cookbook.rs b/heed/src/cookbook.rs index d0fd0be1..c47d5f9c 100644 --- a/heed/src/cookbook.rs +++ b/heed/src/cookbook.rs @@ -423,7 +423,7 @@ //! } //! //! impl<'t> ImmutableMap<'t> { -//! fn from_db(rtxn: &'t RoTxn, db: Database) -> heed::Result { +//! fn from_db(rtxn: &'t RoTxn, db: Database) -> heed::Result { //! let mut map = HashMap::new(); //! for result in db.iter(rtxn)? { //! let (k, v) = result?; diff --git a/heed/src/databases/database.rs b/heed/src/databases/database.rs index c5226634..de9b31df 100644 --- a/heed/src/databases/database.rs +++ b/heed/src/databases/database.rs @@ -1079,7 +1079,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { + pub fn iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwIter::new(cursor)) @@ -1182,7 +1182,7 @@ impl Database { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { + pub fn rev_iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { assert_eq_env_db_txn!(self, txn); RwCursor::new(txn, self.dbi).map(|cursor| RwRevIter::new(cursor)) diff --git a/heed/src/envs/env.rs b/heed/src/envs/env.rs index 52b86200..e1921388 100644 --- a/heed/src/envs/env.rs +++ b/heed/src/envs/env.rs @@ -22,7 +22,7 @@ use crate::mdb::lmdb_flags::AllDatabaseFlags; use crate::EnvOpenOptions; use crate::{ CompactionOption, Database, DatabaseOpenOptions, EnvFlags, Error, Result, RoTxn, RwTxn, - Unspecified, + Unspecified, WithoutTls, }; /// An environment handle constructed by using [`EnvOpenOptions::open`]. @@ -43,6 +43,10 @@ impl Env { self.inner.env_ptr } + pub(crate) unsafe fn as_without_tls(&self) -> &Env { + unsafe { std::mem::transmute::<&Env, &Env>(self) } + } + /// The size of the data file on disk. /// /// # Example diff --git a/heed/src/txn.rs b/heed/src/txn.rs index e7dfa010..18ecc4c7 100644 --- a/heed/src/txn.rs +++ b/heed/src/txn.rs @@ -33,15 +33,15 @@ use crate::Result; /// ```rust /// #[allow(dead_code)] /// trait CovariantMarker<'a>: 'static { -/// type T: 'a; +/// type R: 'a; /// -/// fn is_covariant(&'a self) -> &'a Self::T; +/// fn is_covariant(&'a self) -> &'a Self::R; /// } /// -/// impl<'a> CovariantMarker<'a> for heed::RoTxn<'static> { -/// type T = heed::RoTxn<'a>; +/// impl<'a, T> CovariantMarker<'a> for heed::RoTxn<'static, T> { +/// type R = heed::RoTxn<'a, T>; /// -/// fn is_covariant(&'a self) -> &'a heed::RoTxn<'a> { +/// fn is_covariant(&'a self) -> &'a heed::RoTxn<'a, T> { /// self /// } /// } @@ -193,10 +193,12 @@ impl<'p> RwTxn<'p> { ))? }; + let env_without_tls = unsafe { env.as_without_tls() }; + Ok(RwTxn { txn: RoTxn { txn: NonNull::new(txn), - env: Cow::Borrowed(env), + env: Cow::Borrowed(env_without_tls), _tls_marker: PhantomData, }, }) @@ -210,10 +212,12 @@ impl<'p> RwTxn<'p> { mdb_result(ffi::mdb_txn_begin(env.env_mut_ptr().as_mut(), parent_ptr, 0, &mut txn))? }; + let env_without_tls = unsafe { env.as_without_tls() }; + Ok(RwTxn { txn: RoTxn { txn: NonNull::new(txn), - env: Cow::Borrowed(env), + env: Cow::Borrowed(env_without_tls), _tls_marker: PhantomData, }, }) From d9503bad2cbf8d992e7cba4ec14ea0c57dd8ddc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Sat, 14 Dec 2024 14:13:21 +0100 Subject: [PATCH 03/12] Make it work with EncryptedEnv --- heed/src/databases/database.rs | 4 +- heed/src/databases/encrypted_database.rs | 96 ++++++++++++------------ heed/src/envs/encrypted_env.rs | 21 +++--- heed/src/envs/env.rs | 6 ++ heed/src/envs/env_open_options.rs | 2 +- 5 files changed, 67 insertions(+), 62 deletions(-) diff --git a/heed/src/databases/database.rs b/heed/src/databases/database.rs index de9b31df..e17a02b6 100644 --- a/heed/src/databases/database.rs +++ b/heed/src/databases/database.rs @@ -172,13 +172,13 @@ impl<'e, 'n, T, KC, DC, C> DatabaseOpenOptions<'e, 'n, T, KC, DC, C> { } } -impl Clone for DatabaseOpenOptions<'_, '_, KC, DC, C> { +impl Clone for DatabaseOpenOptions<'_, '_, T, KC, DC, C> { fn clone(&self) -> Self { *self } } -impl Copy for DatabaseOpenOptions<'_, '_, KC, DC, C> {} +impl Copy for DatabaseOpenOptions<'_, '_, T, KC, DC, C> {} /// A typed database that accepts only the types it was created with. /// diff --git a/heed/src/databases/encrypted_database.rs b/heed/src/databases/encrypted_database.rs index 0c19b655..4e06cbc1 100644 --- a/heed/src/databases/encrypted_database.rs +++ b/heed/src/databases/encrypted_database.rs @@ -53,29 +53,29 @@ use crate::*; /// # Ok(()) } /// ``` #[derive(Debug)] -pub struct EncryptedDatabaseOpenOptions<'e, 'n, KC, DC, C = DefaultComparator> { - inner: DatabaseOpenOptions<'e, 'n, KC, DC, C>, +pub struct EncryptedDatabaseOpenOptions<'e, 'n, T, KC, DC, C = DefaultComparator> { + inner: DatabaseOpenOptions<'e, 'n, T, KC, DC, C>, } -impl<'e> EncryptedDatabaseOpenOptions<'e, 'static, Unspecified, Unspecified> { +impl<'e, T> EncryptedDatabaseOpenOptions<'e, 'static, T, Unspecified, Unspecified> { /// Create an options struct to open/create a database with specific flags. - pub fn new(env: &'e EncryptedEnv) -> Self { + pub fn new(env: &'e EncryptedEnv) -> Self { EncryptedDatabaseOpenOptions { inner: DatabaseOpenOptions::new(&env.inner) } } } -impl<'e, 'n, KC, DC, C> EncryptedDatabaseOpenOptions<'e, 'n, KC, DC, C> { +impl<'e, 'n, T, KC, DC, C> EncryptedDatabaseOpenOptions<'e, 'n, T, KC, DC, C> { /// Change the type of the database. /// /// The default types are [`Unspecified`] and require a call to [`Database::remap_types`] /// to use the [`Database`]. - pub fn types(self) -> EncryptedDatabaseOpenOptions<'e, 'n, NKC, NDC> { + pub fn types(self) -> EncryptedDatabaseOpenOptions<'e, 'n, T, NKC, NDC> { EncryptedDatabaseOpenOptions { inner: self.inner.types() } } /// Change the customized key compare function of the database. /// /// By default no customized compare function will be set when opening a database. - pub fn key_comparator(self) -> EncryptedDatabaseOpenOptions<'e, 'n, KC, DC, NC> { + pub fn key_comparator(self) -> EncryptedDatabaseOpenOptions<'e, 'n, T, KC, DC, NC> { EncryptedDatabaseOpenOptions { inner: self.inner.key_comparator() } } @@ -111,7 +111,7 @@ impl<'e, 'n, KC, DC, C> EncryptedDatabaseOpenOptions<'e, 'n, KC, DC, C> { /// /// If not done, you might raise `Io(Os { code: 22, kind: InvalidInput, message: "Invalid argument" })` /// known as `EINVAL`. - pub fn open(&self, rtxn: &RoTxn) -> Result>> + pub fn open(&self, rtxn: &RoTxn) -> Result>> where KC: 'static, DC: 'static, @@ -139,13 +139,13 @@ impl<'e, 'n, KC, DC, C> EncryptedDatabaseOpenOptions<'e, 'n, KC, DC, C> { } } -impl Clone for EncryptedDatabaseOpenOptions<'_, '_, KC, DC, C> { +impl Clone for EncryptedDatabaseOpenOptions<'_, '_, T, KC, DC, C> { fn clone(&self) -> Self { *self } } -impl Copy for EncryptedDatabaseOpenOptions<'_, '_, KC, DC, C> {} +impl Copy for EncryptedDatabaseOpenOptions<'_, '_, T, KC, DC, C> {} /// A typed database that accepts only the types it was created with. /// @@ -305,9 +305,9 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get<'a, 'txn>( + pub fn get<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -369,11 +369,11 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_duplicates<'a, 'txn>( + pub fn get_duplicates<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, key: &'a KC::EItem, - ) -> Result>> + ) -> Result>> where KC: BytesEncode<'a>, { @@ -424,9 +424,9 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_lower_than<'a, 'txn>( + pub fn get_lower_than<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -480,9 +480,9 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_lower_than_or_equal_to<'a, 'txn>( + pub fn get_lower_than_or_equal_to<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -536,9 +536,9 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_greater_than<'a, 'txn>( + pub fn get_greater_than<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -592,9 +592,9 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_greater_than_or_equal_to<'a, 'txn>( + pub fn get_greater_than_or_equal_to<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, key: &'a KC::EItem, ) -> Result> where @@ -640,7 +640,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn first<'txn>(&self, txn: &'txn mut RoTxn) -> Result> + pub fn first<'txn, T>(&self, txn: &'txn mut RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -684,7 +684,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn last<'txn>(&self, txn: &'txn mut RoTxn) -> Result> + pub fn last<'txn, T>(&self, txn: &'txn mut RoTxn) -> Result> where KC: BytesDecode<'txn>, DC: BytesDecode<'txn>, @@ -731,7 +731,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn len(&self, txn: &RoTxn) -> Result { + pub fn len(&self, txn: &RoTxn) -> Result { self.inner.len(txn) } @@ -774,7 +774,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn is_empty(&self, txn: &RoTxn) -> Result { + pub fn is_empty(&self, txn: &RoTxn) -> Result { self.inner.is_empty(txn) } @@ -816,7 +816,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn stat(&self, txn: &RoTxn) -> Result { + pub fn stat(&self, txn: &RoTxn) -> Result { self.inner.stat(txn) } @@ -860,7 +860,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn iter<'txn>(&self, txn: &'txn mut RoTxn) -> Result> { + pub fn iter<'txn, T>(&self, txn: &'txn mut RoTxn) -> Result> { self.inner.iter(txn) } @@ -914,7 +914,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { + pub fn iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { self.inner.iter_mut(txn) } @@ -958,7 +958,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_iter<'txn>(&self, txn: &'txn mut RoTxn) -> Result> { + pub fn rev_iter<'txn, T>(&self, txn: &'txn mut RoTxn) -> Result> { self.inner.rev_iter(txn) } @@ -1013,7 +1013,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_iter_mut<'txn>(&self, txn: &'txn mut RwTxn) -> Result> { + pub fn rev_iter_mut<'txn, T>(&self, txn: &'txn mut RwTxn) -> Result> { self.inner.rev_iter_mut(txn) } @@ -1060,11 +1060,11 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn range<'a, 'txn, R>( + pub fn range<'a, 'txn, R, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, range: &'a R, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, @@ -1182,11 +1182,11 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_range<'a, 'txn, R>( + pub fn rev_range<'a, 'txn, R, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, range: &'a R, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, R: RangeBounds, @@ -1305,11 +1305,11 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn prefix_iter<'a, 'txn>( + pub fn prefix_iter<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, prefix: &'a KC::EItem, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, @@ -1372,7 +1372,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn prefix_iter_mut<'a, 'txn>( + pub fn prefix_iter_mut<'a, 'txn, T>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, @@ -1429,11 +1429,11 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_prefix_iter<'a, 'txn>( + pub fn rev_prefix_iter<'a, 'txn, T>( &self, - txn: &'txn mut RoTxn, + txn: &'txn mut RoTxn, prefix: &'a KC::EItem, - ) -> Result> + ) -> Result> where KC: BytesEncode<'a>, C: LexicographicComparator, @@ -1496,7 +1496,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn rev_prefix_iter_mut<'a, 'txn>( + pub fn rev_prefix_iter_mut<'a, 'txn, T>( &self, txn: &'txn mut RwTxn, prefix: &'a KC::EItem, @@ -1707,7 +1707,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_or_put<'a, 'txn>( + pub fn get_or_put<'a, 'txn, T>( &'txn self, txn: &mut RwTxn, key: &'a KC::EItem, @@ -1754,7 +1754,7 @@ impl EncryptedDatabase { /// wtxn.commit()?; /// # Ok(()) } /// ``` - pub fn get_or_put_with_flags<'a, 'txn>( + pub fn get_or_put_with_flags<'a, 'txn, T>( &'txn self, txn: &mut RwTxn, flags: PutFlags, diff --git a/heed/src/envs/encrypted_env.rs b/heed/src/envs/encrypted_env.rs index 4f558acc..d1347e74 100644 --- a/heed/src/envs/encrypted_env.rs +++ b/heed/src/envs/encrypted_env.rs @@ -15,11 +15,11 @@ use crate::{Database, EnvOpenOptions}; /// An environment handle constructed by using [`EnvOpenOptions::open_encrypted`]. #[derive(Clone)] -pub struct EncryptedEnv { - pub(crate) inner: Env, +pub struct EncryptedEnv { + pub(crate) inner: Env, } -impl EncryptedEnv { +impl EncryptedEnv { /// The size of the data file on disk. /// /// # Example @@ -103,7 +103,7 @@ impl EncryptedEnv { } /// Options and flags which can be used to configure how a [`Database`] is opened. - pub fn database_options(&self) -> EncryptedDatabaseOpenOptions { + pub fn database_options(&self) -> EncryptedDatabaseOpenOptions { EncryptedDatabaseOpenOptions::new(self) } @@ -127,7 +127,7 @@ impl EncryptedEnv { /// known as `EINVAL`. pub fn open_database( &self, - rtxn: &RoTxn, + rtxn: &RoTxn, name: Option<&str>, ) -> Result>> where @@ -213,7 +213,7 @@ impl EncryptedEnv { /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full - pub fn read_txn(&self) -> Result { + pub fn read_txn(&self) -> Result> { self.inner.read_txn() } @@ -242,7 +242,7 @@ impl EncryptedEnv { /// map must be resized /// * [`crate::MdbError::ReadersFull`]: a read-only transaction was requested, and the reader lock table is /// full - pub fn static_read_txn(self) -> Result> { + pub fn static_read_txn(self) -> Result> { self.inner.static_read_txn() } @@ -315,11 +315,10 @@ impl EncryptedEnv { } } -unsafe impl Send for EncryptedEnv {} +unsafe impl Send for EncryptedEnv {} +unsafe impl Sync for EncryptedEnv {} -unsafe impl Sync for EncryptedEnv {} - -impl fmt::Debug for EncryptedEnv { +impl fmt::Debug for EncryptedEnv { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("EncryptedEnv") .field("path", &self.inner.path().display()) diff --git a/heed/src/envs/env.rs b/heed/src/envs/env.rs index e1921388..8d2f0793 100644 --- a/heed/src/envs/env.rs +++ b/heed/src/envs/env.rs @@ -43,6 +43,12 @@ impl Env { self.inner.env_ptr } + /// Converts any `Env` into `Env`, useful for wrapping + /// into a `RwTxn` due to the latter always being `WithoutTls`. + /// + /// # Safety + /// + /// Do not use this `Env` to create transactions but only keep it. pub(crate) unsafe fn as_without_tls(&self) -> &Env { unsafe { std::mem::transmute::<&Env, &Env>(self) } } diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index 8dbc9471..1ebd2fd7 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -322,7 +322,7 @@ impl EnvOpenOptions { /// [^7]: /// [^8]: #[cfg(master3)] - pub unsafe fn open_encrypted(&self, key: Key, path: P) -> Result + pub unsafe fn open_encrypted(&self, key: Key, path: P) -> Result> where E: AeadMutInPlace + KeyInit, P: AsRef, From 4faa7bcbb910c0f3bf07590caeb19bbbb5c14179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Sun, 15 Dec 2024 13:33:26 +0100 Subject: [PATCH 04/12] Document the WithTls, WithoutTls and TlsUsage types and traits --- heed/Cargo.toml | 15 --------------- heed/src/envs/env_open_options.rs | 16 ++++++++++++++-- heed/src/lib.rs | 2 +- heed/src/txn.rs | 22 +++++++++++++++++++++- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/heed/Cargo.toml b/heed/Cargo.toml index c7e5ef9f..3152a49a 100644 --- a/heed/Cargo.toml +++ b/heed/Cargo.toml @@ -44,21 +44,6 @@ url = "2.5.4" default = ["serde", "serde-bincode", "serde-json"] serde = ["bitflags/serde", "dep:serde"] -# The #MDB_NOTLS flag is automatically set on Env opening, -# RoTxn and RoCursors implements the Send trait. This allows the -# user to move RoTxns and RoCursors between threads as read transactions -# will no more use thread local storage and will tie reader locktable -# slots to #MDB_txn objects instead of to threads. -# -# According to the LMDB documentation, when this feature is not enabled: -# A thread can only use one transaction at a time, plus any child -# transactions. Each transaction belongs to one thread. [...] -# The #MDB_NOTLS flag changes this for read-only transactions. -# -# And a #MDB_BAD_RSLOT error will be thrown when multiple read -# transactions exists on the same thread -# read-txn-no-tls = [] - # Enable the serde en/decoders for bincode, serde_json, or rmp_serde serde-bincode = ["heed-types/serde-bincode"] serde-json = ["heed-types/serde-json"] diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index 1ebd2fd7..b83dfd45 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -56,13 +56,25 @@ impl EnvOpenOptions { } impl EnvOpenOptions { - /// Make the read transactions `!Send` by specifying they will use Thread Local Storage (TLS). + /// Make the read transactions `!Send` by specifying they will + /// use Thread Local Storage (TLS). It is often faster to open + /// TLS-backed transactions. + /// + /// A thread can only use one transaction at a time, plus any + /// child (nested) transactions. Each transaction belongs to one + /// thread. A `BadRslot` error will be thrown when multiple read + /// transactions exists on the same thread. pub fn read_txn_with_tls(self) -> EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } } - /// Make the read transactions `Send` by specifying they will not use Thread Local Storage (TLS). + /// Make the read transactions `Send` by specifying they will + /// not use Thread Local Storage (TLS). + /// + /// A thread can use any number of read transactions at a time on + /// the same thread. Read transactions can be moved in between + /// threads (`Send`). pub fn read_txn_without_tls(self) -> EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } diff --git a/heed/src/lib.rs b/heed/src/lib.rs index f3b0657f..5baf29f5 100644 --- a/heed/src/lib.rs +++ b/heed/src/lib.rs @@ -104,7 +104,7 @@ use self::mdb::ffi::{from_val, into_val}; pub use self::mdb::flags::{DatabaseFlags, EnvFlags, PutFlags}; pub use self::reserved_space::ReservedSpace; pub use self::traits::{BoxedError, BytesDecode, BytesEncode, Comparator, LexicographicComparator}; -pub use self::txn::{RoTxn, RwTxn, WithTls, WithoutTls}; +pub use self::txn::{RoTxn, RwTxn, TlsUsage, WithTls, WithoutTls}; /// The underlying LMDB library version information. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/heed/src/txn.rs b/heed/src/txn.rs index 18ecc4c7..c97f71b1 100644 --- a/heed/src/txn.rs +++ b/heed/src/txn.rs @@ -117,14 +117,34 @@ impl Drop for RoTxn<'_, T> { } /// Parameter defining that read transactions are opened with -/// Thread Local Storage (TLS) and cannot be sent between threads `!Send`. +/// Thread Local Storage (TLS) and cannot be sent between threads +/// `!Send`. It is often faster to open TLS-backed transactions. +/// +/// When used to open transactions: A thread can only use one transaction +/// at a time, plus any child (nested) transactions. Each transaction belongs +/// to one thread. A `BadRslot` error will be thrown when multiple read +/// transactions exists on the same thread. pub enum WithTls {} /// Parameter defining that read transactions are opened without /// Thread Local Storage (TLS) and are therefore `Send`. +/// +/// When used to open transactions: A thread can use any number +/// of read transactions at a time on the same thread. Read transactions +/// can be moved in between threads (`Send`). pub enum WithoutTls {} +/// Specifycies if Thread Local Storage (TLS) must be used when +/// opening transactions. It is often faster to open TLS-backed +/// transactions but makes them `!Send`. +/// +/// The `#MDB_NOTLS` flag is set on `Env` opening, `RoTxn`s and +/// iterators implements the `Send` trait. This allows the user to +/// move `RoTxn`s and iterators between threads as read transactions +/// will no more use thread local storage and will tie reader +/// locktable slots to transaction objects instead of to threads. pub trait TlsUsage { + /// True if TLS must be used, false otherwise. const ENABLED: bool; } From 5eeecfca8bbeb30f8339c8c7f1a66c2d75f7d29a Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Sun, 15 Dec 2024 19:25:01 +0100 Subject: [PATCH 05/12] Refine map size documentation --- heed/src/envs/env_open_options.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index b83dfd45..e3457813 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -83,6 +83,8 @@ impl EnvOpenOptions { impl EnvOpenOptions { /// Set the size of the memory map to use for this environment. + /// + /// It must be a multiple of the OS page size. pub fn map_size(&mut self, size: usize) -> &mut Self { self.map_size = Some(size); self From 74fe0242fc468d079e4bbfe1344150d90cf51cae Mon Sep 17 00:00:00 2001 From: Kerollmops Date: Sun, 15 Dec 2024 19:37:12 +0100 Subject: [PATCH 06/12] Add an example to the WithTls and WithoutTls EnvOpenOptions functions --- heed/src/envs/env_open_options.rs | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index e3457813..a72d5a56 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -64,6 +64,29 @@ impl EnvOpenOptions { /// child (nested) transactions. Each transaction belongs to one /// thread. A `BadRslot` error will be thrown when multiple read /// transactions exists on the same thread. + /// + /// # Example + /// + /// This example shows that the `RoTxn<'_, WithTls>` cannot be sent between threads. + /// + /// ```compile_fail + /// use std::fs; + /// use std::path::Path; + /// use heed::{EnvOpenOptions, Database, EnvFlags}; + /// use heed::types::*; + /// + /// /// Checks, at compile time, that a type can be sent accross threads. + /// fn is_sendable(_x: S) {} + /// + /// # fn main() -> Result<(), Box> { + /// let mut env_builder = EnvOpenOptions::new().read_txn_with_tls(); + /// let dir = tempfile::tempdir().unwrap(); + /// let env = unsafe { env_builder.open(dir.path())? }; + /// + /// let rtxn = env.read_txn()?; + /// is_sendable(rtxn); + /// # Ok(()) } + /// ``` pub fn read_txn_with_tls(self) -> EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } @@ -75,6 +98,30 @@ impl EnvOpenOptions { /// A thread can use any number of read transactions at a time on /// the same thread. Read transactions can be moved in between /// threads (`Send`). + /// + /// + /// # Example + /// + /// This example shows that the `RoTxn<'_, WithoutTls>` can be sent between threads. + /// + /// ``` + /// use std::fs; + /// use std::path::Path; + /// use heed::{EnvOpenOptions, Database, EnvFlags}; + /// use heed::types::*; + /// + /// /// Checks, at compile time, that a type can be sent accross threads. + /// fn is_sendable(_x: S) {} + /// + /// # fn main() -> Result<(), Box> { + /// let mut env_builder = EnvOpenOptions::new().read_txn_without_tls(); + /// let dir = tempfile::tempdir().unwrap(); + /// let env = unsafe { env_builder.open(dir.path())? }; + /// + /// let rtxn = env.read_txn()?; + /// is_sendable(rtxn); + /// # Ok(()) } + /// ``` pub fn read_txn_without_tls(self) -> EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } From 607bfafd29ea49f0e9cc8777873aee472768eaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Sun, 15 Dec 2024 21:13:34 +0100 Subject: [PATCH 07/12] Add the original LMDB's doc --- heed/src/envs/env_open_options.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index a72d5a56..6ba2774d 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -99,6 +99,16 @@ impl EnvOpenOptions { /// the same thread. Read transactions can be moved in between /// threads (`Send`). /// + /// ## From LMDB's documentation + /// + /// Don't use Thread-Local Storage. Tie reader locktable slots to + /// #MDB_txn objects instead of to threads. I.e. #mdb_txn_reset() keeps + /// the slot reserved for the #MDB_txn object. A thread may use parallel + /// read-only transactions. A read-only transaction may span threads if + /// the user synchronizes its use. Applications that multiplex many + /// user threads over individual OS threads need this option. Such an + /// application must also serialize the write transactions in an OS + /// thread, since LMDB's write locking is unaware of the user threads. /// /// # Example /// From 8f6db07dcf45dd2acc1123d56970dc001c5a61d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 20 Dec 2024 16:00:56 +0100 Subject: [PATCH 08/12] Make WithTls the default EnvOpenOptions version --- heed/src/envs/env_open_options.rs | 6 +++--- heed/src/txn.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index 6ba2774d..93f2af29 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -36,15 +36,15 @@ pub struct EnvOpenOptions { _tls_marker: PhantomData, } -impl Default for EnvOpenOptions { +impl Default for EnvOpenOptions { fn default() -> Self { Self::new() } } -impl EnvOpenOptions { +impl EnvOpenOptions { /// Creates a blank new set of options ready for configuration. - pub fn new() -> EnvOpenOptions { + pub fn new() -> EnvOpenOptions { EnvOpenOptions { map_size: None, max_readers: None, diff --git a/heed/src/txn.rs b/heed/src/txn.rs index c97f71b1..af5ed8ee 100644 --- a/heed/src/txn.rs +++ b/heed/src/txn.rs @@ -46,7 +46,7 @@ use crate::Result; /// } /// } /// ``` -pub struct RoTxn<'e, T> { +pub struct RoTxn<'e, T = WithTls> { /// Makes the struct covariant and !Sync pub(crate) txn: Option>, env: Cow<'e, Env>, From a4c3bd2ea206506b348733fa99f6397fa59faf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 20 Dec 2024 16:13:34 +0100 Subject: [PATCH 09/12] Fix some derive trait issues on EnvOpenOptions --- heed/src/envs/env_open_options.rs | 24 ++++++++++++++---------- heed/src/txn.rs | 2 ++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index 93f2af29..dbbb1348 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -26,7 +26,7 @@ use crate::txn::{TlsUsage, WithoutTls}; use crate::{EnvFlags, Error, Result, WithTls}; /// Options and flags which can be used to configure how an environment is opened. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EnvOpenOptions { map_size: Option, @@ -36,12 +36,6 @@ pub struct EnvOpenOptions { _tls_marker: PhantomData, } -impl Default for EnvOpenOptions { - fn default() -> Self { - Self::new() - } -} - impl EnvOpenOptions { /// Creates a blank new set of options ready for configuration. pub fn new() -> EnvOpenOptions { @@ -136,9 +130,6 @@ impl EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } } -} - -impl EnvOpenOptions { /// Set the size of the memory map to use for this environment. /// /// It must be a multiple of the OS page size. @@ -494,3 +485,16 @@ impl EnvOpenOptions { } } } + +impl Default for EnvOpenOptions { + fn default() -> Self { + Self::new() + } +} + +impl Clone for EnvOpenOptions { + fn clone(&self) -> Self { + let Self { map_size, max_readers, max_dbs, flags, _tls_marker } = *self; + EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker } + } +} diff --git a/heed/src/txn.rs b/heed/src/txn.rs index af5ed8ee..2d99e136 100644 --- a/heed/src/txn.rs +++ b/heed/src/txn.rs @@ -124,6 +124,7 @@ impl Drop for RoTxn<'_, T> { /// at a time, plus any child (nested) transactions. Each transaction belongs /// to one thread. A `BadRslot` error will be thrown when multiple read /// transactions exists on the same thread. +#[derive(Debug, PartialEq, Eq)] pub enum WithTls {} /// Parameter defining that read transactions are opened without @@ -132,6 +133,7 @@ pub enum WithTls {} /// When used to open transactions: A thread can use any number /// of read transactions at a time on the same thread. Read transactions /// can be moved in between threads (`Send`). +#[derive(Debug, PartialEq, Eq)] pub enum WithoutTls {} /// Specifycies if Thread Local Storage (TLS) must be used when From 53e8319481861c7ef1764b95d4a19f4d036cfd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 20 Dec 2024 16:43:51 +0100 Subject: [PATCH 10/12] Fix dump mistake and do not use TLS when we said so --- heed/src/envs/env_open_options.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/heed/src/envs/env_open_options.rs b/heed/src/envs/env_open_options.rs index dbbb1348..463a07a2 100644 --- a/heed/src/envs/env_open_options.rs +++ b/heed/src/envs/env_open_options.rs @@ -22,8 +22,8 @@ use super::{canonicalize_path, OPENED_ENV}; use crate::envs::OsStrExtLmdb as _; use crate::mdb::error::mdb_result; use crate::mdb::ffi; -use crate::txn::{TlsUsage, WithoutTls}; -use crate::{EnvFlags, Error, Result, WithTls}; +use crate::txn::{TlsUsage, WithTls, WithoutTls}; +use crate::{EnvFlags, Error, Result}; /// Options and flags which can be used to configure how an environment is opened. #[derive(Debug, PartialEq, Eq)] @@ -130,6 +130,7 @@ impl EnvOpenOptions { let Self { map_size, max_readers, max_dbs, flags, _tls_marker: _ } = self; EnvOpenOptions { map_size, max_readers, max_dbs, flags, _tls_marker: PhantomData } } + /// Set the size of the memory map to use for this environment. /// /// It must be a multiple of the OS page size. @@ -462,9 +463,9 @@ impl EnvOpenOptions { #[allow(deprecated)] // NO_TLS is inside of the crate let flags = if T::ENABLED { // TODO make this a ZST flag on the Env and on RoTxn (make them Send when we can) - self.flags | EnvFlags::NO_TLS - } else { self.flags + } else { + self.flags | EnvFlags::NO_TLS }; let result = ffi::mdb_env_open(env, path_str.as_ptr(), flags.bits(), 0o600); From f92b5ad337e998d36b002cfdf118e3b35f639d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 20 Dec 2024 17:23:58 +0100 Subject: [PATCH 11/12] Fix deprecation message --- heed/src/mdb/lmdb_flags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heed/src/mdb/lmdb_flags.rs b/heed/src/mdb/lmdb_flags.rs index 4040d30f..d41881fc 100644 --- a/heed/src/mdb/lmdb_flags.rs +++ b/heed/src/mdb/lmdb_flags.rs @@ -28,7 +28,7 @@ bitflags! { /// Use asynchronous msync when MDB_WRITEMAP is used. const MAP_ASYNC = ffi::MDB_MAPASYNC; /// Tie reader locktable slots to MDB_txn objects instead of to threads. - #[deprecated(since="0.22.0", note="please use `Env::read_txn_with_tls` or `Env::read_txn_without_tls` instead")] + #[deprecated(since="0.22.0", note="please use `EnvOpenOptions::read_txn_with_tls` or `EnvOpenOptions::read_txn_without_tls` instead")] const NO_TLS = ffi::MDB_NOTLS; /// Don't do any locking, caller must manage their own locks. const NO_LOCK = ffi::MDB_NOLOCK; From 66af90df9e603552387ae343121d8eb0c71b3b49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= Date: Fri, 20 Dec 2024 17:27:20 +0100 Subject: [PATCH 12/12] Fix english typo --- heed/src/txn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heed/src/txn.rs b/heed/src/txn.rs index 2d99e136..829117c0 100644 --- a/heed/src/txn.rs +++ b/heed/src/txn.rs @@ -136,7 +136,7 @@ pub enum WithTls {} #[derive(Debug, PartialEq, Eq)] pub enum WithoutTls {} -/// Specifycies if Thread Local Storage (TLS) must be used when +/// Specificies if Thread Local Storage (TLS) must be used when /// opening transactions. It is often faster to open TLS-backed /// transactions but makes them `!Send`. ///