mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-26 23:24:09 +02:00
Merge pull request #9267 from Icinga/bugfix/parallel-api-package-calls-do-not-finish-while-reload
Worker process doesn't let parallel API package stage updates to complete when terminated
This commit is contained in:
commit
b29b95e882
@ -30,12 +30,20 @@ public:
|
|||||||
inline
|
inline
|
||||||
~Defer()
|
~Defer()
|
||||||
{
|
{
|
||||||
|
if (m_Func) {
|
||||||
try {
|
try {
|
||||||
m_Func();
|
m_Func();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor
|
// https://stackoverflow.com/questions/130117/throwing-exceptions-out-of-a-destructor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
void Cancel()
|
||||||
|
{
|
||||||
|
m_Func = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::function<void()> m_Func;
|
std::function<void()> m_Func;
|
||||||
|
@ -167,7 +167,8 @@ void ConfigPackageUtility::ActivateStage(const String& packageName, const String
|
|||||||
WritePackageConfig(packageName);
|
WritePackageConfig(packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigPackageUtility::TryActivateStageCallback(const ProcessResult& pr, const String& packageName, const String& stageName, bool activate, bool reload)
|
void ConfigPackageUtility::TryActivateStageCallback(const ProcessResult& pr, const String& packageName, const String& stageName,
|
||||||
|
bool activate, bool reload, const Shared<Defer>::Ptr& resetPackageUpdates)
|
||||||
{
|
{
|
||||||
String logFile = GetPackageDir() + "/" + packageName + "/" + stageName + "/startup.log";
|
String logFile = GetPackageDir() + "/" + packageName + "/" + stageName + "/startup.log";
|
||||||
std::ofstream fpLog(logFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
|
std::ofstream fpLog(logFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
|
||||||
@ -188,9 +189,18 @@ void ConfigPackageUtility::TryActivateStageCallback(const ProcessResult& pr, con
|
|||||||
ActivateStage(packageName, stageName);
|
ActivateStage(packageName, stageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reload)
|
if (reload) {
|
||||||
|
/*
|
||||||
|
* Cancel the deferred callback before going out of scope so that the config stages handler
|
||||||
|
* flag isn't resetting earlier and allowing other clients to submit further requests while
|
||||||
|
* Icinga2 is reloading. Otherwise, the ongoing request will be cancelled halfway before the
|
||||||
|
* operation is completed once the new worker becomes ready.
|
||||||
|
*/
|
||||||
|
resetPackageUpdates->Cancel();
|
||||||
|
|
||||||
Application::RequestRestart();
|
Application::RequestRestart();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Log(LogCritical, "ConfigPackageUtility")
|
Log(LogCritical, "ConfigPackageUtility")
|
||||||
<< "Config validation failed for package '"
|
<< "Config validation failed for package '"
|
||||||
@ -198,7 +208,8 @@ void ConfigPackageUtility::TryActivateStageCallback(const ProcessResult& pr, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigPackageUtility::AsyncTryActivateStage(const String& packageName, const String& stageName, bool activate, bool reload)
|
void ConfigPackageUtility::AsyncTryActivateStage(const String& packageName, const String& stageName, bool activate, bool reload,
|
||||||
|
const Shared<Defer>::Ptr& resetPackageUpdates)
|
||||||
{
|
{
|
||||||
VERIFY(Application::GetArgC() >= 1);
|
VERIFY(Application::GetArgC() >= 1);
|
||||||
|
|
||||||
@ -224,7 +235,9 @@ void ConfigPackageUtility::AsyncTryActivateStage(const String& packageName, cons
|
|||||||
|
|
||||||
Process::Ptr process = new Process(Process::PrepareCommand(args));
|
Process::Ptr process = new Process(Process::PrepareCommand(args));
|
||||||
process->SetTimeout(Application::GetReloadTimeout());
|
process->SetTimeout(Application::GetReloadTimeout());
|
||||||
process->Run([packageName, stageName, activate, reload](const ProcessResult& pr) { TryActivateStageCallback(pr, packageName, stageName, activate, reload); });
|
process->Run([packageName, stageName, activate, reload, resetPackageUpdates](const ProcessResult& pr) {
|
||||||
|
TryActivateStageCallback(pr, packageName, stageName, activate, reload, resetPackageUpdates);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigPackageUtility::DeleteStage(const String& packageName, const String& stageName)
|
void ConfigPackageUtility::DeleteStage(const String& packageName, const String& stageName)
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
#include "base/dictionary.hpp"
|
#include "base/dictionary.hpp"
|
||||||
#include "base/process.hpp"
|
#include "base/process.hpp"
|
||||||
#include "base/string.hpp"
|
#include "base/string.hpp"
|
||||||
|
#include "base/defer.hpp"
|
||||||
|
#include "base/shared.hpp"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace icinga
|
namespace icinga
|
||||||
@ -37,7 +39,8 @@ public:
|
|||||||
static void SetActiveStage(const String& packageName, const String& stageName);
|
static void SetActiveStage(const String& packageName, const String& stageName);
|
||||||
static void SetActiveStageToFile(const String& packageName, const String& stageName);
|
static void SetActiveStageToFile(const String& packageName, const String& stageName);
|
||||||
static void ActivateStage(const String& packageName, const String& stageName);
|
static void ActivateStage(const String& packageName, const String& stageName);
|
||||||
static void AsyncTryActivateStage(const String& packageName, const String& stageName, bool activate, bool reload);
|
static void AsyncTryActivateStage(const String& packageName, const String& stageName, bool activate, bool reload,
|
||||||
|
const Shared<Defer>::Ptr& resetPackageUpdates);
|
||||||
|
|
||||||
static std::vector<std::pair<String, bool> > GetFiles(const String& packageName, const String& stageName);
|
static std::vector<std::pair<String, bool> > GetFiles(const String& packageName, const String& stageName);
|
||||||
|
|
||||||
@ -59,7 +62,8 @@ private:
|
|||||||
static void WritePackageConfig(const String& packageName);
|
static void WritePackageConfig(const String& packageName);
|
||||||
static void WriteStageConfig(const String& packageName, const String& stageName);
|
static void WriteStageConfig(const String& packageName, const String& stageName);
|
||||||
|
|
||||||
static void TryActivateStageCallback(const ProcessResult& pr, const String& packageName, const String& stageName, bool activate, bool reload);
|
static void TryActivateStageCallback(const ProcessResult& pr, const String& packageName, const String& stageName, bool activate,
|
||||||
|
bool reload, const Shared<Defer>::Ptr& resetPackageUpdates);
|
||||||
|
|
||||||
static bool ValidateFreshName(const String& name);
|
static bool ValidateFreshName(const String& name);
|
||||||
};
|
};
|
||||||
|
@ -5,12 +5,15 @@
|
|||||||
#include "remote/httputility.hpp"
|
#include "remote/httputility.hpp"
|
||||||
#include "remote/filterutility.hpp"
|
#include "remote/filterutility.hpp"
|
||||||
#include "base/application.hpp"
|
#include "base/application.hpp"
|
||||||
|
#include "base/defer.hpp"
|
||||||
#include "base/exception.hpp"
|
#include "base/exception.hpp"
|
||||||
|
|
||||||
using namespace icinga;
|
using namespace icinga;
|
||||||
|
|
||||||
REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler);
|
REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler);
|
||||||
|
|
||||||
|
std::atomic<bool> ConfigStagesHandler::m_RunningPackageUpdates (false);
|
||||||
|
|
||||||
bool ConfigStagesHandler::HandleRequest(
|
bool ConfigStagesHandler::HandleRequest(
|
||||||
AsioTlsStream& stream,
|
AsioTlsStream& stream,
|
||||||
const ApiUser::Ptr& user,
|
const ApiUser::Ptr& user,
|
||||||
@ -128,12 +131,19 @@ void ConfigStagesHandler::HandlePost(
|
|||||||
if (reload && !activate)
|
if (reload && !activate)
|
||||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'reload' must be false when 'activate' is false."));
|
BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'reload' must be false when 'activate' is false."));
|
||||||
|
|
||||||
|
if (m_RunningPackageUpdates.exchange(true)) {
|
||||||
|
return HttpUtility::SendJsonError(response, params, 423,
|
||||||
|
"Conflicting request, there is already an ongoing package update in progress. Please try it again later.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resetPackageUpdates (Shared<Defer>::Make([]() { ConfigStagesHandler::m_RunningPackageUpdates.store(false); }));
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());
|
std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());
|
||||||
|
|
||||||
stageName = ConfigPackageUtility::CreateStage(packageName, files);
|
stageName = ConfigPackageUtility::CreateStage(packageName, files);
|
||||||
|
|
||||||
/* validate the config. on success, activate stage and reload */
|
/* validate the config. on success, activate stage and reload */
|
||||||
ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, activate, reload);
|
ConfigPackageUtility::AsyncTryActivateStage(packageName, stageName, activate, reload, resetPackageUpdates);
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
return HttpUtility::SendJsonError(response, params, 500,
|
return HttpUtility::SendJsonError(response, params, 500,
|
||||||
"Stage creation failed.",
|
"Stage creation failed.",
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#define CONFIGSTAGESHANDLER_H
|
#define CONFIGSTAGESHANDLER_H
|
||||||
|
|
||||||
#include "remote/httphandler.hpp"
|
#include "remote/httphandler.hpp"
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace icinga
|
namespace icinga
|
||||||
{
|
{
|
||||||
@ -47,6 +48,7 @@ private:
|
|||||||
const Dictionary::Ptr& params
|
const Dictionary::Ptr& params
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static std::atomic<bool> m_RunningPackageUpdates;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user