diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 5707772e0a..70db8a2667 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -10,6 +10,7 @@ - Fix: [#25565] Chairlift station ends are missing tunnels at certain rotations. - Fix: [#25569] Placing park entrances in multiplayer does not show for other players, causing desyncs. - Fix: [#25571] Potential crash due to drawing a Crooked House ride. +- Fix: [#25588] When the master server becomes unreachable the server would not register again until a restart. 0.4.29 (2025-11-22) ------------------------------------------------------------------------ diff --git a/src/openrct2/core/Http.WinHttp.cpp b/src/openrct2/core/Http.WinHttp.cpp index b4a53c7f3d..309d515d9f 100644 --- a/src/openrct2/core/Http.WinHttp.cpp +++ b/src/openrct2/core/Http.WinHttp.cpp @@ -236,7 +236,11 @@ namespace OpenRCT2::Http WinHttpCloseHandle(hSession); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hRequest); - throw; + + Response response; + response.status = Status::Error; + response.error = e.what(); + return response; } } } // namespace OpenRCT2::Http diff --git a/src/openrct2/core/Http.cURL.cpp b/src/openrct2/core/Http.cURL.cpp index 447a5ed0b6..fc06edeca1 100644 --- a/src/openrct2/core/Http.cURL.cpp +++ b/src/openrct2/core/Http.cURL.cpp @@ -84,81 +84,91 @@ namespace OpenRCT2::Http Response Do(const Request& req) { - CURL* curl = curl_easy_init(); - std::shared_ptr _(nullptr, [curl](...) { curl_easy_cleanup(curl); }); - - if (!curl) - throw std::runtime_error("Failed to initialize curl"); - - Response res; - WriteThis wt; - - if (req.method == Method::POST || req.method == Method::PUT) + try { - wt.readptr = req.body.c_str(); - wt.sizeleft = req.body.size(); + CURL* curl = curl_easy_init(); + std::shared_ptr _(nullptr, [curl](...) { curl_easy_cleanup(curl); }); - curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadCallback); - curl_easy_setopt(curl, CURLOPT_READDATA, &wt); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(wt.sizeleft)); - } + if (!curl) + throw std::runtime_error("Failed to initialize curl"); - if (req.forceIPv4) - curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + Response res; + WriteThis wt; - if (req.method == Method::POST) - curl_easy_setopt(curl, CURLOPT_POST, 1L); - - if (req.method == Method::PUT) - curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); - - curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str()); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(&res)); - curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback); - curl_easy_setopt(curl, CURLOPT_HEADERDATA, static_cast(&res)); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); - curl_easy_setopt(curl, CURLOPT_USERAGENT, kOpenRCT2UserAgent); - - curl_slist* chunk = nullptr; - std::shared_ptr __(nullptr, [chunk](...) { curl_slist_free_all(chunk); }); - for (auto header : req.header) - { - std::string hs = header.first + ": " + header.second; - chunk = curl_slist_append(chunk, hs.c_str()); - } - if (req.header.size() != 0) - { - if (chunk == nullptr) + if (req.method == Method::POST || req.method == Method::PUT) { - throw std::runtime_error("Failed to set headers"); + wt.readptr = req.body.c_str(); + wt.sizeleft = req.body.size(); + + curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadCallback); + curl_easy_setopt(curl, CURLOPT_READDATA, &wt); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast(wt.sizeleft)); } - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); - } - CURLcode curl_code = curl_easy_perform(curl); - if (curl_code != CURLE_OK) + if (req.forceIPv4) + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + + if (req.method == Method::POST) + curl_easy_setopt(curl, CURLOPT_POST, 1L); + + if (req.method == Method::PUT) + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + + curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast(&res)); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, static_cast(&res)); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); + curl_easy_setopt(curl, CURLOPT_USERAGENT, kOpenRCT2UserAgent); + + curl_slist* chunk = nullptr; + std::shared_ptr __(nullptr, [chunk](...) { curl_slist_free_all(chunk); }); + for (auto header : req.header) + { + std::string hs = header.first + ": " + header.second; + chunk = curl_slist_append(chunk, hs.c_str()); + } + if (req.header.size() != 0) + { + if (chunk == nullptr) + { + throw std::runtime_error("Failed to set headers"); + } + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); + } + + CURLcode curl_code = curl_easy_perform(curl); + if (curl_code != CURLE_OK) + { + using namespace std::literals; + throw std::runtime_error( + "Failed to perform request. curl error code: "s + std::to_string(curl_code) + ": " + + curl_easy_strerror(curl_code)); + } + + // gets freed by curl_easy_cleanup + char* content_type; + long code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type); + res.status = static_cast(code); + if (content_type != nullptr) + { + res.content_type = std::string(content_type); + } + + return res; + } + catch (const std::exception& e) { - using namespace std::literals; - throw std::runtime_error( - "Failed to perform request. curl error code: "s + std::to_string(curl_code) + ": " - + curl_easy_strerror(curl_code)); + Response response; + response.status = Status::Error; + response.error = e.what(); + return response; } - - // gets freed by curl_easy_cleanup - char* content_type; - long code; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); - curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type); - res.status = static_cast(code); - if (content_type != nullptr) - { - res.content_type = std::string(content_type); - } - - return res; } } // namespace OpenRCT2::Http diff --git a/src/openrct2/core/Http.h b/src/openrct2/core/Http.h index cafd8e8de0..22eb318602 100644 --- a/src/openrct2/core/Http.h +++ b/src/openrct2/core/Http.h @@ -12,15 +12,16 @@ #ifndef DISABLE_HTTP #include + #include #include #include - #include namespace OpenRCT2::Http { enum class Status { Invalid = 0, + Error = 1, Ok = 200, NotFound = 404 }; @@ -52,9 +53,9 @@ namespace OpenRCT2::Http Response Do(const Request& req); - inline void DoAsync(const Request& req, std::function fn) + inline auto DoAsync(const Request& req, std::function fn) { - auto thread = std::thread([=]() { + return std::async(std::launch::async, [=]() { Response res{}; try { @@ -67,7 +68,6 @@ namespace OpenRCT2::Http } fn(res); }); - thread.detach(); } } // namespace OpenRCT2::Http diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index 51c5828de1..c97d89c72e 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -46,7 +46,7 @@ namespace OpenRCT2::Network #ifndef DISABLE_HTTP using namespace std::chrono_literals; - constexpr int32_t kMasterServerRegisterTime = std::chrono::milliseconds(2min).count(); + constexpr int32_t kMasterServerRegisterTime = std::chrono::milliseconds(30s).count(); constexpr int32_t kMasterServerHeartbeatTime = std::chrono::milliseconds(1min).count(); #endif @@ -56,6 +56,7 @@ namespace OpenRCT2::Network uint16_t _port; std::unique_ptr _lanListener; + std::shared_future _currentRequest; uint32_t _lastListenTime{}; AdvertiseStatus _status = AdvertiseStatus::unregistered; @@ -84,6 +85,17 @@ namespace OpenRCT2::Network #endif } + ~NetworkServerAdvertiser() final + { + _lanListener->Close(); + + auto currentRequest = _currentRequest; + if (currentRequest.valid()) + { + currentRequest.wait(); + } + } + AdvertiseStatus GetStatus() const override { return _status; @@ -149,13 +161,13 @@ namespace OpenRCT2::Network case AdvertiseStatus::unregistered: if (_lastAdvertiseTime == 0 || Platform::GetTicks() > _lastAdvertiseTime + kMasterServerRegisterTime) { - if (_lastAdvertiseTime == 0) - { - Console::WriteLine("Registering server on master server"); - } + Console::WriteLine("Registering server on master server..."); SendRegistration(_forceIPv4); } break; + case AdvertiseStatus::registering: + // Waiting for registration response. + break; case AdvertiseStatus::registered: if (Platform::GetTicks() > _lastHeartbeatTime + kMasterServerHeartbeatTime) { @@ -171,6 +183,7 @@ namespace OpenRCT2::Network void SendRegistration(bool forceIPv4) { _lastAdvertiseTime = Platform::GetTicks(); + _status = AdvertiseStatus::registering; // Send the registration request Http::Request request; @@ -191,17 +204,21 @@ namespace OpenRCT2::Network request.body = body.dump(); request.header["Content-Type"] = "application/json"; - Http::DoAsync(request, [&](Http::Response response) -> void { - if (response.status != Http::Status::Ok) - { - Console::Error::WriteLine("Unable to connect to master server"); - return; - } + _currentRequest = Http::DoAsync(request, [&](Http::Response response) -> void { + if (response.status != Http::Status::Ok) + { + Console::Error::WriteLine( + "Unable to connect to master server, retrying in %d seconds", + kMasterServerRegisterTime / 1000); - json_t root = Json::FromString(response.body); - root = Json::AsObject(root); - this->OnRegistrationResponse(root); - }); + _status = AdvertiseStatus::unregistered; + return; + } + + json_t root = Json::FromString(response.body); + root = Json::AsObject(root); + this->OnRegistrationResponse(root); + }).share(); } void SendHeartbeat() @@ -215,17 +232,24 @@ namespace OpenRCT2::Network request.header["Content-Type"] = "application/json"; _lastHeartbeatTime = Platform::GetTicks(); - Http::DoAsync(request, [&](Http::Response response) -> void { - if (response.status != Http::Status::Ok) - { - Console::Error::WriteLine("Unable to connect to master server"); - return; - } - json_t root = Json::FromString(response.body); - root = Json::AsObject(root); - this->OnHeartbeatResponse(root); - }); + _currentRequest = Http::DoAsync(request, [&](Http::Response response) -> void { + if (response.status != Http::Status::Ok) + { + Console::Error::WriteLine( + "Unable to connect to master server, retrying in %d seconds", + kMasterServerRegisterTime / 1000); + + _status = AdvertiseStatus::unregistered; + // Don't immediately retry advertising, wait for kMasterServerRegisterTime. + _lastAdvertiseTime = Platform::GetTicks(); + return; + } + + json_t root = Json::FromString(response.body); + root = Json::AsObject(root); + this->OnHeartbeatResponse(root); + }).share(); } /** diff --git a/src/openrct2/network/NetworkServerAdvertiser.h b/src/openrct2/network/NetworkServerAdvertiser.h index 1b48717e61..c7d8e7f3ef 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.h +++ b/src/openrct2/network/NetworkServerAdvertiser.h @@ -18,6 +18,7 @@ namespace OpenRCT2::Network { disabled, unregistered, + registering, registered, };