diff --git a/common/net/daybreak_connection.cpp b/common/net/daybreak_connection.cpp index 0fefc9982..92fd79b7c 100644 --- a/common/net/daybreak_connection.cpp +++ b/common/net/daybreak_connection.cpp @@ -44,7 +44,12 @@ void EQ::Net::DaybreakConnectionManager::Attach(uv_loop_t *loop) DaybreakConnectionManager *c = (DaybreakConnectionManager*)handle->data; c->UpdateDataBudget(); c->Process(); - c->ProcessResend(); + auto now = Clock::now(); + if (std::chrono::duration_cast(now - c->m_last_resend_check).count() > c->m_resend_check_interval) { + c->ProcessResend(); + c->m_last_resend_check = now; + return; + } }, update_rate, update_rate); uv_udp_init(loop, &m_socket); @@ -1091,69 +1096,72 @@ void EQ::Net::DaybreakConnection::ProcessResend() } } +// observed client receive window is 300 packets, 140KB +constexpr size_t MAX_CLIENT_RECV_PACKETS_PER_WINDOW = 300; +constexpr size_t MAX_CLIENT_RECV_BYTES_PER_WINDOW = 140 * 1024; + void EQ::Net::DaybreakConnection::ProcessResend(int stream) { if (m_status == DbProtocolStatus::StatusDisconnected) { return; } - auto resends = 0; - auto now = Clock::now(); - auto s = &m_streams[stream]; - for (auto &entry : s->sent_packets) { - auto time_since_last_send = std::chrono::duration_cast(now - entry.second.last_sent); - if (entry.second.times_resent == 0) { - if ((size_t)time_since_last_send.count() > entry.second.resend_delay) { - auto &p = entry.second.packet; - if (p.Length() >= DaybreakHeader::size()) { - if (p.GetInt8(0) == 0 && p.GetInt8(1) >= OP_Fragment && p.GetInt8(1) <= OP_Fragment4) { - m_stats.resent_fragments++; - } - else { - m_stats.resent_full++; - } - } - else { - m_stats.resent_full++; - } - m_stats.resent_packets++; + if (m_streams[stream].sent_packets.empty()) { + return; + } - InternalBufferedSend(p); - entry.second.last_sent = now; - entry.second.times_resent++; - entry.second.resend_delay = EQ::Clamp(entry.second.resend_delay * 2, m_owner->m_options.resend_delay_min, m_owner->m_options.resend_delay_max); - resends++; + m_resend_packets_sent = 0; + m_resend_bytes_sent = 0; + + auto now = Clock::now(); // Current time + auto s = &m_streams[stream]; + + // Get a reference resend delay (assume first packet represents the typical case) + size_t reference_resend_delay = 0; + if (!s->sent_packets.empty()) { + reference_resend_delay = s->sent_packets.begin()->second.resend_delay; + + // Check if the first packet has timed out + auto time_since_first_sent = std::chrono::duration_cast(now - s->sent_packets.begin()->second.first_sent).count(); + if (time_since_first_sent >= m_owner->m_options.resend_timeout) { + Close(); + return; + } + } + + for (auto &e: s->sent_packets) { + if (m_resend_packets_sent >= MAX_CLIENT_RECV_PACKETS_PER_WINDOW || + m_resend_bytes_sent >= MAX_CLIENT_RECV_BYTES_PER_WINDOW) { + break; + } + + auto &sp = e.second; + auto &p = sp.packet; + if (p.Length() >= DaybreakHeader::size()) { + if (p.GetInt8(0) == 0 && p.GetInt8(1) >= OP_Fragment && p.GetInt8(1) <= OP_Fragment4) { + m_stats.resent_fragments++; + } + else { + m_stats.resent_full++; } } else { - auto time_since_first_sent = std::chrono::duration_cast(now - entry.second.first_sent); - if (time_since_first_sent.count() >= m_owner->m_options.resend_timeout) { - Close(); - return; - } - - if ((size_t)time_since_last_send.count() > entry.second.resend_delay) { - auto &p = entry.second.packet; - if (p.Length() >= DaybreakHeader::size()) { - if (p.GetInt8(0) == 0 && p.GetInt8(1) >= OP_Fragment && p.GetInt8(1) <= OP_Fragment4) { - m_stats.resent_fragments++; - } - else { - m_stats.resent_full++; - } - } - else { - m_stats.resent_full++; - } - m_stats.resent_packets++; - - InternalBufferedSend(p); - entry.second.last_sent = now; - entry.second.times_resent++; - entry.second.resend_delay = EQ::Clamp(entry.second.resend_delay * 2, m_owner->m_options.resend_delay_min, m_owner->m_options.resend_delay_max); - resends++; - } + m_stats.resent_full++; } + m_stats.resent_packets++; + + // Resend the packet + InternalBufferedSend(p); + + m_resend_packets_sent++; + m_resend_bytes_sent += p.Length(); + sp.last_sent = now; + sp.times_resent++; + sp.resend_delay = EQ::Clamp( + sp.resend_delay * 2, + m_owner->m_options.resend_delay_min, + m_owner->m_options.resend_delay_max + ); } } diff --git a/common/net/daybreak_connection.h b/common/net/daybreak_connection.h index c2c714813..289d42975 100644 --- a/common/net/daybreak_connection.h +++ b/common/net/daybreak_connection.h @@ -181,6 +181,10 @@ namespace EQ Timestamp m_close_time; double m_outgoing_budget; + // resend tracking + size_t m_resend_packets_sent = 0; + size_t m_resend_bytes_sent = 0; + struct DaybreakSentPacket { DynamicPacket packet; @@ -318,6 +322,10 @@ namespace EQ void Attach(uv_loop_t *loop); void Detach(); + // resend + Timestamp m_last_resend_check; + int m_resend_check_interval = 50; + EQ::Random m_rand; uv_timer_t m_timer; uv_udp_t m_socket;