From 68480cee67f3eb928dadca5ca776f38119e231e7 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Fri, 21 Jul 2023 11:46:49 +0100 Subject: [PATCH] Add incremental backoff to the filter/permchannels/xline_db modules. Closes #1671. --- docs/conf/modules.conf.example | 13 +++++++++++- src/modules/m_filter.cpp | 39 ++++++++++++++++++++++++++++------ src/modules/m_permchannels.cpp | 29 ++++++++++++++++++++++--- src/modules/m_xline_db.cpp | 21 +++++++++++++++++- 4 files changed, 91 insertions(+), 11 deletions(-) diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 3011d5289..f495b6928 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -1909,12 +1909,20 @@ # 'saveperiod' determines how often to check if the database needs to be # saved to disk. Defaults to every five seconds. # +# 'backoff' is the value to multiply the saveperiod by every time a save +# fails. When the save succeeds the period will be reset. +# +# 'maxbackoff' is the maximum write period that should be allowed even +# if incremental backoff is enabled. +# # 'operonly' determines whether a server operator or services server is # needed to enable the permchannels mode. You should generally keep this # set to yes unless you know what you are doing. # # # @@ -2658,7 +2666,10 @@ # Specify the filename for the xline database and how often to check whether # the database needs to be saved here. -# +# #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # ____ _ _____ _ _ ____ _ _ _ # diff --git a/src/modules/m_filter.cpp b/src/modules/m_filter.cpp index 312bbc91a..53acb329c 100644 --- a/src/modules/m_filter.cpp +++ b/src/modules/m_filter.cpp @@ -206,6 +206,9 @@ private: bool dirty = false; std::string filterconf; Regex::Engine* factory; + unsigned long saveperiod; + unsigned long maxbackoff; + unsigned char backoff; void FreeFilters(); public: @@ -238,6 +241,7 @@ public: ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) override; void OnUnloadModule(Module* mod) override; bool Tick() override; + bool WriteDatabase(); bool AppliesToMe(User* user, const FilterResult& filter, int flags); void ReadFilters(); static bool StringToFilterAction(const std::string& str, FilterAction& fa); @@ -645,7 +649,10 @@ void ModuleFilter::ReadConfig(ConfigStatus& status) filterconf = tag->getString("filename"); if (!filterconf.empty()) filterconf = ServerInstance->Config->Paths.PrependConfig(filterconf); - SetInterval(tag->getDuration("saveperiod", 5)); + saveperiod = tag->getDuration("saveperiod", 5); + backoff = tag->getNum("backoff", 0); + maxbackoff = tag->getDuration("maxbackoff", saveperiod * 120, saveperiod); + SetInterval(saveperiod); factory = RegexEngine ? (RegexEngine.operator->()) : nullptr; @@ -934,9 +941,29 @@ void ModuleFilter::OnUnloadModule(Module* mod) bool ModuleFilter::Tick() { - if (!dirty) // No need to write. - return true; + if (dirty) + { + if (WriteDatabase()) + { + // If we were previously unable to write but now can then reset the time interval. + if (GetInterval() != saveperiod) + SetInterval(saveperiod, false); + dirty = false; + } + else + { + // Back off a bit to avoid spamming opers. + if (backoff > 1) + SetInterval(std::min(GetInterval() * backoff, maxbackoff), false); + ServerInstance->Logs.Debug(MODNAME, "Trying again in {} seconds", GetInterval()); + } + } + return true; +}; + +bool ModuleFilter::WriteDatabase() +{ if (filterconf.empty()) // Nothing to write to. { dirty = false; @@ -949,7 +976,7 @@ bool ModuleFilter::Tick() { ServerInstance->SNO.WriteToSnoMask('f', "Unable to save filters to \"{}\": {} ({})", newfilterconf, strerror(errno), errno); - return true; + return false; } stream @@ -977,7 +1004,7 @@ bool ModuleFilter::Tick() { ServerInstance->SNO.WriteToSnoMask('f', "Unable to save filters to \"{}\": {} ({})", newfilterconf, strerror(errno), errno); - return true; + return false; } stream.close(); @@ -990,7 +1017,7 @@ bool ModuleFilter::Tick() { ServerInstance->SNO.WriteToSnoMask('f', "Unable to replace old filter config \"{}\" with \"{}\": {} ({})", filterconf, newfilterconf, strerror(errno), errno); - return true; + return false; } dirty = false; diff --git a/src/modules/m_permchannels.cpp b/src/modules/m_permchannels.cpp index 1d0c713b7..37e11fa55 100644 --- a/src/modules/m_permchannels.cpp +++ b/src/modules/m_permchannels.cpp @@ -176,10 +176,15 @@ class ModulePermanentChannels final , public Timer { +private: PermChannel p; bool dirty = false; bool loaded = false; bool save_listmodes; + unsigned long saveperiod; + unsigned long maxbackoff; + unsigned char backoff; + public: ModulePermanentChannels() @@ -195,7 +200,10 @@ public: permchannelsconf = tag->getString("filename"); save_listmodes = tag->getBool("listmodes", true); p.SetOperOnly(tag->getBool("operonly", true)); - SetInterval(tag->getDuration("saveperiod", 5)); + saveperiod = tag->getDuration("saveperiod", 5); + backoff = tag->getNum("backoff", 0); + maxbackoff = tag->getDuration("maxbackoff", saveperiod * 120, saveperiod); + SetInterval(saveperiod); if (!permchannelsconf.empty()) permchannelsconf = ServerInstance->Config->Paths.PrependConfig(permchannelsconf); @@ -289,8 +297,23 @@ public: bool Tick() override { if (dirty) - WriteDatabase(p, save_listmodes); - dirty = false; + { + if (WriteDatabase(p, save_listmodes)) + { + // If we were previously unable to write but now can then reset the time interval. + if (GetInterval() != saveperiod) + SetInterval(saveperiod, false); + + dirty = false; + } + else + { + // Back off a bit to avoid spamming opers. + if (backoff > 1) + SetInterval(std::min(GetInterval() * backoff, maxbackoff), false); + ServerInstance->Logs.Debug(MODNAME, "Trying again in {} seconds", GetInterval()); + } + } return true; } diff --git a/src/modules/m_xline_db.cpp b/src/modules/m_xline_db.cpp index 7a3d6e683..aa79e1e10 100644 --- a/src/modules/m_xline_db.cpp +++ b/src/modules/m_xline_db.cpp @@ -39,6 +39,9 @@ class ModuleXLineDB final private: bool dirty; std::string xlinedbpath; + unsigned long saveperiod; + unsigned long maxbackoff; + unsigned char backoff; public: ModuleXLineDB() @@ -57,7 +60,10 @@ public: */ const auto& Conf = ServerInstance->Config->ConfValue("xlinedb"); xlinedbpath = ServerInstance->Config->Paths.PrependData(Conf->getString("filename", "xline.db", 1)); - SetInterval(Conf->getDuration("saveperiod", 5)); + saveperiod = Conf->getDuration("saveperiod", 5); + backoff = Conf->getNum("backoff", 0); + maxbackoff = Conf->getDuration("maxbackoff", saveperiod * 120, saveperiod); + SetInterval(saveperiod); // Read xlines before attaching to events ReadDatabase(); @@ -91,7 +97,20 @@ public: if (dirty) { if (WriteDatabase()) + { + // If we were previously unable to write but now can then reset the time interval. + if (GetInterval() != saveperiod) + SetInterval(saveperiod, false); + dirty = false; + } + else + { + // Back off a bit to avoid spamming opers. + if (backoff > 1) + SetInterval(std::min(GetInterval() * backoff, maxbackoff), false); + ServerInstance->Logs.Debug(MODNAME, "Trying again in {} seconds", GetInterval()); + } } return true; }