Shave off buffer copies in networking code (#11607)

This commit is contained in:
sfan5 2021-09-17 18:14:25 +02:00 committed by rubenwardy
parent e5cfdd369e
commit 05b54a8d18
8 changed files with 126 additions and 92 deletions

@ -200,17 +200,12 @@ RPBSearchResult ReliablePacketBuffer::findPacket(u16 seqnum)
return i;
}
RPBSearchResult ReliablePacketBuffer::notFound()
{
return m_list.end();
}
bool ReliablePacketBuffer::getFirstSeqnum(u16& result)
{
MutexAutoLock listlock(m_list_mutex);
if (m_list.empty())
return false;
const BufferedPacket &p = *m_list.begin();
const BufferedPacket &p = m_list.front();
result = readU16(&p.data[BASE_HEADER_SIZE + 1]);
return true;
}
@ -220,14 +215,14 @@ BufferedPacket ReliablePacketBuffer::popFirst()
MutexAutoLock listlock(m_list_mutex);
if (m_list.empty())
throw NotFoundException("Buffer is empty");
BufferedPacket p = *m_list.begin();
m_list.erase(m_list.begin());
BufferedPacket p = std::move(m_list.front());
m_list.pop_front();
if (m_list.empty()) {
m_oldest_non_answered_ack = 0;
} else {
m_oldest_non_answered_ack =
readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]);
readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]);
}
return p;
}
@ -241,15 +236,7 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum)
<< " not found in reliable buffer"<<std::endl);
throw NotFoundException("seqnum not found in buffer");
}
BufferedPacket p = *r;
RPBSearchResult next = r;
++next;
if (next != notFound()) {
u16 s = readU16(&(next->data[BASE_HEADER_SIZE+1]));
m_oldest_non_answered_ack = s;
}
BufferedPacket p = std::move(*r);
m_list.erase(r);
@ -257,12 +244,12 @@ BufferedPacket ReliablePacketBuffer::popSeqnum(u16 seqnum)
m_oldest_non_answered_ack = 0;
} else {
m_oldest_non_answered_ack =
readU16(&m_list.begin()->data[BASE_HEADER_SIZE + 1]);
readU16(&m_list.front().data[BASE_HEADER_SIZE + 1]);
}
return p;
}
void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected)
void ReliablePacketBuffer::insert(const BufferedPacket &p, u16 next_expected)
{
MutexAutoLock listlock(m_list_mutex);
if (p.data.getSize() < BASE_HEADER_SIZE + 3) {
@ -355,7 +342,7 @@ void ReliablePacketBuffer::insert(BufferedPacket &p, u16 next_expected)
}
/* update last packet number */
m_oldest_non_answered_ack = readU16(&(*m_list.begin()).data[BASE_HEADER_SIZE+1]);
m_oldest_non_answered_ack = readU16(&m_list.front().data[BASE_HEADER_SIZE+1]);
}
void ReliablePacketBuffer::incrementTimeouts(float dtime)
@ -367,17 +354,19 @@ void ReliablePacketBuffer::incrementTimeouts(float dtime)
}
}
std::list<BufferedPacket> ReliablePacketBuffer::getTimedOuts(float timeout,
unsigned int max_packets)
std::list<BufferedPacket>
ReliablePacketBuffer::getTimedOuts(float timeout, u32 max_packets)
{
MutexAutoLock listlock(m_list_mutex);
std::list<BufferedPacket> timed_outs;
for (BufferedPacket &bufferedPacket : m_list) {
if (bufferedPacket.time >= timeout) {
// caller will resend packet so reset time and increase counter
bufferedPacket.time = 0.0f;
bufferedPacket.resend_count++;
timed_outs.push_back(bufferedPacket);
//this packet will be sent right afterwards reset timeout here
bufferedPacket.time = 0.0f;
if (timed_outs.size() >= max_packets)
break;
}
@ -1051,20 +1040,20 @@ bool UDPPeer::processReliableSendCommand(
m_connection->GetProtocolID(), m_connection->GetPeerID(),
c.channelnum);
toadd.push(p);
toadd.push(std::move(p));
}
if (have_sequence_number) {
volatile u16 pcount = 0;
while (!toadd.empty()) {
BufferedPacket p = toadd.front();
BufferedPacket p = std::move(toadd.front());
toadd.pop();
// LOG(dout_con<<connection->getDesc()
// << " queuing reliable packet for peer_id: " << c.peer_id
// << " channel: " << (c.channelnum&0xFF)
// << " seqnum: " << readU16(&p.data[BASE_HEADER_SIZE+1])
// << std::endl)
chan.queued_reliables.push(p);
chan.queued_reliables.push(std::move(p));
pcount++;
}
sanity_check(chan.queued_reliables.size() < 0xFFFF);
@ -1208,12 +1197,19 @@ Connection::~Connection()
}
/* Internal stuff */
void Connection::putEvent(ConnectionEvent &e)
void Connection::putEvent(const ConnectionEvent &e)
{
assert(e.type != CONNEVENT_NONE); // Pre-condition
m_event_queue.push_back(e);
}
void Connection::putEvent(ConnectionEvent &&e)
{
assert(e.type != CONNEVENT_NONE); // Pre-condition
m_event_queue.push_back(std::move(e));
}
void Connection::TriggerSend()
{
m_sendThread->Trigger();
@ -1299,7 +1295,7 @@ ConnectionEvent Connection::waitEvent(u32 timeout_ms)
}
}
void Connection::putCommand(ConnectionCommand &c)
void Connection::putCommand(const ConnectionCommand &c)
{
if (!m_shutting_down) {
m_command_queue.push_back(c);
@ -1307,6 +1303,14 @@ void Connection::putCommand(ConnectionCommand &c)
}
}
void Connection::putCommand(ConnectionCommand &&c)
{
if (!m_shutting_down) {
m_command_queue.push_back(std::move(c));
m_sendThread->Trigger();
}
}
void Connection::Serve(Address bind_addr)
{
ConnectionCommand c;
@ -1408,7 +1412,7 @@ void Connection::Send(session_t peer_id, u8 channelnum,
ConnectionCommand c;
c.send(peer_id, channelnum, pkt, reliable);
putCommand(c);
putCommand(std::move(c));
}
Address Connection::GetPeerAddress(session_t peer_id)
@ -1508,12 +1512,12 @@ u16 Connection::createPeer(Address& sender, MTProtocols protocol, int fd)
<< "createPeer(): giving peer_id=" << peer_id_new << std::endl);
ConnectionCommand cmd;
SharedBuffer<u8> reply(4);
Buffer<u8> reply(4);
writeU8(&reply[0], PACKET_TYPE_CONTROL);
writeU8(&reply[1], CONTROLTYPE_SET_PEER_ID);
writeU16(&reply[2], peer_id_new);
cmd.createPeer(peer_id_new,reply);
putCommand(cmd);
putCommand(std::move(cmd));
// Create peer addition event
ConnectionEvent e;
@ -1560,7 +1564,7 @@ void Connection::sendAck(session_t peer_id, u8 channelnum, u16 seqnum)
writeU16(&ack[2], seqnum);
c.ack(peer_id, channelnum, ack);
putCommand(c);
putCommand(std::move(c));
m_sendThread->Trigger();
}

