diff --git a/src/capi_frontend/capi.cpp b/src/capi_frontend/capi.cpp index fd80377ea6..97694d938d 100644 --- a/src/capi_frontend/capi.cpp +++ b/src/capi_frontend/capi.cpp @@ -370,7 +370,7 @@ DLL_PUBLIC OVMS_Status* OVMS_ServerStartFromConfigurationFile(OVMS_Server* serve ovms::Server* srv = reinterpret_cast(server); ovms::ServerSettingsImpl* serverSettings = reinterpret_cast(server_settings); ovms::ModelsSettingsImpl* modelsSettings = reinterpret_cast(models_settings); - auto res = srv->start(serverSettings, modelsSettings); + auto res = srv->startFromSettings(serverSettings, modelsSettings); if (res.ok()) { std::atexit(server_atexit_handler); return nullptr; diff --git a/src/cli_parser.cpp b/src/cli_parser.cpp index b1dfd7a03d..80e2aff7ce 100644 --- a/src/cli_parser.cpp +++ b/src/cli_parser.cpp @@ -52,7 +52,8 @@ std::string getConfigPath(const std::string& configPath) { return configPath; } -void CLIParser::parse(int argc, char** argv) { +std::variant> CLIParser::parse(int argc, char** argv) { + std::stringstream ss; try { options = std::make_unique(argv[0], "OpenVINO Model Server"); auto configOptions = std::make_unique("ovms --model_name --add_to_config --model_repository_path \n ovms --model_path --model_name --add_to_config \n ovms --remove_from_config --model_name ", "config management commands:"); @@ -353,78 +354,78 @@ void CLIParser::parse(int argc, char** argv) { break; } case UNKNOWN_GRAPH: { - std::cerr << "error parsing options - --task parameter unsupported value: " + result->operator[]("task").as(); - exit(OVMS_EX_USAGE); + ss << "error parsing options - --task parameter unsupported value: " + result->operator[]("task").as(); + return std::make_pair(OVMS_EX_USAGE, ss.str()); } } } else { - std::cerr << "error parsing options - --task parameter wasn't passed"; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --task parameter wasn't passed"; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (unmatchedOptions.size()) { - std::cerr << "task: " << enumToString(task) << " - error parsing options - unmatched arguments : "; + ss << "task: " << enumToString(task) << " - error parsing options - unmatched arguments : "; for (auto& argument : unmatchedOptions) { - std::cerr << argument << ", "; + ss << argument << ", "; } - std::cerr << std::endl; - exit(OVMS_EX_USAGE); + ss << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } } else if (result->unmatched().size()){ - std::cerr << "error parsing options - unmatched arguments: "; + ss << "error parsing options - unmatched arguments: "; for (auto& argument : result->unmatched()) { - std::cerr << argument << ", "; + ss << argument << ", "; } - std::cerr << std::endl; - exit(OVMS_EX_USAGE); + ss << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (isHFPullOrPullAndStart(this->result) && result->count("list_models")) { - std::cerr << "error parsing options - --list_models cannot be used with --pull or --task" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --list_models cannot be used with --pull or --task" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (isHFPullOrPullAndStart(this->result) && result->count("remove_from_config")) { - std::cerr << "error parsing options - --remove_from_config cannot be used with --pull or --task" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --remove_from_config cannot be used with --pull or --task" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (isHFPullOrPullAndStart(this->result) && result->count("add_to_config")) { - std::cerr << "error parsing options - --add_to_config cannot be used with --pull or --task" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --add_to_config cannot be used with --pull or --task" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (result->count("add_to_config") && result->count("list_models")) { - std::cerr << "error parsing options - --list_models cannot be used with --add_to_config" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --list_models cannot be used with --add_to_config" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (result->count("remove_from_config") && result->count("list_models")) { - std::cerr << "error parsing options - --list_models cannot be used with --remove_from_config" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --list_models cannot be used with --remove_from_config" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (result->count("add_to_config") && result->count("model_repository_path") && result->count("model_path")) { - std::cerr << "error parsing options - --model_repository_path cannot be used with --model_path" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --model_repository_path cannot be used with --model_path" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (result->count("remove_from_config") && result->count("model_repository_path")) { - std::cerr << "error parsing options - --model_repository_path cannot be used with --remove_from_config" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --model_repository_path cannot be used with --remove_from_config" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } if (result->count("remove_from_config") && result->count("model_path")) { - std::cerr << "error parsing options - --model_path cannot be used with --remove_from_config" << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options - --model_path cannot be used with --remove_from_config" << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } #pragma warning(push) #pragma warning(disable : 4129) if (result->count("version")) { std::string project_name(PROJECT_NAME); std::string project_version(PROJECT_VERSION); - std::cout << project_name + " " + project_version << std::endl; - std::cout << "OpenVINO backend " << OPENVINO_NAME << std::endl; - std::cout << "Bazel build flags: " << BAZEL_BUILD_FLAGS << std::endl; + ss << project_name + " " + project_version << std::endl; + ss << "OpenVINO backend " << OPENVINO_NAME << std::endl; + ss << "Bazel build flags: " << BAZEL_BUILD_FLAGS << std::endl; #pragma warning(pop) - exit(OVMS_EX_OK); + return std::make_pair(OVMS_EX_OK, ss.str()); } if (result->count("help") || result->arguments().size() == 0) { - std::cout << options->help({"", "multi model", "single model", "pull hf model"}) << std::endl; - std::cout << configOptions->help({CONFIG_MANAGEMENT_HELP_GROUP}) << std::endl; + ss << options->help({"", "multi model", "single model", "pull hf model"}) << std::endl; + ss << configOptions->help({CONFIG_MANAGEMENT_HELP_GROUP}) << std::endl; GraphCLIParser parser1; RerankGraphCLIParser parser2; EmbeddingsGraphCLIParser parser3; @@ -435,11 +436,13 @@ void CLIParser::parse(int argc, char** argv) { parser2.printHelp(); parser3.printHelp(); imageGenParser.printHelp(); - exit(OVMS_EX_OK); + return std::make_pair(OVMS_EX_OK, ss.str()); } + + return true; } catch (const std::exception& e) { - std::cerr << "error parsing options: " << e.what() << std::endl; - exit(OVMS_EX_USAGE); + ss << "error parsing options: " << e.what() << std::endl; + return std::make_pair(OVMS_EX_USAGE, ss.str()); } } @@ -532,8 +535,7 @@ void CLIParser::prepareServer(ServerSettingsImpl& serverSettings) { } file.close(); } else { - std::cerr << "Error reading API key file: Unable to open file " << apiKeyFile << std::endl; - exit(OVMS_EX_USAGE); + throw std::filesystem::filesystem_error("Error reading API key file: Unable to open file ", apiKeyFile, std::error_code(2, std::generic_category())); } } else { const char* envApiKey = std::getenv(API_KEY_ENV_VAR); diff --git a/src/cli_parser.hpp b/src/cli_parser.hpp index 50fbc3a254..f7a1dd0df9 100644 --- a/src/cli_parser.hpp +++ b/src/cli_parser.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -40,7 +41,7 @@ class CLIParser { public: CLIParser() = default; - void parse(int argc, char** argv); + std::variant> parse(int argc, char** argv); void prepare(ServerSettingsImpl*, ModelsSettingsImpl*); protected: diff --git a/src/config.cpp b/src/config.cpp index ea80fb7f1e..18a72f8b8b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -49,11 +50,21 @@ const size_t DEFAULT_GRPC_MEMORY_QUOTA = (size_t)2 * 1024 * 1024 * 1024; // 2GB const uint64_t MAX_REST_WORKERS = 10'000; Config& Config::parse(int argc, char** argv) { - ovms::CLIParser p; + ovms::CLIParser parser; ovms::ServerSettingsImpl serverSettings; ovms::ModelsSettingsImpl modelsSettings; - p.parse(argc, argv); - p.prepare(&serverSettings, &modelsSettings); + auto successOrExit = parser.parse(argc, argv); + // Check for error in parsing + if (std::holds_alternative>(successOrExit)) { + auto printAndExit = std::get>(successOrExit); + if (printAndExit.first > 0) { + std::cerr << printAndExit.second; + } else { + std::cout << printAndExit.second; + } + exit(printAndExit.first); + } + parser.prepare(&serverSettings, &modelsSettings); if (!this->parse(&serverSettings, &modelsSettings)) exit(OVMS_EX_USAGE); return *this; diff --git a/src/main.cpp b/src/main.cpp index 54dea97e19..cd3d48baf2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,10 @@ int main(int argc, char** argv) { return server.start(argc, argv); } #elif _WIN32 +#pragma warning(push) +#pragma warning(disable : 6553) #include "main_windows.hpp" +#pragma warning(pop) int main(int argc, char** argv) { return ovms_service::main_windows(argc, argv); diff --git a/src/main_windows.cpp b/src/main_windows.cpp index 9ba98f16e7..511e186a1e 100644 --- a/src/main_windows.cpp +++ b/src/main_windows.cpp @@ -32,9 +32,10 @@ #include #include +#include "main_windows.hpp" #include "module_names.hpp" +#include "ovms_exit_codes.hpp" #include "server.hpp" -#include "main_windows.hpp" namespace ovms_service { std::string OvmsWindowsServiceManager::getCurrentTimeString() { @@ -47,7 +48,6 @@ std::string OvmsWindowsServiceManager::getCurrentTimeString() { } #define DEBUG_LOG_ENABLE 0 -// TODO: Implement windows logging mechanism with events static std::ofstream logFile("C:\\temp\\ovms.log", std::ios::app); #define DEBUG_LOG(msg) \ { \ @@ -61,11 +61,14 @@ static std::ofstream logFile("C:\\temp\\ovms.log", std::ios::app); using ovms::Server; -OvmsWindowsServiceManager manager; +OvmsWindowsServiceManager& OvmsWindowsServiceManager::instance() { + static OvmsWindowsServiceManager global; + return global; +} // Need this original function pointer type expected by the Windows Service API (LPSERVICE_MAIN_FUNCTIONA), void WINAPI WinServiceMain(DWORD argc, LPTSTR* argv) { - manager.serviceMain(argc, argv); + OvmsWindowsServiceManager::instance().serviceMain(argc, argv); } std::string wstringToString(const std::wstring& wstr) { @@ -106,8 +109,8 @@ inline std::wstring stringToWstring(const std::string& str, UINT codePage = CP_T int main_windows(int argc, char** argv) { DEBUG_LOG("Windows Main - Entry"); - manager.ovmsParams.argc = argc; - manager.ovmsParams.argv = argv; + OvmsWindowsServiceManager::instance().ovmsParams.argc = argc; + OvmsWindowsServiceManager::instance().ovmsParams.argv = argv; OvmsWindowsServiceManager::logParameters(argc, argv, "OVMS Main Argument"); // Install service with ovms.exe @@ -167,13 +170,17 @@ OvmsWindowsServiceManager::~OvmsWindowsServiceManager() { struct WinHandleDeleter { typedef HANDLE pointer; void operator()(HANDLE h) { - DEBUG_LOG("WinHandleDeleter: closing handle"); + std::stringstream ss2; + ss2 << "WinHandleDeleter: closing handle: " << h; + DEBUG_LOG(ss2.str()); if (h != NULL && h != INVALID_HANDLE_VALUE) { CloseHandle(h); } } }; +// Arguments for this function are the arguments from sc start ovms +// When no arguments are passed we use those from sc create ovms - during install service, and overwrite the parameters void WINAPI OvmsWindowsServiceManager::serviceMain(DWORD argc, LPTSTR* argv) { DEBUG_LOG("ServiceMain: Entry"); @@ -187,15 +194,37 @@ void WINAPI OvmsWindowsServiceManager::serviceMain(DWORD argc, LPTSTR* argv) { this->setServiceStartStatus(); DEBUG_LOG("ServiceMain: Performing Service Start Operations"); - OvmsWindowsServiceManager::logParameters(argc, argv, "ServiceMain Argument"); - + // argc = 1 equals ovms.exe if (argv && argc > 1) { DEBUG_LOG("ServiceMain: Setting new parameters for service after service start."); - manager.ovmsParams.argc = argc; - manager.ovmsParams.argv = argv; + OvmsWindowsServiceManager::instance().ovmsParams.argc = argc; + OvmsWindowsServiceManager::instance().ovmsParams.argv = argv; + } + OvmsWindowsServiceManager::logParameters(argc, argv, "ServiceMain Argument"); + + // Parse arguments before server start + auto paramsOrExit = ovms::Server::parseArgs(OvmsWindowsServiceManager::instance().ovmsParams.argc, OvmsWindowsServiceManager::instance().ovmsParams.argv); + // Check for error in parsing + if (std::holds_alternative>(paramsOrExit)) { + auto printAndExit = std::get>(paramsOrExit); + // Check retcode other than success + if (printAndExit.first > 0) { + DEBUG_LOG("ServiceMain: Server::parseArgs returned error"); + serviceReportEventWithExitCode("ovms::Server::parseArgs", printAndExit.second, printAndExit.first); + this->setServiceStopStatusWithExitCode(printAndExit.first); + } else { + // Check retcode 0 but service not started [--help, --version] arguments + DEBUG_LOG("ServiceMain: Server::parseArgs returned success, no valid parameters to start the service provided."); + serviceReportEventWithExitCode("ovms::Server::parseArgs", printAndExit.second, printAndExit.first); + this->setServiceStopStatusWithExitCode(printAndExit.first); + } + + return; + } else { + OvmsWindowsServiceManager::instance().parsedParameters = std::get>(paramsOrExit); } - std::unique_ptr mainThread(CreateThread(NULL, 0, OvmsWindowsServiceManager::serviceWorkerThread, &manager.ovmsParams, 0, NULL)); + std::unique_ptr mainThread(CreateThread(NULL, 0, OvmsWindowsServiceManager::serviceWorkerThread, &OvmsWindowsServiceManager::instance().parsedParameters, 0, NULL)); if (mainThread.get() == NULL || mainThread.get() == INVALID_HANDLE_VALUE) { // Handle error DEBUG_LOG("ServiceMain: mainThread == NULL || mainThread == INVALID_HANDLE_VALUE"); @@ -212,9 +241,6 @@ void WINAPI OvmsWindowsServiceManager::serviceMain(DWORD argc, LPTSTR* argv) { return; } - // Tell the service controller we are started - this->setServiceRunningStatus(); - DEBUG_LOG("ServiceMain: Waiting for Worker Thread to complete"); WaitForSingleObject(mainThread.get(), INFINITE); @@ -229,7 +255,9 @@ void WINAPI OvmsWindowsServiceManager::serviceMain(DWORD argc, LPTSTR* argv) { struct WinSCHandleDeleter { typedef SC_HANDLE pointer; void operator()(SC_HANDLE h) { - DEBUG_LOG("WinSCHandleDeleter: closing handle"); + std::stringstream ss2; + ss2 << "WinSCHandleDeleter: closing handle: " << h; + DEBUG_LOG(ss2.str()); if (h != NULL && h != INVALID_HANDLE_VALUE) { CloseServiceHandle(h); } @@ -240,7 +268,6 @@ struct WinSCHandleDeleter { // Use sc create ... instead // Cannot be used as it does not create the registry entry for the service // Registry entry required to add ovms\python to PATH -// TODO: add serviceReportEvent to the standard main path void OvmsWindowsServiceManager::serviceInstall() { TCHAR szUnquotedPath[MAX_PATH]; DEBUG_LOG("Installing Openvino Model Server service"); @@ -342,23 +369,29 @@ bool OvmsWindowsServiceManager::serviceSetDescription() { } void OvmsWindowsServiceManager::logParameters(DWORD argc, LPTSTR* argv, const std::string& logText) { - for (int i = 0; i < (int)manager.ovmsParams.argc; ++i) { + for (int i = 0; i < (int)OvmsWindowsServiceManager::instance().ovmsParams.argc; ++i) { std::stringstream ss2; - ss2 << logText << " " << i << ": " << manager.ovmsParams.argv[i]; - DEBUG_LOG(ss2.rdbuf()); + ss2 << logText << " " << i << ": " << OvmsWindowsServiceManager::instance().ovmsParams.argv[i]; + DEBUG_LOG(ss2.str()); } } struct WinESHandleDeleter { typedef HANDLE pointer; void operator()(HANDLE h) { - DEBUG_LOG("WinESHandleDeleter: closing handle"); + std::stringstream ss2; + ss2 << "WinESHandleDeleter: closing handle: " << h; + DEBUG_LOG(ss2.str()); if (h != NULL && h != INVALID_HANDLE_VALUE) { DeregisterEventSource(h); } } }; +void OvmsWindowsServiceManager::serviceReportEvent(const std::string& szFunction) { + serviceReportEvent(const_cast(szFunction.c_str())); +} + void OvmsWindowsServiceManager::serviceReportEvent(LPSTR szFunction) { LPCTSTR lpszStrings[2]; TCHAR Buffer[200]; @@ -385,6 +418,64 @@ void OvmsWindowsServiceManager::serviceReportEvent(LPSTR szFunction) { } } +void OvmsWindowsServiceManager::serviceReportEventWithExitCode(const std::string& szFunction, const std::string& message, const int& exitCode) { + serviceReportEventWithExitCode(const_cast(szFunction.c_str()), message, exitCode); +} + +void OvmsWindowsServiceManager::serviceReportEventWithExitCode(LPSTR szFunction, const std::string& message, const int& exitCode) { + // TODO: Write message file for ovms: https://learn.microsoft.com/en-us/windows/win32/eventlog/sample-message-text-file + LPCTSTR lpszStrings[2]; + TCHAR Buffer[200]; + std::unique_ptr hEventSource(RegisterEventSource(NULL, OvmsWindowsServiceManager::serviceName)); + if (hEventSource.get() != NULL) { + StringCchPrintf(Buffer, 200, TEXT("%s failed with %d error: %s"), szFunction, exitCode, message.c_str()); + lpszStrings[0] = OvmsWindowsServiceManager::serviceName; + lpszStrings[1] = Buffer; + ReportEvent(hEventSource.get(), // event log handle + EVENTLOG_ERROR_TYPE, // event type + exitCode, // event category + 0, // event identifier + NULL, // no security identifier + 2, // size of lpszStrings array + 0, // no binary data + lpszStrings, // array of strings + NULL); // no binary data + + } else { + DEBUG_LOG("RegisterEventSource failed"); + DEBUG_LOG(std::system_category().message(GetLastError())); + } +} + +void OvmsWindowsServiceManager::serviceReportEventSuccess(const std::string& szFunction, const std::string& message) { + serviceReportEventSuccess(const_cast(szFunction.c_str()), message); +} + +void OvmsWindowsServiceManager::serviceReportEventSuccess(LPSTR szFunction, const std::string& message) { + // TODO: Write message file for ovms: https://learn.microsoft.com/en-us/windows/win32/eventlog/sample-message-text-file + LPCTSTR lpszStrings[2]; + TCHAR Buffer[200]; + std::unique_ptr hEventSource(RegisterEventSource(NULL, OvmsWindowsServiceManager::serviceName)); + if (hEventSource.get() != NULL) { + StringCchPrintf(Buffer, 200, TEXT("%s success. Status: %s"), szFunction, message.c_str()); + lpszStrings[0] = OvmsWindowsServiceManager::serviceName; + lpszStrings[1] = Buffer; + ReportEvent(hEventSource.get(), // event log handle + EVENTLOG_SUCCESS, // event type + 0, // event category + 0, // event identifier + NULL, // no security identifier + 2, // size of lpszStrings array + 0, // no binary data + lpszStrings, // array of strings + NULL); // no binary data + + } else { + DEBUG_LOG("RegisterEventSource failed"); + DEBUG_LOG(std::system_category().message(GetLastError())); + } +} + void WINAPI OvmsWindowsServiceManager::serviceCtrlHandler(DWORD CtrlCode) { DEBUG_LOG("serviceCtrlHandler: Entry"); @@ -417,31 +508,30 @@ DWORD WINAPI OvmsWindowsServiceManager::serviceWorkerThread(LPVOID lpParam) { std::unique_ptr ovmsService = std::make_unique(); ovmsService->error = 0; ovmsService->started = false; + ovmsService->setup = false; // Start OVMS and check for stop while (WaitForSingleObject(serviceStopEvent->handle, 0) != WAIT_OBJECT_0) { - if (!ovmsService->started) { - ConsoleParameters* params = (ConsoleParameters*)lpParam; - if (params && params->argc > 1) { - DEBUG_LOG("serviceWorkerThread: Starting ovms from start parameters."); - OvmsWindowsServiceManager::logParameters(params->argc, params->argv, "OVMS Main Argument"); - ovmsService->SetUp(params->argc, params->argv); - DEBUG_LOG("serviceWorkerThread: Ovms starting. Waiting for SERVABLE_MANAGER_MODULE to be live ...") - } else { - DEBUG_LOG("serviceWorkerThread: Error - No parameters passed to ovms service."); - break; - } + // Already started + if (!ovmsService->setup) { + std::pair* params = (std::pair*)lpParam; + DEBUG_LOG("serviceWorkerThread: Starting ovms from parameters."); + ovmsService->SetUp(params); } // Check thread not exited - if (ovmsService->started && !ovmsService->isRunning()) { + if (!ovmsService->isRunning()) { DEBUG_LOG("serviceWorkerThread: Server thread is not running.") break; } - ovmsService->checkModulesStarted(); + if (!ovmsService->started && ovmsService->checkModulesStarted()) { + // Tell the service controller we are started + OvmsWindowsServiceManager::setServiceRunningStatus(); + ovmsService->started = true; + } } - if (ovmsService->started) { + if (ovmsService->started || ovmsService->setup) { ovmsService->TearDown(); DEBUG_LOG("serviceWorkerThread: Stopping ovms service."); } else { @@ -449,10 +539,9 @@ DWORD WINAPI OvmsWindowsServiceManager::serviceWorkerThread(LPVOID lpParam) { } if (ovmsService->error) { - // TODO: Catch parsing errors from OVMS - currently we do not have this info DEBUG_LOG("serviceWorkerThread: Ovms start returned error."); DEBUG_LOG(ovmsService->error); - serviceReportEvent("Ovms start returned error."); + serviceReportEventWithExitCode("serviceWorkerThread", "Ovms exited with error.", ovmsService->error); return ovmsService->error; } @@ -488,17 +577,126 @@ void OvmsWindowsServiceManager::setServiceStopStatusWithError() { DEBUG_LOG("ServiceMain: SetServiceStatus stop with error"); } -void OvmsWindowsServiceManager::setServiceRunningStatus() { - serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; - serviceStatus.dwCurrentState = SERVICE_RUNNING; - serviceStatus.dwWin32ExitCode = 0; - serviceStatus.dwCheckPoint = 0; +void OvmsWindowsServiceManager::setServiceStopStatusWithExitCode(const int& exitCode) { + DWORD exitToError = static_cast(exitCode); + // Map known exit code to known win errors for proper service status report on error + // Check https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- for details + switch (exitCode) { + case OVMS_EX_USAGE: { + exitToError = ERROR_BAD_ARGUMENTS; + break; + } + case OVMS_EX_OK: { + exitToError = ERROR_BAD_ARGUMENTS; + break; + } + case OVMS_EX_FAILURE: { + exitToError = ERROR_INVALID_FUNCTION; + break; + } + case OVMS_EX_WARNING: { + exitToError = ERROR_INVALID_FUNCTION; + break; + } + default: + exitToError = ERROR_INVALID_FUNCTION; + } + serviceStatus.dwControlsAccepted = 0; + serviceStatus.dwCurrentState = SERVICE_STOPPED; + serviceStatus.dwWin32ExitCode = exitToError; + serviceStatus.dwCheckPoint = 1; if (SetServiceStatus(this->statusHandle->handle, &serviceStatus) == FALSE) { DEBUG_LOG("ServiceMain: SetServiceStatus returned error"); serviceReportEvent("SetServiceStatus"); } - DEBUG_LOG("ServiceMain: SetServiceStatus running"); + DEBUG_LOG("ServiceMain: SetServiceStatus stop with exit code"); +} + +void OvmsWindowsServiceManager::setServiceRunningStatus() { + OvmsWindowsServiceManager::serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + OvmsWindowsServiceManager::serviceStatus.dwCurrentState = SERVICE_RUNNING; + OvmsWindowsServiceManager::serviceStatus.dwWin32ExitCode = 0; + OvmsWindowsServiceManager::serviceStatus.dwCheckPoint = 0; + + if (SetServiceStatus(OvmsWindowsServiceManager::statusHandle->handle, &OvmsWindowsServiceManager::serviceStatus) == FALSE) { + DEBUG_LOG("OvmsWindowsServiceManager: SetServiceStatus returned error"); + serviceReportEvent("SetServiceStatus"); + } + DEBUG_LOG("OvmsWindowsServiceManager: SetServiceStatus running"); +} + +std::string OvmsWindowsServiceManager::getRegValue(const winreg::RegKey& key, const std::wstring& name, const DWORD& regType) { + std::string retStr = ""; + switch (regType) { + case REG_SZ: { + if (auto testVal = key.TryGetStringValue(name)) { + retStr = wstringToString(testVal.GetValue()); + } + + return retStr; + } + case REG_EXPAND_SZ: { + if (auto testVal = key.TryGetExpandStringValue(name)) { + retStr = wstringToString(testVal.GetValue()); + } + + return retStr; + } + case REG_MULTI_SZ: { + if (auto testVal = key.TryGetMultiStringValue(name)) { + for (auto elem : testVal.GetValue()) { + retStr += wstringToString(elem) + ","; + } + } + + return retStr; + } + case REG_DWORD: { + if (auto testVal = key.TryGetDwordValue(name)) { + retStr = std::to_string(testVal.GetValue()); + } + + return retStr; + } + case REG_QWORD: { + if (auto testVal = key.TryGetQwordValue(name)) { + retStr = std::to_string(testVal.GetValue()); + } + + return retStr; + } + case REG_BINARY: { + if (auto testVal = key.TryGetBinaryValue(name)) { + for (auto elem : testVal.GetValue()) { + retStr += std::to_string(elem) + ","; + } + } + + return retStr; + } + default: + return retStr; + } + return retStr; +} + +void OvmsWindowsServiceManager::logRegistryEntry(HKEY keyType, const std::wstring& keyPath) { + DEBUG_LOG(wstringToString(keyPath)); + winreg::RegKey key{keyType, keyPath}; + std::vector subKeyNames = key.EnumSubKeys(); + DEBUG_LOG("SubKeys:"); + for (const auto& s : subKeyNames) { + DEBUG_LOG(wstringToString(s)); + } + std::vector> values = key.EnumValues(); + DEBUG_LOG("Values:"); + for (const auto& [valueName, valueType] : values) { + std::stringstream ss2; + // Try string reg value + ss2 << " [" << wstringToString(valueName) << "](" << wstringToString(winreg::RegKey::RegTypeToString(valueType)) << "): " << getRegValue(key, valueName, valueType); + DEBUG_LOG(ss2.rdbuf()); + } } // Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ovms @@ -506,29 +704,16 @@ void OvmsWindowsServiceManager::setPythonPathRegistry() { try { const std::wstring ovmsServiceKey = L"SYSTEM\\CurrentControlSet\\Services\\ovms"; winreg::RegKey key{HKEY_LOCAL_MACHINE, ovmsServiceKey}; - DEBUG_LOG(wstringToString(ovmsServiceKey)); - std::vector subKeyNames = key.EnumSubKeys(); - DEBUG_LOG("SubKeys:"); - for (const auto& s : subKeyNames) { - DEBUG_LOG(wstringToString(s)); - } - /*std::vector> values = key.EnumValues(); - DEBUG_LOG("Values:"); - for (const auto& [valueName, valueType] : values) { - //std::stringstream ss2; - // TODO: ss2 << " [" << wstringToString(valueName) << "](" << wstringToString(winreg::RegKey::RegTypeToString(valueType)) << ")"; - //DEBUG_LOG(ss2.rdbuf()); - } */ TCHAR szUnquotedPath[MAX_PATH]; if (!GetModuleFileName(NULL, szUnquotedPath, MAX_PATH)) { DEBUG_LOG("setPythonPathRegistry, GetModuleFileName failed."); return; } - // create PATH=c:\test2\ovms\python;%PATH% std::string ovmsDirectory = std::filesystem::path(szUnquotedPath).parent_path().string(); std::stringstream ss3; + DEBUG_LOG("Adding Service Environment setting:") ss3 << "PATH=" << ovmsDirectory << "\\python;%PATH%"; DEBUG_LOG(ss3.str()); std::vector multiString; @@ -536,6 +721,7 @@ void OvmsWindowsServiceManager::setPythonPathRegistry() { key.Open(HKEY_LOCAL_MACHINE, ovmsServiceKey); key.SetMultiStringValue(L"Environment", multiString); key.Close(); + OvmsWindowsServiceManager::logRegistryEntry(HKEY_LOCAL_MACHINE, ovmsServiceKey); } catch (const std::exception& e) { DEBUG_LOG("setPythonPathRegistry: Add python path variable Failed:"); DEBUG_LOG(e.what()); @@ -577,13 +763,16 @@ void OvmsService::TearDown() { t->join(); server.setShutdownRequest(0); this->started = false; + this->setup = false; + OvmsWindowsServiceManager::serviceReportEventSuccess("[INFO]Modules", "Openvino Model Server is stopped."); } -int OvmsService::SetUp(int argc, char** argv) { +int OvmsService::SetUp(std::pair* parameters) { DEBUG_LOG("OvmsService::SetUp"); - this->started = true; - t.reset(new std::thread([argc, argv, this]() { - this->error = server.start(argc, argv); + this->setup = true; + OvmsWindowsServiceManager::serviceReportEventSuccess("[INFO]Modules", "Openvino Model Server is starting ..."); + t.reset(new std::thread([parameters, this]() { + this->error = server.startServerFromSettings(parameters->first, parameters->second); })); return 0; } @@ -593,7 +782,8 @@ bool OvmsService::isReady() { } bool OvmsService::isRunning() { - return (t && t->joinable()); + // Check if server thread exited + return (t && t->joinable() && server.getShutdownStatus() == 0 && server.getExitStatus() == 0); } bool OvmsService::isLive(const std::string& moduleName) { @@ -601,8 +791,10 @@ bool OvmsService::isLive(const std::string& moduleName) { } bool OvmsService::checkModulesStarted() { - // TODO: Set false for required modules, add logic for start control - timeout? + // TODO: HF_MODEL_PULL_MODULE_LIVE - add logic for pull and start - now we are ready when pull starts in pull and start // Currently we return true on isReady = true; + // Currently we return true on HF_MODEL_PULL_MODULE_LIVE = true; + // Currently we return true on SERVABLES_CONFIG_MANAGER_MODULE_LIVE = true; static bool SERVER_READY = false; static bool PROFILER_MODULE_LIVE = false; static bool GRPC_SERVER_MODULE_LIVE = false; @@ -622,6 +814,7 @@ bool OvmsService::checkModulesStarted() { if (!SERVER_READY && this->isReady()) { DEBUG_LOG("serviceWorkerThread: Ovms service is ready and running."); SERVER_READY = true; + OvmsWindowsServiceManager::serviceReportEventSuccess("[INFO]Modules", "Openvino Model Server is ready."); } if (!PROFILER_MODULE_LIVE && this->isLive(ovms::PROFILER_MODULE_NAME)) { DEBUG_LOG("serviceWorkerThread: Ovms service PROFILER_MODULE is live."); @@ -630,10 +823,12 @@ bool OvmsService::checkModulesStarted() { if (!GRPC_SERVER_MODULE_LIVE && this->isLive(ovms::GRPC_SERVER_MODULE_NAME)) { DEBUG_LOG("serviceWorkerThread: Ovms service GRPC_SERVER_MODULE is live."); GRPC_SERVER_MODULE_LIVE = true; + OvmsWindowsServiceManager::serviceReportEventSuccess("[INFO]Modules", "Openvino Model Server GRPC module is live."); } if (!HTTP_SERVER_MODULE_LIVE && this->isLive(ovms::HTTP_SERVER_MODULE_NAME)) { DEBUG_LOG("serviceWorkerThread: Ovms service HTTP_SERVER_MODULE is live."); HTTP_SERVER_MODULE_LIVE = true; + OvmsWindowsServiceManager::serviceReportEventSuccess("[INFO]Modules", "Openvino Model Server HTTP module is live."); } if (!METRICS_MODULE_LIVE && this->isLive(ovms::METRICS_MODULE_NAME)) { DEBUG_LOG("serviceWorkerThread: Ovms service METRICS_MODULE is live."); @@ -650,10 +845,12 @@ bool OvmsService::checkModulesStarted() { if (!HF_MODEL_PULL_MODULE_LIVE && this->isLive(ovms::HF_MODEL_PULL_MODULE_NAME)) { DEBUG_LOG("serviceWorkerThread: Ovms service HF_MODEL_PULL_MODULE is live."); HF_MODEL_PULL_MODULE_LIVE = true; + return true; } if (!SERVABLES_CONFIG_MANAGER_MODULE_LIVE && this->isLive(ovms::SERVABLES_CONFIG_MANAGER_MODULE_NAME)) { DEBUG_LOG("serviceWorkerThread: Ovms service SERVABLES_CONFIG_MANAGER_MODULE is live."); SERVABLES_CONFIG_MANAGER_MODULE_LIVE = true; + return true; } return SERVER_READY; @@ -663,7 +860,9 @@ WinServiceStatusWrapper::WinServiceStatusWrapper() { handle = NULL; } WinServiceStatusWrapper::~WinServiceStatusWrapper() { - DEBUG_LOG("WinServiceStatusWrapper: closing handle"); + std::stringstream ss2; + ss2 << "WinServiceStatusWrapper: closing handle: " << handle; + DEBUG_LOG(ss2.str()); if (handle != NULL && handle != INVALID_HANDLE_VALUE) { CloseHandle(handle); } @@ -673,7 +872,9 @@ WinServiceEventWrapper::WinServiceEventWrapper() { handle = INVALID_HANDLE_VALUE; } WinServiceEventWrapper::~WinServiceEventWrapper() { - DEBUG_LOG("WinServiceEventWrapper: closing handle"); + std::stringstream ss2; + ss2 << "WinServiceEventWrapper: closing handle: " << handle; + DEBUG_LOG(ss2.str()); if (handle != NULL && handle != INVALID_HANDLE_VALUE) { CloseHandle(handle); } diff --git a/src/main_windows.hpp b/src/main_windows.hpp index 8cf2741772..3d87a83094 100644 --- a/src/main_windows.hpp +++ b/src/main_windows.hpp @@ -22,7 +22,12 @@ #include #include #include +#pragma warning(push) +#pragma warning(disable : 6553) +#include +#pragma warning(pop) +#include "capi_frontend/server_settings.hpp" #include "server.hpp" namespace ovms_service { using ovms::Server; @@ -41,9 +46,11 @@ class OvmsService { public: bool started; + bool setup; int error; void TearDown(); - int SetUp(int argc, char** argv); + + int SetUp(std::pair* parameters); bool isReady(); bool isRunning(); bool isLive(const std::string& moduleName); @@ -69,14 +76,22 @@ class OvmsWindowsServiceManager { // Members ConsoleParameters ovmsParams; + std::pair parsedParameters; static LPSTR serviceName; static LPSTR serviceDisplayName; static LPSTR serviceDesc; // Methods + static OvmsWindowsServiceManager& instance(); static std::string getCurrentTimeString(); static void logParameters(DWORD argc, LPTSTR* argv, const std::string& logText); static void serviceReportEvent(LPSTR szFunction); + static void serviceReportEvent(const std::string& szFunction); + static void serviceReportEventWithExitCode(const std::string& szFunction, const std::string& message, const int& exitCode); + static void serviceReportEventWithExitCode(LPSTR szFunction, const std::string& message, const int& exitCode); + static void serviceReportEventSuccess(const std::string& szFunction, const std::string& message); + static void serviceReportEventSuccess(LPSTR szFunction, const std::string& message); + void WINAPI serviceMain(DWORD argc, LPTSTR* argv); // Registry manipulation @@ -101,7 +116,12 @@ class OvmsWindowsServiceManager { void setServiceStartStatus(); void setServiceStopStatusWithSuccess(); void setServiceStopStatusWithError(); - void setServiceRunningStatus(); + void setServiceStopStatusWithExitCode(const int& exitCode); + static void setServiceRunningStatus(); + + // Registry manipulation + static std::string getRegValue(const winreg::RegKey& key, const std::wstring& name, const DWORD& type); + static void logRegistryEntry(HKEY keyType, const std::wstring& keyPath); }; } // namespace ovms_service diff --git a/src/ovms_exit_codes.hpp b/src/ovms_exit_codes.hpp index a2d6c55e73..3ca5d7cc25 100644 --- a/src/ovms_exit_codes.hpp +++ b/src/ovms_exit_codes.hpp @@ -18,7 +18,7 @@ #ifdef __linux__ #include #elif _WIN32 -#include +#include #endif namespace ovms { diff --git a/src/server.cpp b/src/server.cpp index 6343e658e6..c28078292a 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -76,9 +76,6 @@ using grpc::ServerBuilder; namespace ovms { -namespace { -volatile sig_atomic_t shutdown_request = 0; -} Server& Server::instance() { static Server global; @@ -130,15 +127,15 @@ static void logConfig(const Config& config) { } static void onInterrupt(int status) { - shutdown_request = 1; + Server::instance().setShutdownRequest(1); } static void onTerminate(int status) { - shutdown_request = 1; + Server::instance().setShutdownRequest(1); } static void onIllegal(int status) { - shutdown_request = 2; + Server::instance().setShutdownRequest(2); } #ifdef __linux__ @@ -233,7 +230,53 @@ const Module* Server::getModule(const std::string& name) const { } void Server::setShutdownRequest(int i) { - shutdown_request = i; + std::unique_lock lock{Server::shutdownMtx, std::defer_lock}; + int counter = 11; + // 2 seconds to try to lock exit mutex + while (counter-- && !lock.try_lock()) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + if (counter) { + shutdown_request = i; + } else { + SPDLOG_ERROR("Server shutdown mutex lock failed."); + } +} + +int Server::getShutdownStatus() { + std::unique_lock lock{Server::shutdownMtx, std::defer_lock}; + auto locked = lock.try_lock(); + // Wait in windows thread until we can get the lock and check if ovms exited + if (!locked) { + return 0; + } + + return shutdown_request; +} + +int Server::getExitStatus() { + std::unique_lock lock{Server::exitMtx, std::defer_lock}; + auto locked = lock.try_lock(); + // Wait in windows thread until we can get the lock and check if ovms exited + if (!locked) { + return 0; + } + + return ovms_exited; +} + +void Server::setExitStatus(int i) { + std::unique_lock lock{Server::exitMtx, std::defer_lock}; + int counter = 11; + // 2 seconds to try to lock exit mutex + while (counter-- && !lock.try_lock()) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + if (counter) { + ovms_exited = i; + } else { + SPDLOG_ERROR("Server shutdown mutex lock failed."); + } } Server::~Server() { @@ -411,41 +454,72 @@ static int statusToExitCode(const Status& status) { return OVMS_EX_FAILURE; } -// OVMS Start -int Server::start(int argc, char** argv) { - installSignalHandlers(); - int result = OVMS_EX_OK; - +std::variant, std::pair> Server::parseArgs(int argc, char** argv) { try { CLIParser parser; ServerSettingsImpl serverSettings; ModelsSettingsImpl modelsSettings; - parser.parse(argc, argv); + auto successOrExit = parser.parse(argc, argv); + // Check for error in parsing + if (!std::holds_alternative(successOrExit)) { + auto printAndExit = std::get>(successOrExit); + return printAndExit; + } parser.prepare(&serverSettings, &modelsSettings); + return std::make_pair(serverSettings, modelsSettings); + } catch (const std::exception& e) { + return std::make_pair(OVMS_EX_USAGE, e.what()); + } +} + +int Server::startServerFromSettings(ServerSettingsImpl& serverSettings, ModelsSettingsImpl& modelsSettings) { + installSignalHandlers(); + int result = OVMS_EX_OK; + Server::setExitStatus(0); - Status ret = start(&serverSettings, &modelsSettings); + try { + Status ret = startFromSettings(&serverSettings, &modelsSettings); ModulesShutdownGuard shutdownGuard(*this); if (!ret.ok()) { return statusToExitCode(ret); } - while (!shutdown_request && + while (!getShutdownStatus() && (serverSettings.serverMode == HF_PULL_AND_START_MODE || serverSettings.serverMode == SERVING_MODELS_MODE)) { std::this_thread::sleep_for(std::chrono::milliseconds(200)); } - if (shutdown_request == 2) { + if (getShutdownStatus() == 2) { SPDLOG_ERROR("Illegal operation. OVMS started on unsupported device"); } } catch (const std::exception& e) { SPDLOG_ERROR("Exception; {}", e.what()); result = OVMS_EX_FAILURE; + Server::setExitStatus(1); return result; } + Server::setExitStatus(1); return EXIT_SUCCESS; } +// OVMS Start +int Server::start(int argc, char** argv) { + auto paramsOrExit = parseArgs(argc, argv); + // Check for error in parsing + if (std::holds_alternative>(paramsOrExit)) { + auto printAndExit = std::get>(paramsOrExit); + if (printAndExit.first > 0) { + std::cerr << printAndExit.second; + } else { + std::cout << printAndExit.second; + } + exit(printAndExit.first); + } + std::pair parameters = std::get>(paramsOrExit); + return startServerFromSettings(parameters.first, parameters.second); +} + // C-API Start -Status Server::start(ServerSettingsImpl* serverSettings, ModelsSettingsImpl* modelsSettings) { +Status Server::startFromSettings(ServerSettingsImpl* serverSettings, ModelsSettingsImpl* modelsSettings) { try { std::unique_lock lock{this->startMtx, std::defer_lock}; auto locked = lock.try_lock(); diff --git a/src/server.hpp b/src/server.hpp index 646b0b5948..23306636d3 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -19,19 +19,24 @@ #include #include #include +#include +#include "capi_frontend/server_settings.hpp" #include "module.hpp" #include "module_names.hpp" - +namespace { +volatile sig_atomic_t shutdown_request = 0; +volatile sig_atomic_t ovms_exited = 0; +} // namespace namespace ovms { class Config; class Status; -struct ServerSettingsImpl; -struct ModelsSettingsImpl; class Server { mutable std::shared_mutex modulesMtx; mutable std::mutex startMtx; + mutable std::mutex exitMtx; + mutable std::mutex shutdownMtx; protected: std::unordered_map> modules; @@ -41,13 +46,18 @@ class Server { public: static Server& instance(); int start(int argc, char** argv); - Status start(ServerSettingsImpl*, ModelsSettingsImpl*); + static std::variant, std::pair> parseArgs(int argc, char** argv); + int startServerFromSettings(ServerSettingsImpl& serverSettings, ModelsSettingsImpl& modelsSettings); + Status startFromSettings(ServerSettingsImpl*, ModelsSettingsImpl*); ModuleState getModuleState(const std::string& name) const; const Module* getModule(const std::string& name) const; bool isReady() const; bool isLive(const std::string& moduleName) const; + int getShutdownStatus(); void setShutdownRequest(int i); + int getExitStatus(); + void setExitStatus(int i); virtual ~Server(); Status startModules(ovms::Config& config); void shutdownModules(); diff --git a/src/test/ovmsconfig_test.cpp b/src/test/ovmsconfig_test.cpp index 1807686682..3abcde9690 100644 --- a/src/test/ovmsconfig_test.cpp +++ b/src/test/ovmsconfig_test.cpp @@ -329,7 +329,7 @@ TEST_F(OvmsConfigDeathTest, NegativeListModelsWithoutModelRepositoryPath) { TEST_F(OvmsConfigDeathTest, NegativeInvalidAPIKeyFile) { char* n_argv[] = {"ovms", "--config_path", "/path1", "--api_key_file", "/wrong/dir", "--port", "44"}; int arg_count = 7; - EXPECT_EXIT(ovms::Config::instance().parse(arg_count, n_argv), ::testing::ExitedWithCode(OVMS_EX_USAGE), "Error reading API key file: Unable to open file \"/wrong/dir\""); + EXPECT_THROW(ovms::Config::instance().parse(arg_count, n_argv), std::filesystem::filesystem_error); } TEST_F(OvmsConfigDeathTest, negativeMissingDashes) {