mirror of
https://github.com/BeamMP/BeamMP-Server.git
synced 2026-10-26 22:10:06 +00:00
use a single strand per client to avoid writing to tcp out of order
This commit is contained in:
2 files changed
+31
-15
No files matched your search
@@ -29,7 +29,9 @@ using VehicleID = uint16_t;
|
|||||||
using namespace boost::asio;
|
using namespace boost::asio;
|
||||||
|
|
||||||
struct Client {
|
struct Client {
|
||||||
|
using StrandPtr = std::shared_ptr<boost::asio::strand<ip::tcp::socket::executor_type>>;
|
||||||
using Ptr = std::shared_ptr<Client>;
|
using Ptr = std::shared_ptr<Client>;
|
||||||
|
|
||||||
ClientID id;
|
ClientID id;
|
||||||
Sync<bmp::State> state { bmp::State::None };
|
Sync<bmp::State> state { bmp::State::None };
|
||||||
|
|
||||||
@@ -93,6 +95,8 @@ private:
|
|||||||
ip::udp::endpoint m_udp_endpoint;
|
ip::udp::endpoint m_udp_endpoint;
|
||||||
|
|
||||||
class Network& m_network;
|
class Network& m_network;
|
||||||
|
|
||||||
|
StrandPtr m_tcp_strand;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Vehicle {
|
struct Vehicle {
|
||||||
|
|||||||
+27
-15
@@ -35,13 +35,14 @@
|
|||||||
|
|
||||||
/// Boost::asio + strands + timer magic to make writes timeout after some time.
|
/// Boost::asio + strands + timer magic to make writes timeout after some time.
|
||||||
template <typename HandlerFn>
|
template <typename HandlerFn>
|
||||||
static void async_write_timeout(ip::tcp::socket& stream, const_buffer&& sequence, boost::posix_time::milliseconds timeout_ms, HandlerFn&& handler) {
|
static void async_write_timeout(ip::tcp::socket& stream, const_buffer&& sequence, boost::posix_time::milliseconds timeout_ms, HandlerFn&& handler, Client::StrandPtr strand) {
|
||||||
struct TimeoutHelper : std::enable_shared_from_this<TimeoutHelper> {
|
struct TimeoutHelper : std::enable_shared_from_this<TimeoutHelper> {
|
||||||
/// Given a socket (stream), buffer and a completion handler, constructs a state machine.
|
/// Given a socket (stream), buffer and a completion handler, constructs a state machine.
|
||||||
TimeoutHelper(ip::tcp::socket& stream, const_buffer buffer, HandlerFn handler)
|
TimeoutHelper(ip::tcp::socket& stream, const_buffer buffer, HandlerFn handler, Client::StrandPtr strand)
|
||||||
: m_stream(stream)
|
: m_stream(stream)
|
||||||
, m_buffer(std::move(buffer))
|
, m_buffer(std::move(buffer))
|
||||||
, m_handler_fn(std::move(handler)) { }
|
, m_handler_fn(std::move(handler))
|
||||||
|
, m_strand(std::move(strand)) { }
|
||||||
/// Kicks off the timer and async_write, which race to cancel each other.
|
/// Kicks off the timer and async_write, which race to cancel each other.
|
||||||
/// Whichever completes first gets to cancel the other one.
|
/// Whichever completes first gets to cancel the other one.
|
||||||
/// Effectively, the timer will finish before the write if the write is "timing out",
|
/// Effectively, the timer will finish before the write if the write is "timing out",
|
||||||
@@ -53,12 +54,12 @@ static void async_write_timeout(ip::tcp::socket& stream, const_buffer&& sequence
|
|||||||
// as a copy so that it can call the timeout handler on this object.
|
// as a copy so that it can call the timeout handler on this object.
|
||||||
// the whole thing is wrapped in a strand to avoid this happening on two separate thwrites at the same time,
|
// the whole thing is wrapped in a strand to avoid this happening on two separate thwrites at the same time,
|
||||||
// i.e. the timer and write finish at the same time on separate thwrites, or other goofy stuff.
|
// i.e. the timer and write finish at the same time on separate thwrites, or other goofy stuff.
|
||||||
m_timer.async_wait(bind_executor(m_strand, [self = this->shared_from_this()](auto&& ec) {
|
m_timer.async_wait(bind_executor(*m_strand, [self = this->shared_from_this()](auto&& ec) {
|
||||||
self->handle_timeout(ec);
|
self->handle_timeout(ec);
|
||||||
}));
|
}));
|
||||||
// start the write on the same strand, again giving a copy of a shared_ptr to ourselves so the handler can be
|
// start the write on the same strand, again giving a copy of a shared_ptr to ourselves so the handler can be
|
||||||
// called.
|
// called.
|
||||||
boost::asio::async_write(m_stream, m_buffer, bind_executor(m_strand, [self = this->shared_from_this()](auto&& ec, auto size) {
|
boost::asio::async_write(m_stream, m_buffer, bind_executor(*m_strand, [self = this->shared_from_this()](auto&& ec, auto size) {
|
||||||
self->handle_write(ec, size);
|
self->handle_write(ec, size);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -91,14 +92,15 @@ static void async_write_timeout(ip::tcp::socket& stream, const_buffer&& sequence
|
|||||||
ip::tcp::socket& m_stream;
|
ip::tcp::socket& m_stream;
|
||||||
const_buffer m_buffer;
|
const_buffer m_buffer;
|
||||||
HandlerFn m_handler_fn;
|
HandlerFn m_handler_fn;
|
||||||
boost::asio::strand<ip::tcp::socket::executor_type> m_strand { m_stream.get_executor() };
|
Client::StrandPtr m_strand;
|
||||||
boost::asio::deadline_timer m_timer { m_stream.get_executor() };
|
boost::asio::deadline_timer m_timer { m_stream.get_executor() };
|
||||||
bool m_completed = false;
|
bool m_completed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto helper = std::make_shared<TimeoutHelper>(stream,
|
auto helper = std::make_shared<TimeoutHelper>(stream,
|
||||||
std::forward<const_buffer>(sequence),
|
std::forward<const_buffer>(sequence),
|
||||||
std::forward<HandlerFn>(handler));
|
std::forward<HandlerFn>(handler),
|
||||||
|
strand);
|
||||||
helper->start(timeout_ms);
|
helper->start(timeout_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,12 +197,14 @@ void Client::tcp_write(bmp::Packet& packet) {
|
|||||||
auto timeout = boost::posix_time::milliseconds(std::max(size_t(500), size_t(std::ceil(double(data->size()) * m_write_byte_timeout))));
|
auto timeout = boost::posix_time::milliseconds(std::max(size_t(500), size_t(std::ceil(double(data->size()) * m_write_byte_timeout))));
|
||||||
beammp_tracef("Packet of size {} B given a timeout of {}ms ({}s)", data->size(), timeout.total_milliseconds(), timeout.seconds());
|
beammp_tracef("Packet of size {} B given a timeout of {}ms ({}s)", data->size(), timeout.total_milliseconds(), timeout.seconds());
|
||||||
// write header and packet data
|
// write header and packet data
|
||||||
async_write_timeout(m_tcp_socket, buffer(*data), timeout, [data, this](const boost::system::error_code& ec, size_t) {
|
async_write_timeout(
|
||||||
if (ec && ec.value() == boost::system::errc::operation_canceled) {
|
m_tcp_socket, buffer(*data), timeout, [data, this](const boost::system::error_code& ec, size_t) {
|
||||||
// write timeout is fatal
|
if (ec && ec.value() == boost::system::errc::operation_canceled) {
|
||||||
m_network.disconnect(id, "Write timeout");
|
// write timeout is fatal
|
||||||
}
|
m_network.disconnect(id, "Write timeout");
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
m_tcp_strand);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::tcp_write_file_raw(const std::filesystem::path& path) {
|
void Client::tcp_write_file_raw(const std::filesystem::path& path) {
|
||||||
@@ -237,7 +241,8 @@ Client::Client(ClientID id, Network& network, ip::tcp::socket&& tcp_socket)
|
|||||||
: id(id)
|
: id(id)
|
||||||
, udp_magic(id ^ (uint64_t(std::rand()) << 32) ^ uint64_t(this))
|
, udp_magic(id ^ (uint64_t(std::rand()) << 32) ^ uint64_t(this))
|
||||||
, m_tcp_socket(std::forward<ip::tcp::socket&&>(tcp_socket))
|
, m_tcp_socket(std::forward<ip::tcp::socket&&>(tcp_socket))
|
||||||
, m_network(network) {
|
, m_network(network)
|
||||||
|
, m_tcp_strand(std::make_shared<StrandPtr::element_type>(m_tcp_socket.get_executor())) {
|
||||||
beammp_debugf("Client {} created", id);
|
beammp_debugf("Client {} created", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,6 +884,13 @@ void Network::handle_mod_download(ClientID id, const bmp::Packet& packet, std::s
|
|||||||
switch (packet.purpose) {
|
switch (packet.purpose) {
|
||||||
case bmp::Purpose::ModsSyncDone: {
|
case bmp::Purpose::ModsSyncDone: {
|
||||||
beammp_debugf("Client {} is done with mods sync", id);
|
beammp_debugf("Client {} is done with mods sync", id);
|
||||||
|
beammp_debugf("Sending map info to client {}", id);
|
||||||
|
bmp::Packet map_info {
|
||||||
|
.purpose = bmp::Purpose::MapInfo,
|
||||||
|
.raw_data = std::vector<uint8_t>(Application::Settings.MapName.begin(), Application::Settings.MapName.end()),
|
||||||
|
};
|
||||||
|
client->tcp_write(map_info);
|
||||||
|
beammp_debugf("Client {} entering session setup", id);
|
||||||
bmp::Packet state_change {
|
bmp::Packet state_change {
|
||||||
.purpose = bmp::Purpose::StateChangeSessionSetup,
|
.purpose = bmp::Purpose::StateChangeSessionSetup,
|
||||||
};
|
};
|
||||||
@@ -1066,7 +1078,7 @@ void Vehicle::refresh_cache(std::unique_lock<std::recursive_mutex>& lock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Vehicle position parse, cache, access") {
|
TEST_CASE("Vehicle position parse, cache, access") {
|
||||||
Vehicle veh {};
|
Vehicle veh { {} };
|
||||||
std::string str = R"({"rvel":[0.034001241344458,0.016966195008928,-0.0032029844877877],"rot":[-0.0012675799979579,0.0014056711767528,0.94126306518056,0.3376688606555],"tim":66.978502945043,"vel":[-18.80228647297,22.830758602197,0.0011466381380035],"pos":[562.68027268429,-379.27891669179,160.40605946989],"ping":0.032000000871718})";
|
std::string str = R"({"rvel":[0.034001241344458,0.016966195008928,-0.0032029844877877],"rot":[-0.0012675799979579,0.0014056711767528,0.94126306518056,0.3376688606555],"tim":66.978502945043,"vel":[-18.80228647297,22.830758602197,0.0011466381380035],"pos":[562.68027268429,-379.27891669179,160.40605946989],"ping":0.032000000871718})";
|
||||||
veh.update_status(std::vector<uint8_t>(str.begin(), str.end()));
|
veh.update_status(std::vector<uint8_t>(str.begin(), str.end()));
|
||||||
auto status = veh.get_status();
|
auto status = veh.get_status();
|
||||||
|
|||||||
Reference in new issue
Block a user