feat: enforce online_delete requirement for RWDB to prevent OOM

RWDB (in-memory backend) now requires online_delete configuration to prevent
unbounded memory growth. Exception: standalone mode (used by tests) bypasses
this requirement since tests run for short durations.

- Add enforcement check in SHAMapStoreImp constructor
- Tests use Config::setupControl() to simulate non-standalone environment
- Comprehensive test coverage for all enforcement scenarios
This commit is contained in:
Nicholas Dudfield
2025-08-19 16:03:11 +07:00
parent be586db462
commit 76b36fb308
3 changed files with 32 additions and 33 deletions

View File

@@ -118,16 +118,10 @@ SHAMapStoreImp::SHAMapStoreImp(
get_if_exists(section, "online_delete", deleteInterval_); get_if_exists(section, "online_delete", deleteInterval_);
auto actual_standalone = config.standalone();
auto effective_standalone =
get(section,
"_online_delete_standalone_override",
actual_standalone ? "true" : "false") != "false";
// RWDB (in-memory backend) requires online_delete to prevent OOM // RWDB (in-memory backend) requires online_delete to prevent OOM
// Exception: standalone mode (used by tests) doesn't need online_delete // Exception: standalone mode (used by tests) doesn't need online_delete
// since tests run for short durations // since tests run for short durations
if (config.mem_backend() && !deleteInterval_ && !effective_standalone) if (config.mem_backend() && !deleteInterval_ && !config.standalone())
{ {
Throw<std::runtime_error>( Throw<std::runtime_error>(
"RWDB (in-memory backend) requires online_delete to be configured. " "RWDB (in-memory backend) requires online_delete to be configured. "

View File

@@ -522,40 +522,39 @@ public:
} }
static std::unique_ptr<Config> static std::unique_ptr<Config>
rwdbNoDelete() rwdbNoDeleteDefaultConfig()
{ {
// RWDB without online_delete, but keep standalone override = true // RWDB without online_delete, in standalone mode (default)
// (default)
auto cfg = test::jtx::envconfig(); auto cfg = test::jtx::envconfig();
cfg->section(ConfigSection::nodeDatabase()).set("type", "rwdb"); cfg->section(ConfigSection::nodeDatabase()).set("type", "rwdb");
cfg->section(ConfigSection::nodeDatabase()).set("path", "main"); cfg->section(ConfigSection::nodeDatabase()).set("path", "main");
// Keep the default standalone override value of "true" from // Default is standalone = true from envconfig.cpp
// envconfig.cpp
return cfg; return cfg;
} }
static std::unique_ptr<Config> static std::unique_ptr<Config>
rwdbNoDeleteEnforced() rwdbNoDeleteNotStandalone()
{ {
// RWDB without online_delete and force enforcement // RWDB without online_delete and NOT in standalone mode
// This should trigger the enforcement
auto cfg = test::jtx::envconfig(); auto cfg = test::jtx::envconfig();
cfg->section(ConfigSection::nodeDatabase()).set("type", "rwdb"); cfg->section(ConfigSection::nodeDatabase()).set("type", "rwdb");
cfg->section(ConfigSection::nodeDatabase()).set("path", "main"); cfg->section(ConfigSection::nodeDatabase()).set("path", "main");
cfg->section(ConfigSection::nodeDatabase()) cfg->setupControl(
.set("_online_delete_standalone_override", "false"); true, true, false); // bQuiet, bSilent, bStandalone=false
return cfg; return cfg;
} }
static std::unique_ptr<Config> static std::unique_ptr<Config>
rwdbWithDeleteEnforced() rwdbWithDeleteNotStandalone()
{ {
// RWDB with online_delete and force enforcement // RWDB with online_delete and NOT in standalone mode
auto cfg = test::jtx::envconfig(); auto cfg = test::jtx::envconfig();
cfg->section(ConfigSection::nodeDatabase()).set("type", "rwdb"); cfg->section(ConfigSection::nodeDatabase()).set("type", "rwdb");
cfg->section(ConfigSection::nodeDatabase()).set("path", "main"); cfg->section(ConfigSection::nodeDatabase()).set("path", "main");
cfg->section(ConfigSection::nodeDatabase()).set("online_delete", "256"); cfg->section(ConfigSection::nodeDatabase()).set("online_delete", "256");
cfg->section(ConfigSection::nodeDatabase()) cfg->setupControl(
.set("_online_delete_standalone_override", "false"); true, true, false); // bQuiet, bSilent, bStandalone=false
return cfg; return cfg;
} }
@@ -564,18 +563,18 @@ public:
{ {
testcase("RWDB online_delete enforcement"); testcase("RWDB online_delete enforcement");
// Test 1: RWDB without online_delete but with standalone override = // Test 1: RWDB without online_delete in standalone mode (should
// true (should succeed) // succeed)
{ {
test::jtx::Env env{*this, rwdbNoDelete()}; jtx::Env env{*this, rwdbNoDeleteDefaultConfig()};
pass(); pass();
} }
// Test 2: RWDB without online_delete and standalone override = false // Test 2: RWDB without online_delete NOT in standalone mode (should
// (should throw) // throw)
try try
{ {
test::jtx::Env env{*this, rwdbNoDeleteEnforced()}; jtx::Env env{*this, rwdbNoDeleteNotStandalone()};
fail("Expected exception for RWDB without online_delete"); fail("Expected exception for RWDB without online_delete");
} }
catch (std::runtime_error const& e) catch (std::runtime_error const& e)
@@ -587,10 +586,20 @@ public:
pass(); pass();
} }
// Test 3: RWDB with online_delete and standalone override = false // Test 3: RWDB with online_delete NOT in standalone mode (should
// (should succeed) // succeed) But should throw with another error message unrelated
try
{ {
test::jtx::Env env{*this, rwdbWithDeleteEnforced()}; jtx::Env env{*this, rwdbWithDeleteNotStandalone()};
// Currently throwing due to missing database_path
fail("Test relies upon throwing exception");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find(
"RWDB (in-memory backend) requires online_delete") ==
std::string::npos);
pass(); pass();
} }
} }

View File

@@ -51,10 +51,6 @@ setupConfigForUnitTests(Config& cfg)
cfg.overwrite(ConfigSection::nodeDatabase(), "type", "rwdb"); cfg.overwrite(ConfigSection::nodeDatabase(), "type", "rwdb");
cfg.overwrite(ConfigSection::nodeDatabase(), "path", "main"); cfg.overwrite(ConfigSection::nodeDatabase(), "path", "main");
cfg.overwrite(
ConfigSection::nodeDatabase(),
"_online_delete_standalone_override",
"true");
cfg.overwrite(SECTION_RELATIONAL_DB, "backend", "rwdb"); cfg.overwrite(SECTION_RELATIONAL_DB, "backend", "rwdb");
cfg.deprecatedClearSection(ConfigSection::importNodeDatabase()); cfg.deprecatedClearSection(ConfigSection::importNodeDatabase());
cfg.legacy("database_path", ""); cfg.legacy("database_path", "");