diff --git a/CMakeLists.txt b/CMakeLists.txt index a8f37ca..4f818d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,8 @@ project(BeamMP-Server HOMEPAGE_URL https://beammp.com LANGUAGES CXX C) +set(HTTPLIB_REQUIRE_OPENSSL ON) + include_directories("${PROJECT_SOURCE_DIR}/deps/asio/asio/include") include_directories("${PROJECT_SOURCE_DIR}/deps/rapidjson/include") include_directories("${PROJECT_SOURCE_DIR}/deps/websocketpp") diff --git a/Changelog.md b/Changelog.md index 6774147..7c54dd3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,9 @@ +# v3.0.1 + +- ADDED Backup URLs to UpdateCheck (will fail less often now) +- FIXED a bug where, when run with --working-directory, the Server.log would still be in the original directory +- FIXED a bug which could cause the plugin reload thread to spin at 100% if the reloaded plugin's didn't terminate + # v3.0.0 - CHANGED entire plugin Lua implementation (rewrite) diff --git a/deps/commandline b/deps/commandline index 3d11606..0d3e107 160000 --- a/deps/commandline +++ b/deps/commandline @@ -1 +1 @@ -Subproject commit 3d11606d02b449b8afd40a7132160d2392043eb3 +Subproject commit 0d3e1073c1005270dfad851c1f8c59f4ce29d8c1 diff --git a/deps/cpp-httplib b/deps/cpp-httplib index 301faa0..b324921 160000 --- a/deps/cpp-httplib +++ b/deps/cpp-httplib @@ -1 +1 @@ -Subproject commit 301faa074c4a0fa1dbe470dfb4f77912caa1c57f +Subproject commit b324921c1aeff2976544128e4bb2a0979a4aa595 diff --git a/include/Common.h b/include/Common.h index c61f98b..2cf15b9 100644 --- a/include/Common.h +++ b/include/Common.h @@ -74,10 +74,15 @@ public: static TSettings Settings; + static std::vector GetBackendUrlsInOrder() { + return { + "backend.beammp.com", + "backup1.beammp.com", + "backup2.beammp.com" + }; + } + static std::string GetBackendUrlForAuth() { return "auth.beammp.com"; } - static std::string GetBackendHostname() { return "backend.beammp.com"; } - static std::string GetBackup1Hostname() { return "backup1.beammp.com"; } - static std::string GetBackup2Hostname() { return "backup2.beammp.com"; } static std::string GetBackendUrlForSocketIO() { return "https://backend.beammp.com"; } static void CheckForUpdates(); static std::array VersionStrToInts(const std::string& str); @@ -114,7 +119,7 @@ private: static inline std::mutex mShutdownHandlersMutex {}; static inline std::deque mShutdownHandlers {}; - static inline Version mVersion { 3, 0, 0 }; + static inline Version mVersion { 3, 0, 1 }; }; std::string ThreadName(bool DebugModeOverride = false); diff --git a/include/Http.h b/include/Http.h index 62ded67..3ff2cd5 100644 --- a/include/Http.h +++ b/include/Http.h @@ -25,7 +25,7 @@ constexpr size_t RSA_DEFAULT_KEYLENGTH { 2048 }; namespace Http { std::string GET(const std::string& host, int port, const std::string& target, unsigned int* status = nullptr); -std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr); +std::string POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status = nullptr, const httplib::Headers& headers = {}); namespace Status { std::string ToString(int code); } diff --git a/include/TLuaEngine.h b/include/TLuaEngine.h index 1673c61..9b0a884 100644 --- a/include/TLuaEngine.h +++ b/include/TLuaEngine.h @@ -149,6 +149,7 @@ public: void CancelEventTimers(const std::string& EventName, TLuaStateId StateId); sol::state_view GetStateForPlugin(const fs::path& PluginPath); TLuaStateId GetStateIDForPlugin(const fs::path& PluginPath); + void AddResultToCheck(const std::shared_ptr& Result); static constexpr const char* BeamMPFnNotFoundError = "BEAMMP_FN_NOT_FOUND"; diff --git a/src/Common.cpp b/src/Common.cpp index 4ed5814..a113f74 100644 --- a/src/Common.cpp +++ b/src/Common.cpp @@ -99,25 +99,31 @@ void Application::CheckForUpdates() { Application::SetSubsystemStatus("UpdateCheck", Application::Status::Starting); // checks current version against latest version std::regex VersionRegex { R"(\d+\.\d+\.\d+\n*)" }; - auto Response = Http::GET(GetBackendHostname(), 443, "/v/s"); - bool Matches = std::regex_match(Response, VersionRegex); - if (Matches) { - auto MyVersion = ServerVersion(); - auto RemoteVersion = Version(VersionStrToInts(Response)); - if (IsOutdated(MyVersion, RemoteVersion)) { - std::string RealVersionString = RemoteVersion.AsString(); - beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For more info visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server." + std::string(ANSI_RESET)); + for (const auto& url : GetBackendUrlsInOrder()) { + auto Response = Http::GET(GetBackendUrlsInOrder().at(0), 443, "/v/s"); + bool Matches = std::regex_match(Response, VersionRegex); + if (Matches) { + auto MyVersion = ServerVersion(); + auto RemoteVersion = Version(VersionStrToInts(Response)); + if (IsOutdated(MyVersion, RemoteVersion)) { + std::string RealVersionString = RemoteVersion.AsString(); + beammp_warn(std::string(ANSI_YELLOW_BOLD) + "NEW VERSION OUT! There's a new version (v" + RealVersionString + ") of the BeamMP-Server available! For more info visit https://wiki.beammp.com/en/home/server-maintenance#updating-the-server." + std::string(ANSI_RESET)); + } else { + beammp_info("Server up-to-date!"); + } + Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good); + break; } else { - beammp_info("Server up-to-date!"); + beammp_debug("Failed to fetch version from: " + url); + beammp_trace("got " + Response); + auto Lock = Sentry.CreateExclusiveContext(); + Sentry.SetContext("get-response", { { "response", Response } }); + Sentry.LogError("failed to get server version", _file_basename, _line); + Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad); } - Application::SetSubsystemStatus("UpdateCheck", Application::Status::Good); - } else { - beammp_warn("Unable to fetch version from backend."); - beammp_trace("got " + Response); - auto Lock = Sentry.CreateExclusiveContext(); - Sentry.SetContext("get-response", { { "response", Response } }); - Sentry.LogError("failed to get server version", _file_basename, _line); - Application::SetSubsystemStatus("UpdateCheck", Application::Status::Bad); + } + if (Application::GetSubsystemStatuses().at("UpdateCheck") == Application::Status::Bad) { + beammp_warn("Unable to fetch version info from backend."); } } diff --git a/src/Http.cpp b/src/Http.cpp index 11bea18..467fd3c 100644 --- a/src/Http.cpp +++ b/src/Http.cpp @@ -4,6 +4,7 @@ #include "Common.h" #include "CustomAssert.h" #include "LuaAPI.h" +#include "httplib.h" #include #include @@ -33,17 +34,20 @@ std::string Http::GET(const std::string& host, int port, const std::string& targ } } -std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status) { +std::string Http::POST(const std::string& host, int port, const std::string& target, const std::string& body, const std::string& ContentType, unsigned int* status, const httplib::Headers& headers) { httplib::SSLClient client(host, port); + client.set_read_timeout(std::chrono::seconds(10)); + beammp_assert(client.is_valid()); client.enable_server_certificate_verification(false); client.set_address_family(AF_INET); - auto res = client.Post(target.c_str(), body.c_str(), body.size(), ContentType.c_str()); + auto res = client.Post(target.c_str(), headers, body.c_str(), body.size(), ContentType.c_str()); if (res) { if (status) { *status = res->status; } return res->body; } else { + beammp_debug("POST failed: " + httplib::to_string(res.error())); return Http::ErrorString; } } diff --git a/src/TConsole.cpp b/src/TConsole.cpp index 79df8d2..827881c 100644 --- a/src/TConsole.cpp +++ b/src/TConsole.cpp @@ -386,7 +386,7 @@ TConsole::TConsole() { } else { auto Future = mLuaEngine->EnqueueScript(mStateId, { std::make_shared(cmd), "", "" }); while (!Future->Ready) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); // TODO: Add a timeout + std::this_thread::yield(); // TODO: Add a timeout } if (Future->Error) { beammp_lua_error(Future->ErrorMessage); diff --git a/src/THeartbeatThread.cpp b/src/THeartbeatThread.cpp index 27ef253..f5be47b 100644 --- a/src/THeartbeatThread.cpp +++ b/src/THeartbeatThread.cpp @@ -54,41 +54,71 @@ void THeartbeatThread::operator()() { auto Target = "/heartbeat"; unsigned int ResponseCode = 0; - T = Http::POST(Application::GetBackendHostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode); - if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) { - beammp_trace("got " + T + " from backend"); - Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); - SentryReportError(Application::GetBackendHostname() + Target, ResponseCode); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - T = Http::POST(Application::GetBackup1Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode); - if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) { - SentryReportError(Application::GetBackup1Hostname() + Target, ResponseCode); - Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - T = Http::POST(Application::GetBackup2Hostname(), 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode); - if ((T.substr(0, 2) != "20" && ResponseCode != 200) || ResponseCode != 200) { - beammp_warn("Backend system refused server! Server will not show in the public server list."); - Application::SetSubsystemStatus("Heartbeat", Application::Status::Bad); - isAuth = false; - SentryReportError(Application::GetBackup2Hostname() + Target, ResponseCode); - } else { - Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); - } + json::Document Doc; + bool Ok = false; + for (const auto& Url : Application::GetBackendUrlsInOrder()) { + T = Http::POST(Url, 443, Target, Body, "application/x-www-form-urlencoded", &ResponseCode, { { "api-v", "2" } }); + beammp_trace(T); + Doc.Parse(T.data(), T.size()); + if (Doc.HasParseError() || !Doc.IsObject()) { + beammp_error("Backend response failed to parse as valid json"); + beammp_debug("Response was: `" + T + "`"); + Sentry.SetContext("JSON Response", { { "reponse", T } }); + SentryReportError(Url + Target, ResponseCode); + } else if (ResponseCode != 200) { + SentryReportError(Url + Target, ResponseCode); } else { - Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); + // all ok + Ok = true; + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + std::string Status {}; + std::string Code {}; + std::string Message {}; + const auto StatusKey = "status"; + const auto CodeKey = "code"; + const auto MessageKey = "msg"; + + if (Ok) { + if (Doc.HasMember(StatusKey) && Doc[StatusKey].IsString()) { + Status = Doc[StatusKey].GetString(); + } else { + Sentry.SetContext("JSON Response", { { StatusKey, "invalid string / missing" } }); + Ok = false; + } + if (Doc.HasMember(CodeKey) && Doc[CodeKey].IsString()) { + Code = Doc[CodeKey].GetString(); + } else { + Sentry.SetContext("JSON Response", { { CodeKey, "invalid string / missing" } }); + Ok = false; + } + if (Doc.HasMember(MessageKey) && Doc[MessageKey].IsString()) { + Message = Doc[MessageKey].GetString(); + } else { + Sentry.SetContext("JSON Response", { { MessageKey, "invalid string / missing" } }); + Ok = false; + } + if (!Ok) { + beammp_error("Missing/invalid json members in backend response"); + Sentry.LogError("Missing/invalid json members in backend response", __FILE__, std::to_string(__LINE__)); } - } else { - Application::SetSubsystemStatus("Heartbeat", Application::Status::Good); } - if (!isAuth) { - if (T == "2000") { + if (Ok && !isAuth) { + if (Status == "2000") { beammp_info(("Authenticated!")); isAuth = true; - } else if (T == "200") { + } else if (Status == "200") { beammp_info(("Resumed authenticated session!")); isAuth = true; + } else { + if (Message.empty()) { + Message = "Backend didn't provide a reason"; + } + beammp_error("Backend REFUSED the auth key. " + Message); } } } diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index 89742e2..41ffb96 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -54,25 +54,17 @@ void TLuaEngine::operator()() { auto ResultCheckThread = std::thread([&] { RegisterThread("ResultCheckThread"); while (!mShutdown) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::unique_lock Lock(mResultsToCheckMutex); if (!mResultsToCheck.empty()) { auto Res = mResultsToCheck.front(); mResultsToCheck.pop(); Lock.unlock(); - size_t Waited = 0; - while (!Res->Ready) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - Waited++; - if (Waited > 250) { - // FIXME: This should *eventually* timeout. - // beammp_lua_error(Res->Function + " in " + Res->StateId + " took >1s to respond, not printing possible errors"); - Lock.lock(); - mResultsToCheck.push(Res); - Lock.unlock(); - break; - } + if (!Res->Ready) { + Lock.lock(); + mResultsToCheck.push(Res); + Lock.unlock(); } if (Res->Error) { if (Res->ErrorMessage != BeamMPFnNotFoundError) { @@ -80,13 +72,14 @@ void TLuaEngine::operator()() { } } } + std::this_thread::yield(); } }); // event loop auto Before = std::chrono::high_resolution_clock::now(); while (!mShutdown) { if (mLuaStates.size() == 0) { - std::this_thread::sleep_for(std::chrono::seconds(500)); + std::this_thread::sleep_for(std::chrono::seconds(100)); } { // Timed Events Scope std::unique_lock Lock(mTimedEventsMutex); @@ -149,6 +142,11 @@ TLuaStateId TLuaEngine::GetStateIDForPlugin(const fs::path& PluginPath) { return ""; } +void TLuaEngine::AddResultToCheck(const std::shared_ptr& Result) { + std::unique_lock Lock(mResultsToCheckMutex); + mResultsToCheck.push(Result); +} + void TLuaEngine::WaitForAll(std::vector>& Results, const std::optional& Max) { for (const auto& Result : Results) { bool Cancelled = false; @@ -705,6 +703,7 @@ void TLuaEngine::StateThreadData::AddPath(const fs::path& Path) { void TLuaResult::WaitUntilReady() { while (!Ready) { + std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } @@ -762,12 +761,7 @@ void TPluginMonitor::operator()() { auto StateID = mEngine.GetStateIDForPlugin(fs::path(Pair.first).parent_path()); auto Res = mEngine.EnqueueScript(StateID, Chunk); // TODO: call onInit - while (!Res->Ready) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - if (Res->Error) { - beammp_lua_error(Res->ErrorMessage); - } + mEngine.AddResultToCheck(Res); } else { // TODO: trigger onFileChanged event beammp_trace("Change detected in file \"" + Pair.first + "\", event trigger not implemented yet"); diff --git a/src/TPPSMonitor.cpp b/src/TPPSMonitor.cpp index 8d58d84..b5b4951 100644 --- a/src/TPPSMonitor.cpp +++ b/src/TPPSMonitor.cpp @@ -21,8 +21,8 @@ TPPSMonitor::TPPSMonitor(TServer& Server) void TPPSMonitor::operator()() { RegisterThread("PPSMonitor"); while (!mNetwork) { - // hard spi - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + // hard(-ish) spin + std::this_thread::yield(); } beammp_debug("PPSMonitor starting"); Application::SetSubsystemStatus("PPSMonitor", Application::Status::Good); diff --git a/src/main.cpp b/src/main.cpp index 59764f2..a1c9354 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -77,11 +77,6 @@ int main(int argc, char** argv) { int BeamMPServerMain(MainArguments Arguments) { setlocale(LC_ALL, "C"); - Application::InitializeConsole(); - Application::SetSubsystemStatus("Main", Application::Status::Starting); - - SetupSignalHandlers(); - ArgsParser Parser; Parser.RegisterArgument({ "help" }, ArgsParser::NONE); Parser.RegisterArgument({ "version" }, ArgsParser::NONE); @@ -121,6 +116,11 @@ int BeamMPServerMain(MainArguments Arguments) { } } } + + Application::InitializeConsole(); + Application::SetSubsystemStatus("Main", Application::Status::Starting); + + SetupSignalHandlers(); bool Shutdown = false; Application::RegisterShutdownHandler([&Shutdown] {