]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3652] Fix memory increase on reconfiguration
authorAndrei Pavel <andrei@isc.org>
Mon, 25 Nov 2024 09:14:26 +0000 (11:14 +0200)
committerAndrei Pavel <andrei@isc.org>
Thu, 28 Nov 2024 08:16:57 +0000 (10:16 +0200)
Caused by config history. The config history was soft-disabled.
Code had to be refactored to enable CONFIG_LIST_SIZE == 0.

src/lib/dhcpsrv/cfgmgr.cc
src/lib/dhcpsrv/cfgmgr.h
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
src/lib/dhcpsrv/testutils/alloc_engine_utils.cc

index ef156dd22c7c0b886a7ad01e274a787a37e8f91e..cdd3c307ea9de21698cf31668c26ed99b437e358 100644 (file)
@@ -19,7 +19,9 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
-const size_t CfgMgr::CONFIG_LIST_SIZE = 10;
+// Value 0 effectively disables configuration history. Higher values can lead to proportionally
+// increased memory usage which can be extreme in case of sizable configurations.
+const size_t CfgMgr::CONFIG_LIST_SIZE = 0;
 
 CfgMgr&
 CfgMgr::instance() {
@@ -69,7 +71,7 @@ CfgMgr::getD2ClientMgr() {
 
 void
 CfgMgr::ensureCurrentAllocated() {
-    if (!configuration_ || configs_.empty()) {
+    if (!configuration_) {
         configuration_.reset(new SrvConfig());
         configs_.push_back(configuration_);
     }
@@ -79,6 +81,7 @@ void
 CfgMgr::clear() {
     if (configuration_) {
         configuration_->removeStatistics();
+        configuration_.reset();
     }
     configs_.clear();
     external_configs_.clear();
@@ -92,18 +95,14 @@ CfgMgr::commit() {
 
     // First we need to remove statistics. The new configuration can have fewer
     // subnets. Also, it may change subnet-ids. So we need to remove them all
-    // and add it back.
+    // and add them back.
     configuration_->removeStatistics();
 
-    if (!configs_.back()->sequenceEquals(*configuration_)) {
+    bool promoted(false);
+    if (!configs_.empty() && !configs_.back()->sequenceEquals(*configuration_)) {
+        // Promote the staging configuration to the current configuration.
         configuration_ = configs_.back();
-        // Keep track of the maximum size of the configs history. Before adding
-        // new element, we have to remove the oldest one.
-        if (configs_.size() > CONFIG_LIST_SIZE) {
-            SrvConfigList::iterator it = configs_.begin();
-            std::advance(it, configs_.size() - CONFIG_LIST_SIZE);
-            configs_.erase(configs_.begin(), it);
-        }
+        promoted = true;
     }
 
     // Set the last commit timestamp.
@@ -114,12 +113,21 @@ CfgMgr::commit() {
     configuration_->updateStatistics();
 
     configuration_->configureLowerLevelLibraries();
+
+    if (promoted) {
+        // Keep track of the maximum size of the configs history. Remove the oldest elements.
+        if (configs_.size() > CONFIG_LIST_SIZE) {
+            SrvConfigList::const_iterator it = configs_.begin();
+            std::advance(it, configs_.size() - CONFIG_LIST_SIZE);
+            configs_.erase(configs_.begin(), it);
+        }
+    }
 }
 
 void
 CfgMgr::rollback() {
     ensureCurrentAllocated();
-    if (!configuration_->sequenceEquals(*configs_.back())) {
+    if (!configs_.empty() && !configuration_->sequenceEquals(*configs_.back())) {
         configs_.pop_back();
     }
 }
@@ -130,9 +138,9 @@ CfgMgr::revert(const size_t index) {
     if (index == 0) {
         isc_throw(isc::OutOfRange, "invalid commit index 0 when reverting"
                   " to an old configuration");
-    } else if (index > configs_.size() - 1) {
+    } else if (configs_.size() <= index) {
         isc_throw(isc::OutOfRange, "unable to revert to commit index '"
-                  << index << "', only '" << configs_.size() - 1
+                  << index << "', only '" << (configs_.size() == 0 ? 0 : configs_.size() - 1)
                   << "' previous commits available");
     }
 
@@ -143,18 +151,20 @@ CfgMgr::revert(const size_t index) {
     // configuration is destroyed by this rollback.
     rollback();
 
-    // Get the iterator to the current configuration and then advance to the
-    // desired one.
-    SrvConfigList::const_reverse_iterator it = configs_.rbegin();
-    std::advance(it, index);
+    if (!configs_.empty()) {
+        // Get the iterator to the current configuration and then advance to the
+        // desired one.
+        SrvConfigList::const_reverse_iterator it = configs_.rbegin();
+        std::advance(it, index);
 
-    // Copy the desired configuration to the new staging configuration. The
-    // staging configuration is re-created here because we rolled back earlier
-    // in this function.
-    (*it)->copy(*getStagingCfg());
+        // Copy the desired configuration to the new staging configuration. The
+        // staging configuration is re-created here because we rolled back earlier
+        // in this function.
+        (*it)->copy(*getStagingCfg());
 
-    // Make the staging configuration a current one.
-    commit();
+        // Make the staging configuration a current one.
+        commit();
+    }
 }
 
 SrvConfigPtr
@@ -166,7 +176,7 @@ CfgMgr::getCurrentCfg() {
 SrvConfigPtr
 CfgMgr::getStagingCfg() {
     ensureCurrentAllocated();
-    if (configuration_->sequenceEquals(*configs_.back())) {
+    if (configs_.empty() || configuration_->sequenceEquals(*configs_.back())) {
         uint32_t sequence = configuration_->getSequence();
         configs_.push_back(SrvConfigPtr(new SrvConfig(++sequence)));
     }
index a3aa4ccbf428c796c7be0ae66af1312d4609940a..d5492ed29be2b115cb8040739f53c5894f44fe42 100644 (file)
@@ -150,7 +150,7 @@ public:
     /// @brief Commits the staging configuration.
     ///
     /// The staging configuration becomes current configuration when this
-    /// function is called. It removes the oldest configuration held in the
+    /// function is called. It removes the oldest configurations held in the
     /// history so as the size of the list of configuration does not exceed
     /// the @c CONFIG_LIST_SIZE.
     ///
index 54c42f6e9ef4c4abedb5313c1d6de508d43342ea..115d58550c9c617e960078aafeada04efd4785ae 100644 (file)
@@ -412,7 +412,7 @@ SrvConfig::updateStatistics() {
     // a lease manager, such as D2. @todo We should probably examine why
     // "SrvConfig" is being used by D2.
     if (LeaseMgrFactory::haveInstance()) {
-        // Updates  statistics for v4 and v6 subnets
+        // Updates statistics for v4 and v6 subnets.
         getCfgSubnets4()->updateStatistics();
         getCfgSubnets6()->updateStatistics();
     }
index 44699638f3a19635db69af336da6f58e3574a622..e6caa3ae5591282cfa1c742bddf1c8a537817e9b 100644 (file)
@@ -543,6 +543,10 @@ TEST_F(CfgMgrTest, revert) {
     // Value of 0 also doesn't make sense.
     ASSERT_THROW(cfg_mgr.revert(0), isc::OutOfRange);
 
+    // Return early because the checks that follow simply are not supported by
+    // CONFIG_LIST_SIZE == 0 at the time of writing.
+    return;
+
     // We should be able to revert to configuration with debuglevel = 10.
     ASSERT_NO_THROW(cfg_mgr.revert(4));
     // And this configuration should be now the current one and the debuglevel
index 21ef2ba6b77e7f21a16dde0691bcf1cf81f3a97e..910e98dc0a6fda232ce47c167a846c2717e01286 100644 (file)
@@ -17,6 +17,7 @@
 #include <hooks/hooks_manager.h>
 #include <hooks/callout_handle.h>
 #include <stats/stats_mgr.h>
+#include <testutils/gtest_utils.h>
 
 #include <dhcpsrv/testutils/test_utils.h>
 #include <dhcpsrv/testutils/alloc_engine_utils.h>
@@ -642,7 +643,7 @@ AllocEngine4Test::initSubnet(const asiolink::IOAddress& pool_start,
     pool_ = Pool4Ptr(new Pool4(pool_start, pool_end));
     subnet_->addPool(pool_);
 
-    cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_);
+    EXPECT_NO_THROW_LOG(cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_));
 }
 
 AllocEngine4Test::AllocEngine4Test() {