Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions storage/blockchain/src/free.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub fn open(config: Config) -> Result<ConcreteEnv, InitError> {
RuntimeError::KeyExists
| RuntimeError::KeyNotFound
| RuntimeError::ResizeNeeded
| RuntimeError::ResizedByAnotherProcess
| RuntimeError::TableNotFound => unreachable!(),
}
}
Expand Down
43 changes: 28 additions & 15 deletions storage/database/src/backend/heed/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,29 +206,42 @@ impl Env for ConcreteEnv {
Ok(self.env.read().unwrap().force_sync()?)
}

fn resize_map(&self, resize_algorithm: Option<ResizeAlgorithm>) -> NonZeroUsize {
let resize_algorithm = resize_algorithm.unwrap_or_else(|| self.config().resize_algorithm);

let current_size_bytes = self.current_map_size();
let new_size_bytes = resize_algorithm.resize(current_size_bytes);

fn resize_map(
&self,
resize_algorithm: Option<ResizeAlgorithm>,
resized_by_another_process: bool,
) -> NonZeroUsize {
// SAFETY:
// Resizing requires that we have
// exclusive access to the database environment.
// Our `heed::Env` is wrapped within a `RwLock`,
// and we have a WriteGuard to it, so we're safe.
//
// <http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5>
unsafe {
// INVARIANT: `resize()` returns a valid `usize` to resize to.
self.env
.write()
.unwrap()
.resize(new_size_bytes.get())
.unwrap();
}
let resize = |new_size_bytes| unsafe {
self.env.write().unwrap().resize(new_size_bytes).unwrap();
};

if resized_by_another_process {
// > If the mapsize is increased by another process,
// > and data has grown beyond the range of the current mapsize,
// > mdb_txn_begin() will return MDB_MAP_RESIZED.
// >
// > This function may be called with a size of zero to adopt the new size.
//
// <http://www.lmdb.tech/doc/group__mdb.html#gad7ea55da06b77513609efebd44b26920>
resize(0);
NonZeroUsize::new(self.current_map_size()).unwrap()
} else {
let resize_algorithm =
resize_algorithm.unwrap_or_else(|| self.config().resize_algorithm);

new_size_bytes
let current_size_bytes = self.current_map_size();

let x = resize_algorithm.resize(current_size_bytes);
resize(x.get());
x
}
}

#[inline]
Expand Down
13 changes: 1 addition & 12 deletions storage/database/src/backend/heed/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ impl From<heed::Error> for crate::RuntimeError {
E2::KeyExist => Self::KeyExists,
E2::NotFound => Self::KeyNotFound,
E2::MapFull => Self::ResizeNeeded,
E2::MapResized => Self::ResizedByAnotherProcess,

// Corruption errors, these have special panic messages.
//
Expand Down Expand Up @@ -104,18 +105,6 @@ impl From<heed::Error> for crate::RuntimeError {
// of being errors we can't control, these are errors
// that only happen if we write incorrect code.

// "Database contents grew beyond environment mapsize."
// We should be resizing the map when needed, this error
// occurring indicates we did _not_ do that, which is a bug
// and we should panic.
//
// FIXME: This can also mean _another_ process wrote to our
// LMDB file and increased the size. I don't think we need to accommodate for this.
// <http://www.lmdb.tech/doc/group__mdb.html#gaa2506ec8dab3d969b0e609cd82e619e5>
// Although `monerod` reacts to that instead of `MDB_MAP_FULL`
// which is what `mdb_put()` returns so... idk?
// <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L526>
| E2::MapResized
// We should be setting `heed::EnvOpenOptions::max_readers()`
// with our reader thread value in [`crate::config::Config`],
// thus this error should never occur.
Expand Down
4 changes: 2 additions & 2 deletions storage/database/src/backend/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ fn resize() {
// Resize by the OS page size.
let page_size = *crate::resize::PAGE_SIZE;
let old_size = env.current_map_size();
env.resize_map(Some(ResizeAlgorithm::FixedBytes(page_size)));
env.resize_map(Some(ResizeAlgorithm::FixedBytes(page_size)), false);

// Assert it resized exactly by the OS page size.
let new_size = env.current_map_size();
Expand All @@ -143,7 +143,7 @@ fn non_manual_resize_1() {
unreachable!();
}
let (env, _tempdir) = tmp_concrete_env();
env.resize_map(None);
env.resize_map(None, false);
}

#[test]
Expand Down
14 changes: 10 additions & 4 deletions storage/database/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,22 @@ pub trait Env: Sized {
///
/// By default, this function will use the `ResizeAlgorithm` in [`Env::config`].
///
/// If `resize_algorithm` is `Some`, that will be used instead.
///
/// This function returns the _new_ memory map size in bytes.
/// - If `resized_by_another_process` is `true`, `0` will
/// be passed to the internal resize function, see:
// <http://www.lmdb.tech/doc/group__mdb.html#gad7ea55da06b77513609efebd44b26920>
/// - If `resize_algorithm` is `Some`, that will be used instead
/// - This function returns the _new_ memory map size in bytes
///
/// # Invariant
/// This function _must_ be re-implemented if [`Env::MANUAL_RESIZE`] is `true`.
///
/// Otherwise, this function will panic with `unreachable!()`.
#[expect(unused_variables)]
fn resize_map(&self, resize_algorithm: Option<ResizeAlgorithm>) -> NonZeroUsize {
fn resize_map(
&self,
resize_algorithm: Option<ResizeAlgorithm>,
resized_by_another_process: bool,
) -> NonZeroUsize {
unreachable!()
}

Expand Down
10 changes: 10 additions & 0 deletions storage/database/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ pub enum RuntimeError {
#[error("database memory map must be resized")]
ResizeNeeded,

/// Another process resized the database.
///
/// This error will continue to surface if the `heed`
/// backend is used and another process resized the database.
///
/// To fix this, [`Env::resize_map`](crate::Env::resize_map)
/// must be called with `resized_by_another_process: true`.
#[error("database memory map resized by another process")]
ResizedByAnotherProcess,

/// The given table did not exist in the database.
#[error("database table did not exist")]
TableNotFound,
Expand Down
8 changes: 7 additions & 1 deletion storage/service/src/service/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ fn database_writer<Req, Res>(
// this won't have to be an enum.
let response = inner_handler(env, &request);

if ConcreteEnv::MANUAL_RESIZE
&& matches!(response, Err(RuntimeError::ResizedByAnotherProcess))
{
panic!("Database was resized by another process - no other process should write to `cuprated`'s database.");
}

// If the database needs to resize, do so.
if ConcreteEnv::MANUAL_RESIZE && matches!(response, Err(RuntimeError::ResizeNeeded)) {
// If this is the last iteration of the outer `for` loop and we
Expand All @@ -151,7 +157,7 @@ fn database_writer<Req, Res>(
// add that much instead of the default 1GB.
// <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L665-L695>
let old = env.current_map_size();
let new = env.resize_map(None).get();
let new = env.resize_map(None, false).get();

const fn bytes_to_megabytes(bytes: usize) -> usize {
bytes / 1_000_000
Expand Down
7 changes: 4 additions & 3 deletions storage/txpool/src/free.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ pub fn open(config: &Config) -> Result<ConcreteEnv, InitError> {
RuntimeError::KeyNotFound => InitError::InvalidVersion,

// These errors shouldn't be happening here.
RuntimeError::KeyExists | RuntimeError::ResizeNeeded | RuntimeError::TableNotFound => {
unreachable!()
}
RuntimeError::KeyExists
| RuntimeError::ResizeNeeded
| RuntimeError::ResizedByAnotherProcess
| RuntimeError::TableNotFound => unreachable!(),
}
}

Expand Down
Loading