mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-02-01 11:15:13 +01:00
If the master server vanishes make sure to register again, cleanup (#25588)
* If the master server vanishes make sure to register again, cleanup * Update changelog.txt * Fix uncaught exceptions for the HTTP impl just silently failing * Don't set the future object to an empty one, that is always pending * Improve the console messages, adjust retry time
This commit is contained in:
@@ -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)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -84,81 +84,91 @@ namespace OpenRCT2::Http
|
||||
|
||||
Response Do(const Request& req)
|
||||
{
|
||||
CURL* curl = curl_easy_init();
|
||||
std::shared_ptr<void> _(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<void> _(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<long>(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<void*>(&res));
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, static_cast<void*>(&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<void> __(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<long>(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<void*>(&res));
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, HeaderCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, static_cast<void*>(&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<void> __(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<Status>(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<Status>(code);
|
||||
if (content_type != nullptr)
|
||||
{
|
||||
res.content_type = std::string(content_type);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace OpenRCT2::Http
|
||||
|
||||
@@ -12,15 +12,16 @@
|
||||
#ifndef DISABLE_HTTP
|
||||
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
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<void(Response& res)> fn)
|
||||
inline auto DoAsync(const Request& req, std::function<void(Response& res)> 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
|
||||
|
||||
|
||||
@@ -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<IUdpSocket> _lanListener;
|
||||
std::shared_future<void> _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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace OpenRCT2::Network
|
||||
{
|
||||
disabled,
|
||||
unregistered,
|
||||
registering,
|
||||
registered,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user