@ -19,7 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once
#include "irrlichttypes_bloated.h"
#include "irrlichttypes.h"
#include "peerhandler.h"
#include "socket.h"
#include "constants.h"
@ -29,7 +29,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include "networkprotocol.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
@ -242,20 +241,19 @@ public:
BufferedPacket popFirst();
BufferedPacket popSeqnum(u16 seqnum);
void insert(BufferedPacket &p, u16 next_expected);
void insert(const BufferedPacket &p, u16 next_expected);
void incrementTimeouts(float dtime);
std::list<BufferedPacket> getTimedOuts(float timeout,
unsigned int max_packets);
std::list<BufferedPacket> getTimedOuts(float timeout, u32 max_packets);
void print();
bool empty();
RPBSearchResult notFound();
u32 size();
private:
RPBSearchResult findPacket(u16 seqnum); // does not perform locking
inline RPBSearchResult notFound() { return m_list.end(); }
std::list<BufferedPacket> m_list;
@ -329,18 +327,6 @@ struct ConnectionCommand
bool raw = false;
ConnectionCommand() = default;
ConnectionCommand &operator=(const ConnectionCommand &other)
{
type = other.type;
address = other.address;
peer_id = other.peer_id;
channelnum = other.channelnum;
// We must copy the buffer here to prevent race condition
data = SharedBuffer<u8>(*other.data, other.data.getSize());
reliable = other.reliable;
raw = other.raw;
return *this;
}
void serve(Address address_)
{
@ -364,7 +350,7 @@ struct ConnectionCommand
void send(session_t peer_id_, u8 channelnum_, NetworkPacket *pkt, bool reliable_);
void ack(session_t peer_id_, u8 channelnum_, const SharedBuffer<u8> &data_)
void ack(session_t peer_id_, u8 channelnum_, const Buffer<u8> &data_)
{
type = CONCMD_ACK;
peer_id = peer_id_;
@ -373,7 +359,7 @@ struct ConnectionCommand
reliable = false;
}
void createPeer(session_t peer_id_, const SharedBuffer<u8> &data_)
void createPeer(session_t peer_id_, const Buffer<u8> &data_)
{
type = CONCMD_CREATE_PEER;
peer_id = peer_id_;
@ -707,7 +693,7 @@ struct ConnectionEvent
ConnectionEvent() = default;
std::string describe()
const char *describe() const
{
switch(type) {
case CONNEVENT_NONE:
@ -724,7 +710,7 @@ struct ConnectionEvent
return "Invalid ConnectionEvent";
}
void dataReceived(session_t peer_id_, const SharedBuffer<u8> &data_)
void dataReceived(session_t peer_id_, const Buffer<u8> &data_)
{
type = CONNEVENT_DATA_RECEIVED;
peer_id = peer_id_;
@ -763,7 +749,9 @@ public:
/* Interface */
ConnectionEvent waitEvent(u32 timeout_ms);
void putCommand(ConnectionCommand &c);
// Warning: creates an unnecessary copy, prefer putCommand(T&&) if possible
void putCommand(const ConnectionCommand &c);
void putCommand(ConnectionCommand &&c);
void SetTimeoutMs(u32 timeout) { m_bc_receive_timeout = timeout; }
void Serve(Address bind_addr);
@ -802,11 +790,14 @@ protected:
}
UDPSocket m_udpSocket;
// Command queue: user -> SendThread
MutexedQueue<ConnectionCommand> m_command_queue;
bool Receive(NetworkPacket *pkt, u32 timeout);
void putEvent(ConnectionEvent &e);
// Warning: creates an unnecessary copy, prefer putEvent(T&&) if possible
void putEvent(const ConnectionEvent &e);
void putEvent(ConnectionEvent &&e);
void TriggerSend();
@ -815,6 +806,7 @@ protected:
return getPeerNoEx(PEER_ID_SERVER) != nullptr;
}
private:
// Event queue: ReceiveThread -> user
MutexedQueue<ConnectionEvent> m_event_queue;
session_t m_peer_id = 0;

@ -174,6 +174,11 @@ void ConnectionSendThread::runTimeouts(float dtime)
std::vector<session_t> timeouted_peers;
std::vector<session_t> peerIds = m_connection->getPeerIDs();
const u32 numpeers = m_connection->m_peers.size();
if (numpeers == 0)
return;
for (session_t &peerId : peerIds) {
PeerHelper peer = m_connection->getPeerNoEx(peerId);
@ -209,7 +214,6 @@ void ConnectionSendThread::runTimeouts(float dtime)
float resend_timeout = udpPeer->getResendTimeout();
bool retry_count_exceeded = false;
for (Channel &channel : udpPeer->channels) {
std::list<BufferedPacket> timed_outs;
// Remove timed out incomplete unreliable split packets
channel.incoming_splits.removeUnreliableTimedOuts(dtime, m_timeout);
@ -217,13 +221,8 @@ void ConnectionSendThread::runTimeouts(float dtime)
// Increment reliable packet times
channel.outgoing_reliables_sent.incrementTimeouts(dtime);
unsigned int numpeers = m_connection->m_peers.size();
if (numpeers == 0)
return;
// Re-send timed out outgoing reliables
timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout,
auto timed_outs = channel.outgoing_reliables_sent.getTimedOuts(resend_timeout,
(m_max_data_packets_per_iteration / numpeers));
channel.UpdatePacketLossCounter(timed_outs.size());
@ -231,16 +230,14 @@ void ConnectionSendThread::runTimeouts(float dtime)
m_iteration_packets_avaialble -= timed_outs.size();
for (std::list<BufferedPacket>::iterator k = timed_outs.begin();
k != timed_outs.end(); ++k) {
session_t peer_id = readPeerId(*(k->data));
u8 channelnum = readChannel(*(k->data));
u16 seqnum = readU16(&(k->data[BASE_HEADER_SIZE + 1]));
for (const auto &k : timed_outs) {
session_t peer_id = readPeerId(*k.data);
u8 channelnum = readChannel(*k.data);
u16 seqnum = readU16(&(k.data[BASE_HEADER_SIZE + 1]));
channel.UpdateBytesLost(k->data.getSize());
k->resend_count++;
channel.UpdateBytesLost(k.data.getSize());
if (k->resend_count > MAX_RELIABLE_RETRY) {
if (k.resend_count > MAX_RELIABLE_RETRY) {
retry_count_exceeded = true;
timeouted_peers.push_back(peer->id);
/* no need to check additional packets if a single one did timeout*/
@ -249,14 +246,14 @@ void ConnectionSendThread::runTimeouts(float dtime)
LOG(derr_con << m_connection->getDesc()
<< "RE-SENDING timed-out RELIABLE to "
<< k->address.serializeString()
<< k.address.serializeString()
<< "(t/o=" << resend_timeout << "): "
<< "from_peer_id=" << peer_id
<< ", channel=" << ((int) channelnum & 0xff)
<< ", seqnum=" << seqnum
<< std::endl);
rawSend(*k);
rawSend(k);
// do not handle rtt here as we can't decide if this packet was
// lost or really takes more time to transmit
@ -375,7 +372,7 @@ bool ConnectionSendThread::rawSendAsPacket(session_t peer_id, u8 channelnum,
<< " INFO: queueing reliable packet for peer_id: " << peer_id
<< " channel: " << (u32)channelnum
<< " seqnum: " << seqnum << std::endl);
channel->queued_reliables.push(p);
channel->queued_reliables.push(std::move(p));
return false;
}
@ -717,13 +714,15 @@ void ConnectionSendThread::sendPackets(float dtime)
channel.outgoing_reliables_sent.size()
< channel.getWindowSize() &&
peer->m_increment_packets_remaining > 0) {
BufferedPacket p = channel.queued_reliables.front();
BufferedPacket p = std::move(channel.queued_reliables.front());
channel.queued_reliables.pop();
LOG(dout_con << m_connection->getDesc()
<< " INFO: sending a queued reliable packet "
<< " channel: " << i
<< ", seqnum: " << readU16(&p.data[BASE_HEADER_SIZE + 1])
<< std::endl);
sendAsPacketReliable(p, &channel);
peer->m_increment_packets_remaining--;
}
@ -911,7 +910,7 @@ void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata,
if (data_left) {
ConnectionEvent e;
e.dataReceived(peer_id, resultdata);
m_connection->putEvent(e);
m_connection->putEvent(std::move(e));
}
}
catch (ProcessedSilentlyException &e) {
@ -1022,7 +1021,7 @@ void ConnectionReceiveThread::receive(SharedBuffer<u8> &packetdata,
ConnectionEvent e;
e.dataReceived(peer_id, resultdata);
m_connection->putEvent(e);
m_connection->putEvent(std::move(e));
}
catch (ProcessedSilentlyException &e) {
}

@ -549,14 +549,11 @@ NetworkPacket& NetworkPacket::operator<<(video::SColor src)
return *this;
}
SharedBuffer<u8> NetworkPacket::oldForgePacket()
Buffer<u8> NetworkPacket::oldForgePacket()
{
SharedBuffer<u8> sb(m_datasize + 2);
Buffer<u8> sb(m_datasize + 2);
writeU16(&sb[0], m_command);
memcpy(&sb[2], m_data.data(), m_datasize);
u8* datas = getU8Ptr(0);
if (datas != NULL)
memcpy(&sb[2], datas, m_datasize);
return sb;
}

@ -115,7 +115,8 @@ public:
NetworkPacket &operator<<(video::SColor src);
// Temp, we remove SharedBuffer when migration finished
SharedBuffer<u8> oldForgePacket();
// ^ this comment has been here for 4 years
Buffer<u8> oldForgePacket();
private:
void checkReadOffset(u32 from_offset, u32 field_size);

@ -88,7 +88,7 @@ void TestConnection::testNetworkPacketSerialize()
};
if (sizeof(wchar_t) == 2)
warningstream << __func__ << " may fail on this platform." << std::endl;
warningstream << __FUNCTION__ << " may fail on this platform." << std::endl;
{
NetworkPacket pkt(123, 0);
@ -96,7 +96,7 @@ void TestConnection::testNetworkPacketSerialize()
// serializing wide strings should do surrogate encoding, we test that here
pkt << std::wstring(L"\U00020b9a");
SharedBuffer<u8> buf = pkt.oldForgePacket();
auto buf = pkt.oldForgePacket();
UASSERTEQ(int, buf.getSize(), sizeof(expected));
UASSERT(!memcmp(expected, &buf[0], buf.getSize()));
}
@ -280,7 +280,7 @@ void TestConnection::testConnectSendReceive()
NetworkPacket pkt;
pkt.putRawPacket((u8*) "Hello World !", 14, 0);
SharedBuffer<u8> sentdata = pkt.oldForgePacket();
auto sentdata = pkt.oldForgePacket();
infostream<<"** running client.Send()"<<std::endl;
client.Send(PEER_ID_SERVER, 0, &pkt, true);
@ -295,7 +295,7 @@ void TestConnection::testConnectSendReceive()
<< ", data=" << (const char*)pkt.getU8Ptr(0)
<< std::endl;
SharedBuffer<u8> recvdata = pkt.oldForgePacket();
auto recvdata = pkt.oldForgePacket();
UASSERT(memcmp(*sentdata, *recvdata, recvdata.getSize()) == 0);
}
@ -324,13 +324,13 @@ void TestConnection::testConnectSendReceive()
infostream << "...";
infostream << std::endl;
SharedBuffer<u8> sentdata = pkt.oldForgePacket();
auto sentdata = pkt.oldForgePacket();
server.Send(peer_id_client, 0, &pkt, true);
//sleep_ms(3000);
SharedBuffer<u8> recvdata;
Buffer<u8> recvdata;
infostream << "** running client.Receive()" << std::endl;
session_t peer_id = 132;
u16 size = 0;

@ -143,6 +143,13 @@ public:
m_signal.post();
}
void push_back(T &&t)
{
MutexAutoLock lock(m_mutex);
m_queue.push_back(std::move(t));
m_signal.post();
}
/* this version of pop_front returns a empty element of T on timeout.
* Make sure default constructor of T creates a recognizable "empty" element
*/

@ -51,6 +51,19 @@ public:
else
data = NULL;
}
Buffer(Buffer &&buffer)
{
m_size = buffer.m_size;
if(m_size != 0)
{
data = buffer.data;
buffer.data = nullptr;
buffer.m_size = 0;
}
else
data = nullptr;
}
// Copies whole buffer
Buffer(const T *t, unsigned int size)
{
m_size = size;
@ -62,10 +75,12 @@ public:
else
data = NULL;
}
~Buffer()
{
drop();
}
Buffer& operator=(const Buffer &buffer)
{
if(this == &buffer)
@ -81,6 +96,23 @@ public:
data = NULL;
return *this;
}
Buffer& operator=(Buffer &&buffer)
{
if(this == &buffer)
return *this;
drop();
m_size = buffer.m_size;
if(m_size != 0)
{
data = buffer.data;
buffer.data = nullptr;
buffer.m_size = 0;
}
else
data = nullptr;
return *this;
}
T & operator[](unsigned int i) const
{
return data[i];
@ -89,10 +121,12 @@ public:
{
return data;
}
unsigned int getSize() const
{
return m_size;
}
private:
void drop()
{