diff --git a/lib/ApplicationFeatures/ApplicationServer.cpp b/lib/ApplicationFeatures/ApplicationServer.cpp index cf5a99d56c..4ab2134be0 100644 --- a/lib/ApplicationFeatures/ApplicationServer.cpp +++ b/lib/ApplicationFeatures/ApplicationServer.cpp @@ -146,10 +146,11 @@ bool ApplicationServer::isRequired(std::string const& name) const { // signal. after that, it will shutdown all features void ApplicationServer::run(int argc, char* argv[]) { LOG_TOPIC(TRACE, Logger::STARTUP) << "ApplicationServer::run"; - + // collect options from all features // in this phase, all features are order-independent _state = ServerState::IN_COLLECT_OPTIONS; + reportServerProgress(_state); collectOptions(); // setup dependency, but ignore any failure for now @@ -164,6 +165,7 @@ void ApplicationServer::run(int argc, char* argv[]) { // validate options of all features _state = ServerState::IN_VALIDATE_OPTIONS; + reportServerProgress(_state); validateOptions(); // enable automatic features @@ -181,24 +183,30 @@ void ApplicationServer::run(int argc, char* argv[]) { // if they want other features to access them, or if they want to access // these files with dropped privileges _state = ServerState::IN_PREPARE; + reportServerProgress(_state); prepare(); // permanently drop the privileges dropPrivilegesPermanently(); - + // start features. now features are allowed to start threads, write files etc. _state = ServerState::IN_START; + reportServerProgress(_state); start(); // wait until we get signaled the shutdown request + _state = ServerState::IN_WAIT; + reportServerProgress(_state); wait(); // stop all features _state = ServerState::IN_STOP; + reportServerProgress(_state); stop(); // stopped _state = ServerState::STOPPED; + reportServerProgress(_state); } // signal the server to shut down @@ -253,6 +261,7 @@ void ApplicationServer::collectOptions() { apply([this](ApplicationFeature* feature) { LOG_TOPIC(TRACE, Logger::STARTUP) << feature->name() << "::loadOptions"; feature->collectOptions(_options); + reportFeatureProgress(_state, feature->name()); }, true); } @@ -302,11 +311,13 @@ void ApplicationServer::parseOptions(int argc, char* argv[]) { void ApplicationServer::validateOptions() { LOG_TOPIC(TRACE, Logger::STARTUP) << "ApplicationServer::validateOptions"; - for (auto it = _orderedFeatures.begin(); it != _orderedFeatures.end(); ++it) { - if ((*it)->isEnabled()) { - LOG_TOPIC(TRACE, Logger::STARTUP) << (*it)->name() << "::validateOptions"; - (*it)->validateOptions(_options); - (*it)->state(FeatureState::VALIDATED); + for (auto feature : _orderedFeatures) { + if (feature->isEnabled()) { + LOG_TOPIC(TRACE, Logger::STARTUP) << feature->name() + << "::validateOptions"; + feature->validateOptions(_options); + feature->state(FeatureState::VALIDATED); + reportFeatureProgress(_state, feature->name()); } } } @@ -383,7 +394,7 @@ void ApplicationServer::setupDependencies(bool failOnMissing) { } insert = j - 1; } - if (insert != i ){ + if (insert != i) { for (size_t j = i; j > insert; --j) { features[j] = features[j - 1]; } @@ -437,9 +448,9 @@ void ApplicationServer::prepare() { // we start with elevated privileges bool privilegesElevated = true; - for (auto it = _orderedFeatures.begin(); it != _orderedFeatures.end(); ++it) { - if ((*it)->isEnabled()) { - bool const requiresElevated = (*it)->requiresElevatedPrivileges(); + for (auto feature : _orderedFeatures) { + if (feature->isEnabled()) { + bool const requiresElevated = feature->requiresElevatedPrivileges(); if (requiresElevated != privilegesElevated) { // must change privileges for the feature @@ -453,9 +464,9 @@ void ApplicationServer::prepare() { } try { - LOG_TOPIC(TRACE, Logger::STARTUP) << (*it)->name() << "::prepare"; - (*it)->prepare(); - (*it)->state(FeatureState::PREPARED); + LOG_TOPIC(TRACE, Logger::STARTUP) << feature->name() << "::prepare"; + feature->prepare(); + feature->state(FeatureState::PREPARED); } catch (...) { // restore original privileges if (!privilegesElevated) { @@ -463,6 +474,8 @@ void ApplicationServer::prepare() { } throw; } + + reportFeatureProgress(_state, feature->name()); } } } @@ -470,21 +483,22 @@ void ApplicationServer::prepare() { void ApplicationServer::start() { LOG_TOPIC(TRACE, Logger::STARTUP) << "ApplicationServer::start"; - for (auto it = _orderedFeatures.begin(); it != _orderedFeatures.end(); ++it) { - LOG_TOPIC(TRACE, Logger::STARTUP) << (*it)->name() << "::start"; - (*it)->start(); - (*it)->state(FeatureState::STARTED); + for (auto feature : _orderedFeatures) { + LOG_TOPIC(TRACE, Logger::STARTUP) << feature->name() << "::start"; + feature->start(); + feature->state(FeatureState::STARTED); + reportFeatureProgress(_state, feature->name()); } } void ApplicationServer::stop() { LOG_TOPIC(TRACE, Logger::STARTUP) << "ApplicationServer::stop"; - for (auto it = _orderedFeatures.rbegin(); it != _orderedFeatures.rend(); - ++it) { - LOG_TOPIC(TRACE, Logger::STARTUP) << (*it)->name() << "::stop"; - (*it)->stop(); - (*it)->state(FeatureState::STOPPED); + for (auto feature : _orderedFeatures) { + LOG_TOPIC(TRACE, Logger::STARTUP) << feature->name() << "::stop"; + feature->stop(); + feature->state(FeatureState::STOPPED); + reportFeatureProgress(_state, feature->name()); } } @@ -538,3 +552,16 @@ void ApplicationServer::dropPrivilegesPermanently() { _privilegesDropped = true; } + +void ApplicationServer::reportServerProgress(ServerState state) { + for (auto reporter : _progressReports) { + reporter._state(state); + } +} + +void ApplicationServer::reportFeatureProgress(ServerState state, + std::string const& name) { + for (auto reporter : _progressReports) { + reporter._feature(state, name); + } +} diff --git a/lib/ApplicationFeatures/ApplicationServer.h b/lib/ApplicationFeatures/ApplicationServer.h index 5301523242..1eafcdb811 100644 --- a/lib/ApplicationFeatures/ApplicationServer.h +++ b/lib/ApplicationFeatures/ApplicationServer.h @@ -36,6 +36,23 @@ class ProgramOptions; namespace application_features { class ApplicationFeature; +enum class ServerState { + UNINITIALIZED, + IN_COLLECT_OPTIONS, + IN_VALIDATE_OPTIONS, + IN_PREPARE, + IN_START, + IN_WAIT, + IN_STOP, + STOPPED +}; + +class ProgressHandler { + public: + std::function _state; + std::function _feature; +}; + // the following phases exists: // // `collectOptions` @@ -91,16 +108,6 @@ class ApplicationServer { ApplicationServer& operator=(ApplicationServer const&) = delete; public: - enum class ServerState { - UNINITIALIZED, - IN_COLLECT_OPTIONS, - IN_VALIDATE_OPTIONS, - IN_PREPARE, - IN_START, - IN_STOP, - STOPPED - }; - enum class FeatureState { UNINITIALIZED, INITIALIZED, @@ -116,6 +123,7 @@ class ApplicationServer { } static bool isPrepared() { return server != nullptr && (server->_state == ServerState::IN_START || + server->_state == ServerState::IN_WAIT || server->_state == ServerState::IN_STOP); } @@ -189,6 +197,10 @@ class ApplicationServer { // return the server state ServerState state() const { return _state; } + void addReporter(ProgressHandler reporter) { + _progressReports.emplace_back(reporter); + } + private: // look up a feature and return a pointer to it. may be nullptr static ApplicationFeature* lookupFeature(std::string const&); @@ -244,6 +256,9 @@ class ApplicationServer { void dropPrivilegesTemporarily(); void dropPrivilegesPermanently(); + void reportServerProgress(ServerState); + void reportFeatureProgress(ServerState, std::string const&); + private: // the current state ServerState _state = ServerState::UNINITIALIZED; @@ -265,6 +280,9 @@ class ApplicationServer { // whether or not to dump dependencies bool _dumpDependencies = false; + + // reporter for progress + std::vector _progressReports; }; } } diff --git a/lib/ApplicationFeatures/TempFeature.cpp b/lib/ApplicationFeatures/TempFeature.cpp index b0b1083333..d6db0e94b6 100644 --- a/lib/ApplicationFeatures/TempFeature.cpp +++ b/lib/ApplicationFeatures/TempFeature.cpp @@ -22,6 +22,7 @@ #include "ApplicationFeatures/TempFeature.h" +#include "Basics/ArangoGlobalContext.h" #include "Basics/files.h" #include "Logger/Logger.h" #include "ProgramOptions/ProgramOptions.h" @@ -58,4 +59,11 @@ void TempFeature::start() { // must be used after drop privileges and be called to set it to avoid raise // conditions TRI_GetTempPath(); + + // signal that the temp path is available + auto context = ArangoGlobalContext::CONTEXT; + + if (context != nullptr) { + context->tempPathAvailable(); + } } diff --git a/lib/Basics/ArangoGlobalContext.cpp b/lib/Basics/ArangoGlobalContext.cpp index 9621092304..ce6bb6f8d5 100644 --- a/lib/Basics/ArangoGlobalContext.cpp +++ b/lib/Basics/ArangoGlobalContext.cpp @@ -22,6 +22,10 @@ #include "ArangoGlobalContext.h" +#ifdef _WIN32 +#include +#endif + #include "Basics/debugging.h" #include "Basics/files.h" #include "Logger/LogAppender.h" @@ -30,10 +34,6 @@ using namespace arangodb; -#ifndef _WIN32 -static void ReopenLog(int) { LogAppender::reopen(); } -#endif - static void AbortHandler(int signum) { TRI_PrintBacktrace(); #ifdef _WIN32 @@ -44,11 +44,77 @@ static void AbortHandler(int signum) { #endif } +#ifndef _WIN32 +static void ReopenLog(int) { LogAppender::reopen(); } +#endif + +#ifdef _WIN32 +static std::string miniDumpFilename = "c:\\arangodpanic.dmp"; + +LONG CALLBACK unhandledExceptionHandler(EXCEPTION_POINTERS* e) { +#if ARANGODB_ENABLE_BACKTRACE + + if ((e != nullptr) && (e->ExceptionRecord != nullptr)) { + LOG_FATAL_WINDOWS("Unhandled exception: %d", + (int)e->ExceptionRecord->ExceptionCode); + } else { + LOG_FATAL_WINDOWS("Unhandled exception without ExceptionCode!"); + } + + std::string bt; + TRI_GetBacktrace(bt); + std::cerr << bt << std::endl; + LOG_FATAL_WINDOWS(bt.c_str()); + + HANDLE hFile = + CreateFile(miniDumpFilename.c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + + if (hFile == INVALID_HANDLE_VALUE) { + LOG_FATAL_WINDOWS("could not open minidump file : %lu", GetLastError()); + return EXCEPTION_CONTINUE_SEARCH; + } + + MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; + exceptionInfo.ThreadId = GetCurrentThreadId(); + exceptionInfo.ExceptionPointers = e; + exceptionInfo.ClientPointers = FALSE; + + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, + MINIDUMP_TYPE(MiniDumpWithIndirectlyReferencedMemory | + MiniDumpScanMemory | MiniDumpWithFullMemory), + e ? &exceptionInfo : nullptr, nullptr, nullptr); + + if (hFile) { + CloseHandle(hFile); + hFile = nullptr; + } + + LOG_FATAL_WINDOWS("wrote minidump: %s", miniDumpFilename.c_str()); +#endif + + if ((e != nullptr) && (e->ExceptionRecord != nullptr)) { + LOG_FATAL_WINDOWS("Unhandled exception: %d - will crash now.", + (int)e->ExceptionRecord->ExceptionCode); + } else { + LOG_FATAL_WINDOWS( + "Unhandled exception without ExceptionCode - will crash now.!"); + } + + return EXCEPTION_CONTINUE_SEARCH; +} +#endif + ArangoGlobalContext* ArangoGlobalContext::CONTEXT = nullptr; ArangoGlobalContext::ArangoGlobalContext(int argc, char* argv[]) : _binaryName(TRI_BinaryName(argv[0])), _ret(EXIT_FAILURE) { ADB_WindowsEntryFunction(); + +#ifdef _WIN32 + SetUnhandledExceptionFilter(unhandledExceptionHandler); +#endif + TRIAGENS_REST_INITIALIZE(); CONTEXT = this; } @@ -184,3 +250,11 @@ void ArangoGlobalContext::runStartupChecks() { #endif } +void ArangoGlobalContext::tempPathAvailable() { +#ifdef _WIN32 + miniDumpFilename = TRI_GetTempPath(); + + miniDumpFilename += + "\\minidump_" + std::to_string(GetCurrentProcessId()) + ".dmp"; +#endif +} diff --git a/lib/Basics/ArangoGlobalContext.h b/lib/Basics/ArangoGlobalContext.h index 917876f87b..64eccce93e 100644 --- a/lib/Basics/ArangoGlobalContext.h +++ b/lib/Basics/ArangoGlobalContext.h @@ -42,6 +42,7 @@ class ArangoGlobalContext { void maskAllSignals(); void unmaskStandardSignals(); void runStartupChecks(); + void tempPathAvailable(); private: std::string _binaryName;