1
0
Fork 0

Bug fix/applicationserver stop (#9414)

This commit is contained in:
Jan 2019-07-08 20:30:05 +02:00 committed by GitHub
parent 66d8c01ad6
commit 1d15b50d22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 277 additions and 199 deletions

View File

@ -1346,7 +1346,7 @@ AgencyCommResult AgencyComm::sendWithFailover(arangodb::rest::RequestType method
auto waitSomeTime = [&waitInterval, &result]() -> bool { auto waitSomeTime = [&waitInterval, &result]() -> bool {
// Returning true means timeout because of shutdown: // Returning true means timeout because of shutdown:
if (!application_features::ApplicationServer::isRetryOK()) { if (application_features::ApplicationServer::isStopping()) {
LOG_TOPIC("53e58", INFO, Logger::AGENCYCOMM) LOG_TOPIC("53e58", INFO, Logger::AGENCYCOMM)
<< "Unsuccessful AgencyComm: Timeout because of shutdown " << "Unsuccessful AgencyComm: Timeout because of shutdown "
<< "errorCode: " << result.errorCode() << "errorCode: " << result.errorCode()
@ -1366,7 +1366,7 @@ AgencyCommResult AgencyComm::sendWithFailover(arangodb::rest::RequestType method
} }
// Check again for shutdown, since some time has passed: // Check again for shutdown, since some time has passed:
if (!application_features::ApplicationServer::isRetryOK()) { if (application_features::ApplicationServer::isStopping()) {
LOG_TOPIC("afe45", INFO, Logger::AGENCYCOMM) LOG_TOPIC("afe45", INFO, Logger::AGENCYCOMM)
<< "Unsuccessful AgencyComm: Timeout because of shutdown " << "Unsuccessful AgencyComm: Timeout because of shutdown "
<< "errorCode: " << result.errorCode() << "errorCode: " << result.errorCode()
@ -1411,30 +1411,12 @@ AgencyCommResult AgencyComm::sendWithFailover(arangodb::rest::RequestType method
// Some reporting: // Some reporting:
if (tries > 20) { if (tries > 20) {
auto serverState = application_features::ApplicationServer::server->state(); std::string serverState = application_features::ApplicationServer::server->stringifyState();
std::string serverStateStr;
switch (serverState) {
case arangodb::application_features::ServerState::UNINITIALIZED:
case arangodb::application_features::ServerState::IN_COLLECT_OPTIONS:
case arangodb::application_features::ServerState::IN_VALIDATE_OPTIONS:
case arangodb::application_features::ServerState::IN_PREPARE:
case arangodb::application_features::ServerState::IN_START:
serverStateStr = "in startup";
break;
case arangodb::application_features::ServerState::IN_WAIT:
serverStateStr = "running";
break;
case arangodb::application_features::ServerState::IN_STOP:
case arangodb::application_features::ServerState::IN_UNPREPARE:
case arangodb::application_features::ServerState::STOPPED:
case arangodb::application_features::ServerState::ABORT:
serverStateStr = "in shutdown";
}
LOG_TOPIC("2f181", INFO, Logger::AGENCYCOMM) LOG_TOPIC("2f181", INFO, Logger::AGENCYCOMM)
<< "Flaky agency communication to " << endpoint << "Flaky agency communication to " << endpoint
<< ". Unsuccessful consecutive tries: " << tries << " (" << elapsed << ". Unsuccessful consecutive tries: " << tries << " (" << elapsed
<< "s). Network checks advised." << "s). Network checks advised."
<< " Server " << serverStateStr << "."; << " Server " << serverState << ".";
} }
if (1 < tries) { if (1 < tries) {

View File

@ -56,7 +56,7 @@ enum ActionState {
* @brief Action description for mainenance actions * @brief Action description for mainenance actions
* *
* This structure holds once initialized constant parameters of a maintenance * This structure holds once initialized constant parameters of a maintenance
* action. Members are declared const, thus thread safety guards are ommited. * action. Members are declared const, thus thread safety guards are omitted.
*/ */
struct ActionDescription { struct ActionDescription {
public: public:

View File

@ -130,7 +130,7 @@ bool AgencyCallback::executeByCallbackOrTimeout(double maxTimeout) {
// One needs to acquire the mutex of the condition variable // One needs to acquire the mutex of the condition variable
// before entering this function! // before entering this function!
if (!_cv.wait(static_cast<uint64_t>(maxTimeout * 1000000.0)) && if (!_cv.wait(static_cast<uint64_t>(maxTimeout * 1000000.0)) &&
application_features::ApplicationServer::isRetryOK()) { !application_features::ApplicationServer::isStopping()) {
LOG_TOPIC("1514e", DEBUG, Logger::CLUSTER) LOG_TOPIC("1514e", DEBUG, Logger::CLUSTER)
<< "Waiting done and nothing happended. Refetching to be sure"; << "Waiting done and nothing happended. Refetching to be sure";
// mop: watches have not triggered during our sleep...recheck to be sure // mop: watches have not triggered during our sleep...recheck to be sure

View File

@ -661,7 +661,7 @@ std::unique_ptr<ClusterCommResult> ClusterComm::syncRequest(
communicator()->addRequest(std::move(newRequest)); communicator()->addRequest(std::move(newRequest));
while (!sharedData->wasSignaled while (!sharedData->wasSignaled
&& application_features::ApplicationServer::isRetryOK()) { && !application_features::ApplicationServer::isStopping()) {
sharedData->cv.wait(100000); sharedData->cv.wait(100000);
} // while } // while

View File

@ -1479,7 +1479,7 @@ Result ClusterInfo::createDatabaseCoordinator( // create database
agencyCallback->executeByCallbackOrTimeout(getReloadServerListTimeout() / interval); agencyCallback->executeByCallbackOrTimeout(getReloadServerListTimeout() / interval);
if (!application_features::ApplicationServer::isRetryOK()) { if (application_features::ApplicationServer::isStopping()) {
return Result(TRI_ERROR_CLUSTER_TIMEOUT); return Result(TRI_ERROR_CLUSTER_TIMEOUT);
} }
} }
@ -1583,7 +1583,7 @@ Result ClusterInfo::dropDatabaseCoordinator( // drop database
agencyCallback->executeByCallbackOrTimeout(interval); agencyCallback->executeByCallbackOrTimeout(interval);
if (!application_features::ApplicationServer::isRetryOK()) { if (application_features::ApplicationServer::isStopping()) {
return Result(TRI_ERROR_CLUSTER_TIMEOUT); return Result(TRI_ERROR_CLUSTER_TIMEOUT);
} }
} }
@ -2084,10 +2084,10 @@ Result ClusterInfo::createCollectionsCoordinator(std::string const& databaseName
} }
} }
} while (application_features::ApplicationServer::isRetryOK()); } while (!application_features::ApplicationServer::isStopping());
// If we get here we are not allowed to retry. // If we get here we are not allowed to retry.
// The loop above does not contain a break // The loop above does not contain a break
TRI_ASSERT(!application_features::ApplicationServer::isRetryOK()); TRI_ASSERT(application_features::ApplicationServer::isStopping());
for (auto const& info : infos) { for (auto const& info : infos) {
events::CreateCollection(databaseName, info.name, TRI_ERROR_SHUTTING_DOWN); events::CreateCollection(databaseName, info.name, TRI_ERROR_SHUTTING_DOWN);
} }
@ -2265,7 +2265,7 @@ Result ClusterInfo::dropCollectionCoordinator( // drop collection
agencyCallback->executeByCallbackOrTimeout(interval); agencyCallback->executeByCallbackOrTimeout(interval);
if (!application_features::ApplicationServer::isRetryOK()) { if (application_features::ApplicationServer::isStopping()) {
events::DropCollection(dbName, collectionID, TRI_ERROR_CLUSTER_TIMEOUT); events::DropCollection(dbName, collectionID, TRI_ERROR_CLUSTER_TIMEOUT);
return Result(TRI_ERROR_CLUSTER_TIMEOUT); return Result(TRI_ERROR_CLUSTER_TIMEOUT);
} }
@ -3269,7 +3269,7 @@ Result ClusterInfo::dropIndexCoordinator( // drop index
agencyCallback->executeByCallbackOrTimeout(interval); agencyCallback->executeByCallbackOrTimeout(interval);
if (!application_features::ApplicationServer::isRetryOK()) { if (application_features::ApplicationServer::isStopping()) {
return Result(TRI_ERROR_CLUSTER_TIMEOUT); return Result(TRI_ERROR_CLUSTER_TIMEOUT);
} }
} }

View File

@ -183,11 +183,11 @@ Result FollowerInfo::add(ServerID const& sid) {
} }
std::this_thread::sleep_for(std::chrono::microseconds(500000)); std::this_thread::sleep_for(std::chrono::microseconds(500000));
} while (TRI_microtime() < startTime + 3600 && } while (TRI_microtime() < startTime + 3600 &&
application_features::ApplicationServer::isRetryOK()); !application_features::ApplicationServer::isStopping());
// This is important, give it 1h if needed. We really do not want to get // This is important, give it 1h if needed. We really do not want to get
// into the position to not accept a shard getting-in-sync just because // into the position to not accept a shard getting-in-sync just because
// we cannot talk to the agency temporarily. // we cannot talk to the agency temporarily.
int errorCode = (application_features::ApplicationServer::isRetryOK()) ? TRI_ERROR_CLUSTER_AGENCY_COMMUNICATION_FAILED : TRI_ERROR_SHUTTING_DOWN; int errorCode = (application_features::ApplicationServer::isStopping()) ? TRI_ERROR_SHUTTING_DOWN : TRI_ERROR_CLUSTER_AGENCY_COMMUNICATION_FAILED;
std::string errorMessage = "unable to add follower in agency, timeout in agency CAS operation for key " + path + ": " + TRI_errno_string(errorCode); std::string errorMessage = "unable to add follower in agency, timeout in agency CAS operation for key " + path + ": " + TRI_errno_string(errorCode);
LOG_TOPIC("6295b", ERR, Logger::CLUSTER) << errorMessage; LOG_TOPIC("6295b", ERR, Logger::CLUSTER) << errorMessage;
@ -325,7 +325,8 @@ Result FollowerInfo::remove(ServerID const& sid) {
} }
std::this_thread::sleep_for(std::chrono::microseconds(500000)); std::this_thread::sleep_for(std::chrono::microseconds(500000));
} while (TRI_microtime() < startTime + 7200 && } while (TRI_microtime() < startTime + 7200 &&
application_features::ApplicationServer::isRetryOK()); !application_features::ApplicationServer::isStopping());
// This is important, give it 2h if needed. We really do not want to get // This is important, give it 2h if needed. We really do not want to get
// into the position to fail to drop a follower, just because we cannot // into the position to fail to drop a follower, just because we cannot
// talk to the agency temporarily. The worst would be to drop the follower // talk to the agency temporarily. The worst would be to drop the follower
@ -337,7 +338,7 @@ Result FollowerInfo::remove(ServerID const& sid) {
// rollback: // rollback:
_followers = _oldFollowers; _followers = _oldFollowers;
int errorCode = (application_features::ApplicationServer::isRetryOK()) ? TRI_ERROR_CLUSTER_AGENCY_COMMUNICATION_FAILED : TRI_ERROR_SHUTTING_DOWN; int errorCode = (application_features::ApplicationServer::isStopping()) ? TRI_ERROR_SHUTTING_DOWN : TRI_ERROR_CLUSTER_AGENCY_COMMUNICATION_FAILED;
std::string errorMessage = "unable to remove follower from agency, timeout in agency CAS operation for key " + path + ": " + TRI_errno_string(errorCode); std::string errorMessage = "unable to remove follower from agency, timeout in agency CAS operation for key " + path + ": " + TRI_errno_string(errorCode);
LOG_TOPIC("a0dcc", ERR, Logger::CLUSTER) << errorMessage; LOG_TOPIC("a0dcc", ERR, Logger::CLUSTER) << errorMessage;

View File

@ -34,7 +34,7 @@ namespace application_features {
ApplicationFeature::ApplicationFeature(ApplicationServer& server, std::string const& name) ApplicationFeature::ApplicationFeature(ApplicationServer& server, std::string const& name)
: _server(server), : _server(server),
_name(name), _name(name),
_state(ApplicationServer::FeatureState::UNINITIALIZED), _state(State::UNINITIALIZED),
_enabled(true), _enabled(true),
_optional(false), _optional(false),
_requiresElevatedPrivileges(false), _requiresElevatedPrivileges(false),

View File

@ -44,6 +44,16 @@ class ApplicationFeature {
ApplicationFeature(ApplicationServer& server, std::string const& name); ApplicationFeature(ApplicationServer& server, std::string const& name);
virtual ~ApplicationFeature(); virtual ~ApplicationFeature();
enum class State {
UNINITIALIZED,
INITIALIZED,
VALIDATED,
PREPARED,
STARTED,
STOPPED,
UNPREPARED
};
// return the feature's name // return the feature's name
std::string const& name() const { return _name; } std::string const& name() const { return _name; }
@ -51,7 +61,7 @@ class ApplicationFeature {
bool isOptional() const { return _optional; } bool isOptional() const { return _optional; }
bool isRequired() const { return !_optional; } bool isRequired() const { return !_optional; }
ApplicationServer::FeatureState state() const { return _state; } State state() const { return _state; }
// whether or not the feature is enabled // whether or not the feature is enabled
bool isEnabled() const { return _enabled; } bool isEnabled() const { return _enabled; }
@ -177,7 +187,7 @@ class ApplicationFeature {
private: private:
// set a feature's state. this method should be called by the // set a feature's state. this method should be called by the
// application server only // application server only
void state(ApplicationServer::FeatureState state) { _state = state; } void state(State state) { _state = state; }
// determine all direct and indirect ancestors of a feature // determine all direct and indirect ancestors of a feature
void determineAncestors(); void determineAncestors();
@ -205,7 +215,7 @@ class ApplicationFeature {
std::unordered_set<std::string> _onlyEnabledWith; std::unordered_set<std::string> _onlyEnabledWith;
// state of feature // state of feature
ApplicationServer::FeatureState _state; State _state;
// whether or not the feature is enabled // whether or not the feature is enabled
bool _enabled; bool _enabled;

View File

@ -26,6 +26,7 @@
#include "ApplicationFeatures/PrivilegeFeature.h" #include "ApplicationFeatures/PrivilegeFeature.h"
#include "Basics/ConditionLocker.h" #include "Basics/ConditionLocker.h"
#include "Basics/Result.h" #include "Basics/Result.h"
#include "Basics/ScopeGuard.h"
#include "Basics/StringUtils.h" #include "Basics/StringUtils.h"
#include "Basics/process-utils.h" #include "Basics/process-utils.h"
#include "Logger/Logger.h" #include "Logger/Logger.h"
@ -49,9 +50,8 @@ ApplicationServer* ApplicationServer::server = nullptr;
ApplicationServer::ApplicationServer(std::shared_ptr<ProgramOptions> options, ApplicationServer::ApplicationServer(std::shared_ptr<ProgramOptions> options,
const char* binaryPath) const char* binaryPath)
: _state(ServerState::UNINITIALIZED), : _state(State::UNINITIALIZED),
_options(options), _options(options),
_stopping(false),
_binaryPath(binaryPath) { _binaryPath(binaryPath) {
// register callback function for failures // register callback function for failures
fail = failCallback; fail = failCallback;
@ -78,6 +78,35 @@ ApplicationServer::~ApplicationServer() {
ApplicationServer::server = nullptr; ApplicationServer::server = nullptr;
} }
bool ApplicationServer::isPrepared() {
if (server == nullptr) {
return false;
}
auto tmp = server->state();
return tmp == State::IN_START ||
tmp == State::IN_WAIT ||
tmp == State::IN_SHUTDOWN ||
tmp == State::IN_STOP;
}
bool ApplicationServer::isStopping() {
if (server == nullptr) {
return false;
}
auto tmp = server->state();
return isStoppingState(tmp);
}
bool ApplicationServer::isStoppingState(State state) {
return state == State::IN_SHUTDOWN ||
state == State::IN_STOP ||
state == State::IN_UNPREPARE ||
state == State::STOPPED ||
state == State::ABORTED;
}
void ApplicationServer::throwFeatureNotFoundException(std::string const& name) { void ApplicationServer::throwFeatureNotFoundException(std::string const& name) {
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"unknown feature '" + name + "'"); "unknown feature '" + name + "'");
@ -126,7 +155,7 @@ void ApplicationServer::disableFeatures(std::vector<std::string> const& names, b
// will take ownership of the feature object and destroy it in its // will take ownership of the feature object and destroy it in its
// destructor // destructor
void ApplicationServer::addFeature(ApplicationFeature* feature) { void ApplicationServer::addFeature(ApplicationFeature* feature) {
TRI_ASSERT(feature->state() == FeatureState::UNINITIALIZED); TRI_ASSERT(feature->state() == ApplicationFeature::State::UNINITIALIZED);
_features.emplace(feature->name(), feature); _features.emplace(feature->name(), feature);
} }
@ -173,8 +202,8 @@ void ApplicationServer::run(int argc, char* argv[]) {
// collect options from all features // collect options from all features
// in this phase, all features are order-independent // in this phase, all features are order-independent
_state.store(ServerState::IN_COLLECT_OPTIONS, std::memory_order_relaxed); _state.store(State::IN_COLLECT_OPTIONS, std::memory_order_relaxed);
reportServerProgress(ServerState::IN_COLLECT_OPTIONS); reportServerProgress(State::IN_COLLECT_OPTIONS);
collectOptions(); collectOptions();
// setup dependency, but ignore any failure for now // setup dependency, but ignore any failure for now
@ -193,8 +222,8 @@ void ApplicationServer::run(int argc, char* argv[]) {
_options->seal(); _options->seal();
// validate options of all features // validate options of all features
_state.store(ServerState::IN_VALIDATE_OPTIONS, std::memory_order_relaxed); _state.store(State::IN_VALIDATE_OPTIONS, std::memory_order_relaxed);
reportServerProgress(ServerState::IN_VALIDATE_OPTIONS); reportServerProgress(State::IN_VALIDATE_OPTIONS);
validateOptions(); validateOptions();
// setup and validate all feature dependencies // setup and validate all feature dependencies
@ -212,8 +241,8 @@ void ApplicationServer::run(int argc, char* argv[]) {
// furthermore, they must not write any files under elevated privileges // furthermore, they must not write any files under elevated privileges
// if they want other features to access them, or if they want to access // if they want other features to access them, or if they want to access
// these files with dropped privileges // these files with dropped privileges
_state.store(ServerState::IN_PREPARE, std::memory_order_relaxed); _state.store(State::IN_PREPARE, std::memory_order_relaxed);
reportServerProgress(ServerState::IN_PREPARE); reportServerProgress(State::IN_PREPARE);
prepare(); prepare();
// turn off all features that depend on other features that have been // turn off all features that depend on other features that have been
@ -225,62 +254,79 @@ void ApplicationServer::run(int argc, char* argv[]) {
dropPrivilegesPermanently(); dropPrivilegesPermanently();
// start features. now features are allowed to start threads, write files etc. // start features. now features are allowed to start threads, write files etc.
_state.store(ServerState::IN_START, std::memory_order_relaxed); _state.store(State::IN_START, std::memory_order_relaxed);
reportServerProgress(ServerState::IN_START); reportServerProgress(State::IN_START);
start(); start();
// wait until we get signaled the shutdown request // wait until we get signaled the shutdown request
_state.store(ServerState::IN_WAIT, std::memory_order_relaxed); _state.store(State::IN_WAIT, std::memory_order_relaxed);
reportServerProgress(ServerState::IN_WAIT); reportServerProgress(State::IN_WAIT);
wait(); wait();
// beginShutdown is called asynchronously ----------
// stop all features // stop all features
_state.store(ServerState::IN_STOP, std::memory_order_relaxed); _state.store(State::IN_STOP, std::memory_order_relaxed);
reportServerProgress(ServerState::IN_STOP); reportServerProgress(State::IN_STOP);
stop(); stop();
// unprepare all features // unprepare all features
_state.store(ServerState::IN_UNPREPARE, std::memory_order_relaxed); _state.store(State::IN_UNPREPARE, std::memory_order_relaxed);
reportServerProgress(ServerState::IN_UNPREPARE); reportServerProgress(State::IN_UNPREPARE);
unprepare(); unprepare();
// stopped // stopped
_state.store(ServerState::STOPPED, std::memory_order_relaxed); _state.store(State::STOPPED, std::memory_order_relaxed);
reportServerProgress(ServerState::STOPPED); reportServerProgress(State::STOPPED);
} }
// signal the server to shut down // signal the server to shut down
void ApplicationServer::beginShutdown() { void ApplicationServer::beginShutdown() {
// fetch the old state, check if somebody already called shutdown, and only
// proceed if not.
State old = State::UNINITIALIZED;
do {
old = state();
if (isStoppingState(old)) {
// beginShutdown already called, nothing to do now
return;
}
// try to enter the new state, but make sure nobody changed it in between
} while (!_state.compare_exchange_weak(old, State::IN_SHUTDOWN, std::memory_order_relaxed));
LOG_TOPIC("c7911", TRACE, Logger::STARTUP) << "ApplicationServer::beginShutdown"; LOG_TOPIC("c7911", TRACE, Logger::STARTUP) << "ApplicationServer::beginShutdown";
bool old = _stopping.exchange(true); // make sure that we advance the state when we get out of here
auto waitAborter = scopeGuard([this]() {
CONDITION_LOCKER(guard, _shutdownCondition);
_abortWaiting = true;
guard.signal();
});
// now we can execute the actual shutdown sequence
// fowards the begin shutdown signal to all features // fowards the begin shutdown signal to all features
if (!old) { for (auto it = _orderedFeatures.rbegin(); it != _orderedFeatures.rend(); ++it) {
for (auto it = _orderedFeatures.rbegin(); it != _orderedFeatures.rend(); ++it) { if ((*it)->isEnabled()) {
if ((*it)->isEnabled()) { LOG_TOPIC("e181f", TRACE, Logger::STARTUP) << (*it)->name() << "::beginShutdown";
LOG_TOPIC("e181f", TRACE, Logger::STARTUP) << (*it)->name() << "::beginShutdown"; try {
try { (*it)->beginShutdown();
(*it)->beginShutdown(); } catch (std::exception const& ex) {
} catch (std::exception const& ex) { LOG_TOPIC("b2cf4", ERR, Logger::STARTUP)
LOG_TOPIC("b2cf4", ERR, Logger::STARTUP) << "caught exception during beginShutdown of feature '"
<< "caught exception during beginShutdown of feature '" << (*it)->name() << "': " << ex.what();
<< (*it)->name() << "': " << ex.what(); } catch (...) {
} catch (...) { LOG_TOPIC("3f708", ERR, Logger::STARTUP)
LOG_TOPIC("3f708", ERR, Logger::STARTUP) << "caught unknown exception during beginShutdown of feature '"
<< "caught unknown exception during beginShutdown of feature '" << (*it)->name() << "'";
<< (*it)->name() << "'";
}
} }
} }
} }
CONDITION_LOCKER(guard, _shutdownCondition);
guard.signal();
} }
void ApplicationServer::shutdownFatalError() { void ApplicationServer::shutdownFatalError() {
reportServerProgress(ServerState::ABORT); reportServerProgress(State::ABORTED);
} }
// return VPack options, with optional filters applied to filter // return VPack options, with optional filters applied to filter
@ -384,7 +430,7 @@ void ApplicationServer::validateOptions() {
if (feature->isEnabled()) { if (feature->isEnabled()) {
LOG_TOPIC("fa73c", TRACE, Logger::STARTUP) << feature->name() << "::validateOptions"; LOG_TOPIC("fa73c", TRACE, Logger::STARTUP) << feature->name() << "::validateOptions";
feature->validateOptions(_options); feature->validateOptions(_options);
feature->state(FeatureState::VALIDATED); feature->state(ApplicationFeature::State::VALIDATED);
reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name()); reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name());
} }
} }
@ -487,7 +533,7 @@ void ApplicationServer::setupDependencies(bool failOnMissing) {
for (auto it = features.begin(); it != features.end(); /* no hoisting */) { for (auto it = features.begin(); it != features.end(); /* no hoisting */) {
if ((*it)->isEnabled()) { if ((*it)->isEnabled()) {
// keep feature // keep feature
(*it)->state(FeatureState::INITIALIZED); (*it)->state(ApplicationFeature::State::INITIALIZED);
++it; ++it;
} else { } else {
// remove feature // remove feature
@ -566,7 +612,7 @@ void ApplicationServer::prepare() {
try { try {
LOG_TOPIC("d4e57", TRACE, Logger::STARTUP) << feature->name() << "::prepare"; LOG_TOPIC("d4e57", TRACE, Logger::STARTUP) << feature->name() << "::prepare";
feature->prepare(); feature->prepare();
feature->state(FeatureState::PREPARED); feature->state(ApplicationFeature::State::PREPARED);
} catch (std::exception const& ex) { } catch (std::exception const& ex) {
LOG_TOPIC("37921", ERR, Logger::STARTUP) LOG_TOPIC("37921", ERR, Logger::STARTUP)
<< "caught exception during prepare of feature '" << feature->name() << "caught exception during prepare of feature '" << feature->name()
@ -606,7 +652,7 @@ void ApplicationServer::start() {
try { try {
feature->start(); feature->start();
feature->state(FeatureState::STARTED); feature->state(ApplicationFeature::State::STARTED);
reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name()); reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name());
} catch (basics::Exception const& ex) { } catch (basics::Exception const& ex) {
res.reset( res.reset(
@ -643,13 +689,13 @@ void ApplicationServer::start() {
if (!feature->isEnabled()) { if (!feature->isEnabled()) {
continue; continue;
} }
if (feature->state() == FeatureState::STARTED) { if (feature->state() == ApplicationFeature::State::STARTED) {
LOG_TOPIC("e5cfd", TRACE, Logger::STARTUP) LOG_TOPIC("e5cfd", TRACE, Logger::STARTUP)
<< "forcefully stopping feature '" << feature->name() << "'"; << "forcefully stopping feature '" << feature->name() << "'";
try { try {
feature->beginShutdown(); feature->beginShutdown();
feature->stop(); feature->stop();
feature->state(FeatureState::STOPPED); feature->state(ApplicationFeature::State::STOPPED);
} catch (...) { } catch (...) {
// ignore errors on shutdown // ignore errors on shutdown
LOG_TOPIC("13223", TRACE, Logger::STARTUP) LOG_TOPIC("13223", TRACE, Logger::STARTUP)
@ -661,12 +707,12 @@ void ApplicationServer::start() {
// try to unprepare all feature that we just started // try to unprepare all feature that we just started
for (auto it = _orderedFeatures.rbegin(); it != _orderedFeatures.rend(); ++it) { for (auto it = _orderedFeatures.rbegin(); it != _orderedFeatures.rend(); ++it) {
auto feature = *it; auto feature = *it;
if (feature->state() == FeatureState::STOPPED) { if (feature->state() == ApplicationFeature::State::STOPPED) {
LOG_TOPIC("6ba4f", TRACE, Logger::STARTUP) LOG_TOPIC("6ba4f", TRACE, Logger::STARTUP)
<< "forcefully unpreparing feature '" << feature->name() << "'"; << "forcefully unpreparing feature '" << feature->name() << "'";
try { try {
feature->unprepare(); feature->unprepare();
feature->state(FeatureState::UNPREPARED); feature->state(ApplicationFeature::State::UNPREPARED);
} catch (...) { } catch (...) {
// ignore errors on shutdown // ignore errors on shutdown
LOG_TOPIC("7d68f", TRACE, Logger::STARTUP) LOG_TOPIC("7d68f", TRACE, Logger::STARTUP)
@ -707,7 +753,7 @@ void ApplicationServer::stop() {
<< "caught unknown exception during stop of feature '" << "caught unknown exception during stop of feature '"
<< feature->name() << "'"; << feature->name() << "'";
} }
feature->state(FeatureState::STOPPED); feature->state(ApplicationFeature::State::STOPPED);
reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name()); reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name());
} }
} }
@ -733,7 +779,7 @@ void ApplicationServer::unprepare() {
<< "caught unknown exception during unprepare of feature '" << "caught unknown exception during unprepare of feature '"
<< feature->name() << "'"; << feature->name() << "'";
} }
feature->state(FeatureState::UNPREPARED); feature->state(ApplicationFeature::State::UNPREPARED);
reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name()); reportFeatureProgress(_state.load(std::memory_order_relaxed), feature->name());
} }
} }
@ -741,9 +787,18 @@ void ApplicationServer::unprepare() {
void ApplicationServer::wait() { void ApplicationServer::wait() {
LOG_TOPIC("f86df", TRACE, Logger::STARTUP) << "ApplicationServer::wait"; LOG_TOPIC("f86df", TRACE, Logger::STARTUP) << "ApplicationServer::wait";
while (!_stopping) { // wait here until beginShutdown has been called and finished
while (true) {
// wait until somebody calls beginShutdown and it finishes
CONDITION_LOCKER(guard, _shutdownCondition); CONDITION_LOCKER(guard, _shutdownCondition);
guard.wait(100000);
if (_abortWaiting) {
// yippieh!
break;
}
using namespace std::chrono_literals;
guard.wait(100ms);
} }
} }
@ -789,14 +844,44 @@ void ApplicationServer::dropPrivilegesPermanently() {
_privilegesDropped = true; _privilegesDropped = true;
} }
void ApplicationServer::reportServerProgress(ServerState state) { void ApplicationServer::reportServerProgress(State state) {
for (auto reporter : _progressReports) { for (auto reporter : _progressReports) {
reporter._state(state); reporter._state(state);
} }
} }
void ApplicationServer::reportFeatureProgress(ServerState state, std::string const& name) { void ApplicationServer::reportFeatureProgress(State state, std::string const& name) {
for (auto reporter : _progressReports) { for (auto reporter : _progressReports) {
reporter._feature(state, name); reporter._feature(state, name);
} }
} }
char const* ApplicationServer::stringifyState() const {
switch (_state.load()) {
case State::UNINITIALIZED:
return "uninitialized";
case State::IN_COLLECT_OPTIONS:
return "in collect options";
case State::IN_VALIDATE_OPTIONS:
return "in validate options";
case State::IN_PREPARE:
return "in prepare";
case State::IN_START:
return "in start";
case State::IN_WAIT:
return "in wait";
case State::IN_SHUTDOWN:
return "in beginShutdown";
case State::IN_STOP:
return "in stop";
case State::IN_UNPREPARE:
return "in unprepare";
case State::STOPPED:
return "in stopped";
case State::ABORTED:
return "in aborted";
}
// we should never get here
TRI_ASSERT(false);
return "unknown";
}

View File

@ -39,26 +39,6 @@ class ProgramOptions;
namespace application_features { namespace application_features {
class ApplicationFeature; class ApplicationFeature;
// handled i.e. in WindowsServiceFeature.cpp
enum class ServerState {
UNINITIALIZED,
IN_COLLECT_OPTIONS,
IN_VALIDATE_OPTIONS,
IN_PREPARE,
IN_START,
IN_WAIT,
IN_STOP,
IN_UNPREPARE,
STOPPED,
ABORT
};
class ProgressHandler {
public:
std::function<void(ServerState)> _state;
std::function<void(ServerState, std::string const& featureName)> _feature;
};
// the following phases exists: // the following phases exists:
// //
// `collectOptions` // `collectOptions`
@ -119,39 +99,38 @@ class ApplicationServer {
ApplicationServer& operator=(ApplicationServer const&) = delete; ApplicationServer& operator=(ApplicationServer const&) = delete;
public: public:
enum class FeatureState { // handled i.e. in WindowsServiceFeature.cpp
enum class State : int {
UNINITIALIZED, UNINITIALIZED,
INITIALIZED, IN_COLLECT_OPTIONS,
VALIDATED, IN_VALIDATE_OPTIONS,
PREPARED, IN_PREPARE,
STARTED, IN_START,
IN_WAIT,
IN_SHUTDOWN,
IN_STOP,
IN_UNPREPARE,
STOPPED, STOPPED,
UNPREPARED ABORTED
}; };
class ProgressHandler {
public:
std::function<void(State)> _state;
std::function<void(State, std::string const& featureName)> _feature;
};
static ApplicationServer* server; static ApplicationServer* server;
static bool isStopping() {
return server != nullptr && server->_stopping.load(); /// @brief whether or not the server has made it as least as far as the IN_START state
} static bool isPrepared();
/// @brief whether or not the server has made it as least as far as the IN_SHUTDOWN state
static bool isStopping();
// Today this static function is a duplicate of isStopping(). The /// @brief whether or not state is the shutting down state or further (i.e. stopped, aborted etc.)
// function name 'isStopping()' is defined in other classes and static bool isStoppingState(State state);
// can cause scope confusion. It also causes confusion as to when
// the application versus an individual feature or thread has begun
// stopping. This function is intended to be used within communication
// retry loops where infinite retries have previously blocked clean
// "stopping".
static bool isRetryOK() { return !isStopping(); }
static bool isPrepared() {
if (server != nullptr) {
ServerState tmp = server->_state.load(std::memory_order_relaxed);
return tmp == ServerState::IN_START || tmp == ServerState::IN_WAIT ||
tmp == ServerState::IN_STOP;
}
return false;
}
// returns the feature with the given name if known // returns the feature with the given name if known
// throws otherwise // throws otherwise
@ -192,6 +171,9 @@ class ApplicationServer {
std::string helpSection() const { return _helpSection; } std::string helpSection() const { return _helpSection; }
bool helpShown() const { return !_helpSection.empty(); } bool helpShown() const { return !_helpSection.empty(); }
/// @brief stringify the internal state
char const* stringifyState() const;
// adds a feature to the application server. the application server // adds a feature to the application server. the application server
// will take ownership of the feature object and destroy it in its // will take ownership of the feature object and destroy it in its
// destructor // destructor
@ -237,7 +219,7 @@ class ApplicationServer {
std::shared_ptr<options::ProgramOptions> options() const { return _options; } std::shared_ptr<options::ProgramOptions> options() const { return _options; }
// return the server state // return the server state
ServerState state() const { return _state; } State state() const { return _state; }
void addReporter(ProgressHandler reporter) { void addReporter(ProgressHandler reporter) {
_progressReports.emplace_back(reporter); _progressReports.emplace_back(reporter);
@ -257,7 +239,7 @@ class ApplicationServer {
return lookupFeature<T>(T::name()); return lookupFeature<T>(T::name());
} }
char const* getBinaryPath() { return _binaryPath; } char const* getBinaryPath() const { return _binaryPath; }
void registerStartupCallback(std::function<void()> const& callback) { void registerStartupCallback(std::function<void()> const& callback) {
_startupCallbacks.emplace_back(callback); _startupCallbacks.emplace_back(callback);
@ -323,12 +305,12 @@ class ApplicationServer {
void dropPrivilegesTemporarily(); void dropPrivilegesTemporarily();
void dropPrivilegesPermanently(); void dropPrivilegesPermanently();
void reportServerProgress(ServerState); void reportServerProgress(State);
void reportFeatureProgress(ServerState, std::string const&); void reportFeatureProgress(State, std::string const&);
private: private:
// the current state // the current state
std::atomic<ServerState> _state; std::atomic<State> _state;
// the shared program options // the shared program options
std::shared_ptr<options::ProgramOptions> _options; std::shared_ptr<options::ProgramOptions> _options;
@ -339,11 +321,12 @@ class ApplicationServer {
// features order for prepare/start // features order for prepare/start
std::vector<ApplicationFeature*> _orderedFeatures; std::vector<ApplicationFeature*> _orderedFeatures;
// will be signalled when the application server is asked to shut down // will be signaled when the application server is asked to shut down
basics::ConditionVariable _shutdownCondition; basics::ConditionVariable _shutdownCondition;
// stop flag. this is being changed by calling beginShutdown /// @brief the condition variable protects access to this flag
std::atomic<bool> _stopping; /// the flag is set to true when beginShutdown finishes
bool _abortWaiting = false;
// whether or not privileges have been dropped permanently // whether or not privileges have been dropped permanently
bool _privilegesDropped = false; bool _privilegesDropped = false;

View File

@ -36,16 +36,17 @@ class CommunicationFeaturePhase : public ApplicationFeaturePhase {
*/ */
bool getCommAllowed() { bool getCommAllowed() {
switch (state()) { switch (state()) {
case ApplicationServer::FeatureState::UNINITIALIZED: case ApplicationFeature::State::UNINITIALIZED:
case ApplicationServer::FeatureState::INITIALIZED: case ApplicationFeature::State::INITIALIZED:
case ApplicationServer::FeatureState::VALIDATED: case ApplicationFeature::State::VALIDATED:
case ApplicationServer::FeatureState::PREPARED: case ApplicationFeature::State::PREPARED:
case ApplicationServer::FeatureState::STARTED: case ApplicationFeature::State::STARTED:
case ApplicationServer::FeatureState::STOPPED: case ApplicationFeature::State::STOPPED:
return true; return true;
case ApplicationServer::FeatureState::UNPREPARED: case ApplicationFeature::State::UNPREPARED:
return false; break;
} }
return false; return false;
} }
}; };

View File

@ -382,7 +382,7 @@ void SetServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode,
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/// @brief wrap ArangoDB server so we can properly emmit a status once we're /// @brief wrap ArangoDB server so we can properly emit a status once we're
/// really up and running. /// really up and running.
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
void WindowsServiceFeature::startupProgress() { void WindowsServiceFeature::startupProgress() {
@ -390,47 +390,54 @@ void WindowsServiceFeature::startupProgress() {
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/// @brief wrap ArangoDB server so we can properly emmit a status once we're /// @brief wrap ArangoDB server so we can properly emit a status once we're
/// really up and running. /// really up and running.
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
void WindowsServiceFeature::startupFinished() { void WindowsServiceFeature::startupFinished() {
// startup finished - signalize we're running. // startup finished - signal that we're running.
SetServiceStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0); SetServiceStatus(SERVICE_RUNNING, NO_ERROR, 0, 0, 0);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/// @brief wrap ArangoDB server so we can properly emmit a status on shutdown /// @brief wrap ArangoDB server so we can properly emit a status on shutdown
/// starting /// starting
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
void WindowsServiceFeature::shutDownBegins() { void WindowsServiceFeature::shutdownBegins() {
// startup finished - signalize we're running. auto shutdownNoted = _shutdownNoted.exchange(true);
if (shutdownNoted) {
// we were already called before. don't note the shutdown twice
return;
}
// startup finished - signal that we are shutting down
SetServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 0, 0); SetServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0, 0, 0);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/// @brief wrap ArangoDB server so we can properly emmit a status on shutdown /// @brief wrap ArangoDB server so we can properly emit a status on shutdown
/// starting /// starting
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
void WindowsServiceFeature::shutDownComplete() { void WindowsServiceFeature::shutdownComplete() {
// startup finished - signalize we're running. // startup finished - signal that we have shut down
SetServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0); SetServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0, 0);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/// @brief wrap ArangoDB server so we can properly emmit a status on shutdown /// @brief wrap ArangoDB server so we can properly emit a status on shutdown
/// starting /// failed
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
void WindowsServiceFeature::shutDownFailure() { void WindowsServiceFeature::shutdownFailure() {
// startup finished - signalize we're running. // startup finished - signal that shutdown has failed
SetServiceStatus(SERVICE_STOP, ERROR_SERVICE_SPECIFIC_ERROR, 0, 0, 1); SetServiceStatus(SERVICE_STOP, ERROR_SERVICE_SPECIFIC_ERROR, 0, 0, 1);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
/// @brief wrap ArangoDB server so we can properly emmit a status on shutdown /// @brief wrap ArangoDB server so we can properly emit a status on shutdown
/// starting /// starting
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
void WindowsServiceFeature::abortFailure(uint16_t exitCode) { void WindowsServiceFeature::abortFailure(uint16_t exitCode) {
// startup finished - signalize we're running. // startup finished - signal that the service has been aborted
SetServiceStatus(SERVICE_STOP, ERROR_SERVICE_SPECIFIC_ERROR, 0, 0, exitCode); SetServiceStatus(SERVICE_STOP, ERROR_SERVICE_SPECIFIC_ERROR, 0, 0, exitCode);
} }
@ -471,8 +478,10 @@ void WINAPI ServiceCtrl(DWORD dwCtrlCode) {
} }
WindowsServiceFeature::WindowsServiceFeature(application_features::ApplicationServer& server) WindowsServiceFeature::WindowsServiceFeature(application_features::ApplicationServer& server)
: ApplicationFeature(server, "WindowsService"), _server(&server) { : ApplicationFeature(server, "WindowsService"),
_progress = 2; _server(&server),
_progress(2),
_shutdownNoted(false) {
setOptional(true); setOptional(true);
requiresElevatedPrivileges(true); requiresElevatedPrivileges(true);
startsAfter("GreetingsPhase"); startsAfter("GreetingsPhase");
@ -502,7 +511,7 @@ void WindowsServiceFeature::collectOptions(std::shared_ptr<ProgramOptions> optio
arangodb::options::Flags::Command)); arangodb::options::Flags::Command));
options->addOption("--uninstall-service", options->addOption("--uninstall-service",
"used to UNregister a service with windows", "used to unregister a service with windows",
new BooleanParameter(&_unInstallService), new BooleanParameter(&_unInstallService),
arangodb::options::makeFlags(arangodb::options::Flags::Hidden, arangodb::options::makeFlags(arangodb::options::Flags::Hidden,
arangodb::options::Flags::Command)); arangodb::options::Flags::Command));
@ -523,7 +532,7 @@ void WindowsServiceFeature::collectOptions(std::shared_ptr<ProgramOptions> optio
options->addOption( options->addOption(
"--servicectl-start-wait", "--servicectl-start-wait",
"command an already registered service to start and wait till its up", "command an already registered service to start and wait till it's up",
new BooleanParameter(&_startWaitService), new BooleanParameter(&_startWaitService),
arangodb::options::makeFlags(arangodb::options::Flags::Hidden, arangodb::options::makeFlags(arangodb::options::Flags::Hidden,
arangodb::options::Flags::Command)); arangodb::options::Flags::Command));
@ -536,7 +545,7 @@ void WindowsServiceFeature::collectOptions(std::shared_ptr<ProgramOptions> optio
options->addOption( options->addOption(
"--servicectl-stop-wait", "--servicectl-stop-wait",
"command an already registered service to stop and wait till its gone", "command an already registered service to stop and wait till it's gone",
new BooleanParameter(&_stopWaitService), new BooleanParameter(&_stopWaitService),
arangodb::options::makeFlags(arangodb::options::Flags::Hidden, arangodb::options::makeFlags(arangodb::options::Flags::Hidden,
arangodb::options::Flags::Command)); arangodb::options::Flags::Command));
@ -563,35 +572,36 @@ void WindowsServiceFeature::validateOptions(std::shared_ptr<ProgramOptions> opti
} else if (_startAsService) { } else if (_startAsService) {
TRI_SetWindowsServiceAbortFunction(abortService); TRI_SetWindowsServiceAbortFunction(abortService);
ProgressHandler reporter{[this](ServerState state) { ApplicationServer::ProgressHandler reporter{[this](ApplicationServer::State state) {
switch (state) { switch (state) {
case ServerState::IN_WAIT: case ApplicationServer::State::IN_WAIT:
this->startupFinished(); this->startupFinished();
break; break;
case ServerState::IN_STOP: case ApplicationServer::State::IN_SHUTDOWN:
this->shutDownBegins(); case ApplicationServer::State::IN_STOP:
this->shutdownBegins();
break; break;
case ServerState::IN_COLLECT_OPTIONS: case ApplicationServer::State::IN_COLLECT_OPTIONS:
case ServerState::IN_VALIDATE_OPTIONS: case ApplicationServer::State::IN_VALIDATE_OPTIONS:
case ServerState::IN_PREPARE: case ApplicationServer::State::IN_PREPARE:
case ServerState::IN_START: case ApplicationServer::State::IN_START:
this->startupProgress(); this->startupProgress();
break; break;
case ServerState::ABORT: case ApplicationServer::State::ABORTED:
this->shutDownFailure(); this->shutdownFailure();
break; break;
case ServerState::UNINITIALIZED: case ApplicationServer::State::UNINITIALIZED:
case ServerState::STOPPED: case ApplicationServer::State::STOPPED:
break; break;
} }
}, },
[this](application_features::ServerState state, [this](application_features::ApplicationServer::State state,
std::string const& name) { std::string const& name) {
switch (state) { switch (state) {
case ServerState::IN_COLLECT_OPTIONS: case ApplicationServer::State::IN_COLLECT_OPTIONS:
case ServerState::IN_VALIDATE_OPTIONS: case ApplicationServer::State::IN_VALIDATE_OPTIONS:
case ServerState::IN_PREPARE: case ApplicationServer::State::IN_PREPARE:
case ServerState::IN_START: case ApplicationServer::State::IN_START:
this->startupProgress(); this->startupProgress();
break; break;
default: default:

View File

@ -24,6 +24,9 @@
#define ARANGODB_APPLICATION_FEATURES_WINDOWS_SERVICE_FEATURE_H 1 #define ARANGODB_APPLICATION_FEATURES_WINDOWS_SERVICE_FEATURE_H 1
#include "ApplicationFeatures/ApplicationFeature.h" #include "ApplicationFeatures/ApplicationFeature.h"
#include "ApplicationFeatures/ApplicationServer.h"
#include <atomic>
extern SERVICE_STATUS_HANDLE ServiceStatus; extern SERVICE_STATUS_HANDLE ServiceStatus;
@ -49,9 +52,9 @@ class WindowsServiceFeature final : public application_features::ApplicationFeat
void startupFinished(); void startupFinished();
void shutDownBegins(); void shutdownBegins();
void shutDownComplete(); void shutdownComplete();
void shutDownFailure(); void shutdownFailure();
void abortFailure(uint16_t exitCode); void abortFailure(uint16_t exitCode);
static void abortService(uint16_t exitCode); static void abortService(uint16_t exitCode);
@ -69,8 +72,11 @@ class WindowsServiceFeature final : public application_features::ApplicationFeat
private: private:
uint16_t _progress; uint16_t _progress;
/// @brief flag that tells us whether we have been informed about the shutdown before
std::atomic<bool> _shutdownNoted;
}; };
} // namespace arangodb } // namespace arangodb
#endif #endif

View File

@ -46,7 +46,7 @@ typedef std::vector<Expected> ExpectedVec_t;
// //
// TestProgressHandler lets us know once ApplicationServer is ready // TestProgressHandler lets us know once ApplicationServer is ready
// //
class TestProgressHandler : public arangodb::application_features::ProgressHandler { class TestProgressHandler : public arangodb::application_features::ApplicationServer::ProgressHandler {
public: public:
TestProgressHandler() { TestProgressHandler() {
_serverReady=false; _serverReady=false;
@ -59,15 +59,15 @@ public:
} }
void StateChange(arangodb::application_features::ServerState newState) { void StateChange(arangodb::application_features::ApplicationServer::State newState) {
if (arangodb::application_features::ServerState::IN_WAIT == newState) { if (arangodb::application_features::ApplicationServer::State::IN_WAIT == newState) {
CONDITION_LOCKER(clock, _serverReadyCond); CONDITION_LOCKER(clock, _serverReadyCond);
_serverReady = true; _serverReady = true;
_serverReadyCond.broadcast(); _serverReadyCond.broadcast();
} }
} }
void FeatureChange(arangodb::application_features::ServerState newState, std::string const &) { void FeatureChange(arangodb::application_features::ApplicationServer::State newState, std::string const &) {
} }
arangodb::basics::ConditionVariable _serverReadyCond; arangodb::basics::ConditionVariable _serverReadyCond;